const randseed = new Array(4); // Xorshift: [x, y, z, w] 32 bit values

interface BlockiesOpt {
  seed: string;
  size: number;
  scale: number;
  color?: string;
  bgcolor?: string;
  spotcolor?: string;
}

export function getBlocky(address: string) {
  if (!address || typeof address !== 'string') {
    return;
  }

  const blockiesData = createIcon({
    seed: address?.toLowerCase(),
    size: 8,
    scale: 4,
  }).toDataURL();

  return blockiesData;
}

function seedrand(seed: string) {
  for (let i = 0; i < randseed.length; i++) {
    randseed[i] = 0;
  }
  for (let i = 0; i < seed.length; i++) {
    randseed[i % 4] = (randseed[i % 4] << 5) - randseed[i % 4] + seed.charCodeAt(i);
  }
}

function rand() {
  // based on Java's String.hashCode(), expanded to 4 32bit values
  const t = randseed[0] ^ (randseed[0] << 11);

  randseed[0] = randseed[1];
  randseed[1] = randseed[2];
  randseed[2] = randseed[3];
  randseed[3] = randseed[3] ^ (randseed[3] >> 19) ^ t ^ (t >> 8);

  return (randseed[3] >>> 0) / ((1 << 31) >>> 0);
}

function createColor() {
  //saturation is the whole color spectrum
  const h = Math.floor(rand() * 360);
  //saturation goes from 40 to 100, it avoids greyish colors
  const s = rand() * 60 + 40 + '%';
  //lightness can be anything from 0 to 100, but probabilities are a bell curve around 50%
  const l = (rand() + rand() + rand() + rand()) * 25 + '%';

  const color = 'hsl(' + h + ',' + s + ',' + l + ')';
  return color;
}

function createImageData(size: number) {
  const width = size; // Only support square icons for now
  const height = size;

  const dataWidth = Math.ceil(width / 2);
  const mirrorWidth = width - dataWidth;

  const data = [];
  for (let y = 0; y < height; y++) {
    let row = [];
    for (let x = 0; x < dataWidth; x++) {
      // this makes foreground and background color to have a 43% (1/2.3) probability
      // spot color has 13% chance
      row[x] = Math.floor(rand() * 2.3);
    }
    const r = row.slice(0, mirrorWidth);
    r.reverse();
    row = row.concat(r);

    for (let i = 0; i < row.length; i++) {
      data.push(row[i]);
    }
  }

  return data;
}

function buildOpts(opts: Partial<BlockiesOpt>): BlockiesOpt {
  const seed = opts.seed || Math.floor(Math.random() * Math.pow(10, 16)).toString(16);
  seedrand(seed);

  return {
    seed,
    size: opts.size || 8,
    scale: opts.scale || 4,
    color: opts.color || createColor(),
    bgcolor: opts.bgcolor || createColor(),
    spotcolor: opts.spotcolor || createColor(),
  };
}

function renderIcon(opts: Partial<BlockiesOpt>, canvas: HTMLCanvasElement) {
  const fullOpts = buildOpts(opts);
  const imageData = createImageData(fullOpts.size);
  const width = Math.sqrt(imageData.length);

  canvas.width = canvas.height = fullOpts.size * fullOpts.scale;

  const cc = canvas.getContext('2d')!;
  cc.fillStyle = fullOpts.bgcolor!;
  cc.fillRect(0, 0, canvas.width, canvas.height);
  cc.fillStyle = fullOpts.color!;

  for (let i = 0; i < imageData.length; i++) {
    // if data is 0, leave the background
    if (imageData[i]) {
      const row = Math.floor(i / width);
      const col = i % width;

      // if data is 2, choose spot color, if 1 choose foreground
      cc.fillStyle = imageData[i] == 1 ? fullOpts.color! : fullOpts.spotcolor!;

      cc.fillRect(col * fullOpts.scale, row * fullOpts.scale, fullOpts.scale, fullOpts.scale);
    }
  }
  return canvas;
}

export function createIcon(opts: BlockiesOpt) {
  const canvas = document.createElement('canvas');

  renderIcon(opts, canvas);

  return canvas;
}
