import type { SkColor, Color as InputColor } from "../types";

const alphaf = (c: number) => ((c >> 24) & 255) / 255;
const red = (c: number) => (c >> 16) & 255;
const green = (c: number) => (c >> 8) & 255;
const blue = (c: number) => c & 255;

// From https://raw.githubusercontent.com/deanm/css-color-parser-js/master/csscolorparser.js
const CSSColorTable = {
  transparent: Float32Array.of(0, 0, 0, 0),
  aliceblue: Float32Array.of(240, 248, 255, 1),
  antiquewhite: Float32Array.of(250, 235, 215, 1),
  aqua: Float32Array.of(0, 255, 255, 1),
  aquamarine: Float32Array.of(127, 255, 212, 1),
  azure: Float32Array.of(240, 255, 255, 1),
  beige: Float32Array.of(245, 245, 220, 1),
  bisque: Float32Array.of(255, 228, 196, 1),
  black: Float32Array.of(0, 0, 0, 1),
  blanchedalmond: Float32Array.of(255, 235, 205, 1),
  blue: Float32Array.of(0, 0, 255, 1),
  blueviolet: Float32Array.of(138, 43, 226, 1),
  brown: Float32Array.of(165, 42, 42, 1),
  burlywood: Float32Array.of(222, 184, 135, 1),
  cadetblue: Float32Array.of(95, 158, 160, 1),
  chartreuse: Float32Array.of(127, 255, 0, 1),
  chocolate: Float32Array.of(210, 105, 30, 1),
  coral: Float32Array.of(255, 127, 80, 1),
  cornflowerblue: Float32Array.of(100, 149, 237, 1),
  cornsilk: Float32Array.of(255, 248, 220, 1),
  crimson: Float32Array.of(220, 20, 60, 1),
  cyan: Float32Array.of(0, 255, 255, 1),
  darkblue: Float32Array.of(0, 0, 139, 1),
  darkcyan: Float32Array.of(0, 139, 139, 1),
  darkgoldenrod: Float32Array.of(184, 134, 11, 1),
  darkgray: Float32Array.of(169, 169, 169, 1),
  darkgreen: Float32Array.of(0, 100, 0, 1),
  darkgrey: Float32Array.of(169, 169, 169, 1),
  darkkhaki: Float32Array.of(189, 183, 107, 1),
  darkmagenta: Float32Array.of(139, 0, 139, 1),
  darkolivegreen: Float32Array.of(85, 107, 47, 1),
  darkorange: Float32Array.of(255, 140, 0, 1),
  darkorchid: Float32Array.of(153, 50, 204, 1),
  darkred: Float32Array.of(139, 0, 0, 1),
  darksalmon: Float32Array.of(233, 150, 122, 1),
  darkseagreen: Float32Array.of(143, 188, 143, 1),
  darkslateblue: Float32Array.of(72, 61, 139, 1),
  darkslategray: Float32Array.of(47, 79, 79, 1),
  darkslategrey: Float32Array.of(47, 79, 79, 1),
  darkturquoise: Float32Array.of(0, 206, 209, 1),
  darkviolet: Float32Array.of(148, 0, 211, 1),
  deeppink: Float32Array.of(255, 20, 147, 1),
  deepskyblue: Float32Array.of(0, 191, 255, 1),
  dimgray: Float32Array.of(105, 105, 105, 1),
  dimgrey: Float32Array.of(105, 105, 105, 1),
  dodgerblue: Float32Array.of(30, 144, 255, 1),
  firebrick: Float32Array.of(178, 34, 34, 1),
  floralwhite: Float32Array.of(255, 250, 240, 1),
  forestgreen: Float32Array.of(34, 139, 34, 1),
  fuchsia: Float32Array.of(255, 0, 255, 1),
  gainsboro: Float32Array.of(220, 220, 220, 1),
  ghostwhite: Float32Array.of(248, 248, 255, 1),
  gold: Float32Array.of(255, 215, 0, 1),
  goldenrod: Float32Array.of(218, 165, 32, 1),
  gray: Float32Array.of(128, 128, 128, 1),
  green: Float32Array.of(0, 128, 0, 1),
  greenyellow: Float32Array.of(173, 255, 47, 1),
  grey: Float32Array.of(128, 128, 128, 1),
  honeydew: Float32Array.of(240, 255, 240, 1),
  hotpink: Float32Array.of(255, 105, 180, 1),
  indianred: Float32Array.of(205, 92, 92, 1),
  indigo: Float32Array.of(75, 0, 130, 1),
  ivory: Float32Array.of(255, 255, 240, 1),
  khaki: Float32Array.of(240, 230, 140, 1),
  lavender: Float32Array.of(230, 230, 250, 1),
  lavenderblush: Float32Array.of(255, 240, 245, 1),
  lawngreen: Float32Array.of(124, 252, 0, 1),
  lemonchiffon: Float32Array.of(255, 250, 205, 1),
  lightblue: Float32Array.of(173, 216, 230, 1),
  lightcoral: Float32Array.of(240, 128, 128, 1),
  lightcyan: Float32Array.of(224, 255, 255, 1),
  lightgoldenrodyellow: Float32Array.of(250, 250, 210, 1),
  lightgray: Float32Array.of(211, 211, 211, 1),
  lightgreen: Float32Array.of(144, 238, 144, 1),
  lightgrey: Float32Array.of(211, 211, 211, 1),
  lightpink: Float32Array.of(255, 182, 193, 1),
  lightsalmon: Float32Array.of(255, 160, 122, 1),
  lightseagreen: Float32Array.of(32, 178, 170, 1),
  lightskyblue: Float32Array.of(135, 206, 250, 1),
  lightslategray: Float32Array.of(119, 136, 153, 1),
  lightslategrey: Float32Array.of(119, 136, 153, 1),
  lightsteelblue: Float32Array.of(176, 196, 222, 1),
  lightyellow: Float32Array.of(255, 255, 224, 1),
  lime: Float32Array.of(0, 255, 0, 1),
  limegreen: Float32Array.of(50, 205, 50, 1),
  linen: Float32Array.of(250, 240, 230, 1),
  magenta: Float32Array.of(255, 0, 255, 1),
  maroon: Float32Array.of(128, 0, 0, 1),
  mediumaquamarine: Float32Array.of(102, 205, 170, 1),
  mediumblue: Float32Array.of(0, 0, 205, 1),
  mediumorchid: Float32Array.of(186, 85, 211, 1),
  mediumpurple: Float32Array.of(147, 112, 219, 1),
  mediumseagreen: Float32Array.of(60, 179, 113, 1),
  mediumslateblue: Float32Array.of(123, 104, 238, 1),
  mediumspringgreen: Float32Array.of(0, 250, 154, 1),
  mediumturquoise: Float32Array.of(72, 209, 204, 1),
  mediumvioletred: Float32Array.of(199, 21, 133, 1),
  midnightblue: Float32Array.of(25, 25, 112, 1),
  mintcream: Float32Array.of(245, 255, 250, 1),
  mistyrose: Float32Array.of(255, 228, 225, 1),
  moccasin: Float32Array.of(255, 228, 181, 1),
  navajowhite: Float32Array.of(255, 222, 173, 1),
  navy: Float32Array.of(0, 0, 128, 1),
  oldlace: Float32Array.of(253, 245, 230, 1),
  olive: Float32Array.of(128, 128, 0, 1),
  olivedrab: Float32Array.of(107, 142, 35, 1),
  orange: Float32Array.of(255, 165, 0, 1),
  orangered: Float32Array.of(255, 69, 0, 1),
  orchid: Float32Array.of(218, 112, 214, 1),
  palegoldenrod: Float32Array.of(238, 232, 170, 1),
  palegreen: Float32Array.of(152, 251, 152, 1),
  paleturquoise: Float32Array.of(175, 238, 238, 1),
  palevioletred: Float32Array.of(219, 112, 147, 1),
  papayawhip: Float32Array.of(255, 239, 213, 1),
  peachpuff: Float32Array.of(255, 218, 185, 1),
  peru: Float32Array.of(205, 133, 63, 1),
  pink: Float32Array.of(255, 192, 203, 1),
  plum: Float32Array.of(221, 160, 221, 1),
  powderblue: Float32Array.of(176, 224, 230, 1),
  purple: Float32Array.of(128, 0, 128, 1),
  rebeccapurple: Float32Array.of(102, 51, 153, 1),
  red: Float32Array.of(255, 0, 0, 1),
  rosybrown: Float32Array.of(188, 143, 143, 1),
  royalblue: Float32Array.of(65, 105, 225, 1),
  saddlebrown: Float32Array.of(139, 69, 19, 1),
  salmon: Float32Array.of(250, 128, 114, 1),
  sandybrown: Float32Array.of(244, 164, 96, 1),
  seagreen: Float32Array.of(46, 139, 87, 1),
  seashell: Float32Array.of(255, 245, 238, 1),
  sienna: Float32Array.of(160, 82, 45, 1),
  silver: Float32Array.of(192, 192, 192, 1),
  skyblue: Float32Array.of(135, 206, 235, 1),
  slateblue: Float32Array.of(106, 90, 205, 1),
  slategray: Float32Array.of(112, 128, 144, 1),
  slategrey: Float32Array.of(112, 128, 144, 1),
  snow: Float32Array.of(255, 250, 250, 1),
  springgreen: Float32Array.of(0, 255, 127, 1),
  steelblue: Float32Array.of(70, 130, 180, 1),
  tan: Float32Array.of(210, 180, 140, 1),
  teal: Float32Array.of(0, 128, 128, 1),
  thistle: Float32Array.of(216, 191, 216, 1),
  tomato: Float32Array.of(255, 99, 71, 1),
  turquoise: Float32Array.of(64, 224, 208, 1),
  violet: Float32Array.of(238, 130, 238, 1),
  wheat: Float32Array.of(245, 222, 179, 1),
  white: Float32Array.of(255, 255, 255, 1),
  whitesmoke: Float32Array.of(245, 245, 245, 1),
  yellow: Float32Array.of(255, 255, 0, 1),
  yellowgreen: Float32Array.of(154, 205, 50, 1),
};

const clampCSSByte = (j: number) => {
  // Clamp to integer 0 .. 255.
  const i = Math.round(j); // Seems to be what Chrome does (vs truncation).
  // eslint-disable-next-line no-nested-ternary
  return i < 0 ? 0 : i > 255 ? 255 : i;
};

const clampCSSFloat = (f: number) => {
  // eslint-disable-next-line no-nested-ternary
  return f < 0 ? 0 : f > 1 ? 1 : f;
};

const parseCSSInt = (str: string) => {
  // int or percentage.
  if (str[str.length - 1] === "%") {
    return clampCSSByte((parseFloat(str) / 100) * 255);
  }

  return clampCSSByte(parseInt(str));
};

const parseCSSFloat = (str: string | undefined) => {
  if (str === undefined) {
    return 1;
  }
  // float or percentage.
  if (str[str.length - 1] === "%") {
    return clampCSSFloat(parseFloat(str) / 100);
  }
  return clampCSSFloat(parseFloat(str));
};

const CSSHueToRGB = (m1: number, m2: number, h: number) => {
  if (h < 0) {
    h += 1;
  } else if (h > 1) {
    h -= 1;
  }

  if (h * 6 < 1) {
    return m1 + (m2 - m1) * h * 6;
  }
  if (h * 2 < 1) {
    return m2;
  }
  if (h * 3 < 2) {
    return m1 + (m2 - m1) * (2 / 3 - h) * 6;
  }
  return m1;
};

const parseCSSColor = (cssStr: string) => {
  // Remove all whitespace, not compliant, but should just be more accepting.
  var str = cssStr.replace(/ /g, "").toLowerCase();

  // Color keywords (and transparent) lookup.
  if (str in CSSColorTable) {
    const cl = CSSColorTable[str as keyof typeof CSSColorTable];
    if (cl) {
      return Float32Array.of(...cl);
    }
    return null;
  } // dup.

  // #abc and #abc123 syntax.
  if (str[0] === "#") {
    if (str.length === 4) {
      var iv = parseInt(str.substr(1), 16); // TODO(deanm): Stricter parsing.
      if (!(iv >= 0 && iv <= 0xfff)) {
        return null;
      } // Covers NaN.
      return [
        ((iv & 0xf00) >> 4) | ((iv & 0xf00) >> 8),
        (iv & 0xf0) | ((iv & 0xf0) >> 4),
        (iv & 0xf) | ((iv & 0xf) << 4),
        1,
      ];
    } else if (str.length === 7) {
      var iv = parseInt(str.substr(1), 16); // TODO(deanm): Stricter parsing.
      if (!(iv >= 0 && iv <= 0xffffff)) {
        return null;
      } // Covers NaN.
      return [(iv & 0xff0000) >> 16, (iv & 0xff00) >> 8, iv & 0xff, 1];
    } else if (str.length === 9) {
      var iv = parseInt(str.substr(1), 16); // TODO(deanm): Stricter parsing.
      if (!(iv >= 0 && iv <= 0xffffffff)) {
        return null; // Covers NaN.
      }
      return [
        ((iv & 0xff000000) >> 24) & 0xff,
        (iv & 0xff0000) >> 16,
        (iv & 0xff00) >> 8,
        (iv & 0xff) / 255,
      ];
    }

    return null;
  }

  var op = str.indexOf("("),
    ep = str.indexOf(")");
  if (op !== -1 && ep + 1 === str.length) {
    var fname = str.substr(0, op);
    var params = str.substr(op + 1, ep - (op + 1)).split(",");
    var alpha = 1; // To allow case fallthrough.
    switch (fname) {
      // eslint-disable-next-line @typescript-eslint/ban-ts-comment
      //@ts-expect-error
      case "rgba":
        if (params.length !== 4) {
          return null;
        }
        alpha = parseCSSFloat(params.pop());
      // Fall through.
      case "rgb":
        if (params.length !== 3) {
          return null;
        }
        return [
          parseCSSInt(params[0]),
          parseCSSInt(params[1]),
          parseCSSInt(params[2]),
          alpha,
        ];
      // eslint-disable-next-line @typescript-eslint/ban-ts-comment
      //@ts-expect-error
      case "hsla":
        if (params.length !== 4) {
          return null;
        }
        alpha = parseCSSFloat(params.pop());
      // Fall through.
      case "hsl":
        if (params.length !== 3) {
          return null;
        }
        var h = (((parseFloat(params[0]) % 360) + 360) % 360) / 360; // 0 .. 1
        // NOTE(deanm): According to the CSS spec s/l should only be
        // percentages, but we don't bother and let float or percentage.
        var s = parseCSSFloat(params[1]);
        var l = parseCSSFloat(params[2]);
        var m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s;
        var m1 = l * 2 - m2;
        return [
          clampCSSByte(CSSHueToRGB(m1, m2, h + 1 / 3) * 255),
          clampCSSByte(CSSHueToRGB(m1, m2, h) * 255),
          clampCSSByte(CSSHueToRGB(m1, m2, h - 1 / 3) * 255),
          alpha,
        ];
      default:
        return null;
    }
  }

  return null;
};

export const Color = (color: InputColor): SkColor => {
  if (color instanceof Float32Array) {
    return color;
  } else if (Array.isArray(color)) {
    return new Float32Array(color);
  } else if (typeof color === "string") {
    const r = parseCSSColor(color);
    const rgba = r === null ? CSSColorTable.black : r;
    return Float32Array.of(
      rgba[0] / 255,
      rgba[1] / 255,
      rgba[2] / 255,
      rgba[3]
    );
  } else {
    return Float32Array.of(
      red(color) / 255,
      green(color) / 255,
      blue(color) / 255,
      alphaf(color)
    );
  }
};
