import type { DecodedPng, IndexedColorBitDepth } from './types';

/**
 * Converts indexed data into RGB/RGBA format
 * @param decodedImage - Image to decode data from.
 * @returns Uint8Array with RGB data.
 */
export function convertIndexedToRgb(decodedImage: DecodedPng) {
  const palette = decodedImage.palette;
  const depth = decodedImage.depth as IndexedColorBitDepth;
  if (!palette) {
    throw new Error('Color palette is undefined.');
  }
  checkDataSize(decodedImage);
  const indexSize = decodedImage.width * decodedImage.height;
  const resSize = indexSize * palette[0].length;
  const res = new Uint8Array(resSize);
  let indexPos = 0;
  let offset = 0;
  const indexes = new Uint8Array(indexSize);
  let bit = 0xff;
  switch (depth) {
    case 1:
      bit = 0x80;
      break;
    case 2:
      bit = 0xc0;
      break;
    case 4:
      bit = 0xf0;
      break;
    case 8:
      bit = 0xff;
      break;
    default:
      throw new Error('Incorrect depth value');
  }
  for (const byte of decodedImage.data) {
    let bit2 = bit;
    let shift = 8;
    while (bit2) {
      shift -= depth;
      indexes[indexPos++] = (byte & bit2) >> shift;

      bit2 = bit2 >> depth;
      if (indexPos % decodedImage.width === 0) {
        break;
      }
    }
  }

  if (decodedImage.palette) {
    for (const index of indexes) {
      const color = decodedImage.palette.at(index);
      if (!color) {
        throw new Error('Incorrect index of palette color');
      }
      res.set(color, offset);
      offset += color.length;
    }
  }
  return res;
}

function checkDataSize(image: DecodedPng): void {
  const expectedSize =
    image.depth < 8
      ? Math.ceil((image.width * image.depth) / 8) *
        image.height *
        image.channels
      : image.width * image.height * image.channels;

  if (image.data.length !== expectedSize) {
    throw new RangeError(
      `wrong data size. Found ${image.data.length}, expected ${expectedSize}`,
    );
  }
}
