import dE00 from "./dE00";

export const D65 = [
  0.4124564, 0.3575761, 0.1804375,
  0.2126729, 0.7151522, 0.0721750,
  0.0193339, 0.1191920, 0.9503041];

export const rD65 = [
  3.2404542, -1.5371385, -0.4985314,
  -0.9692660, 1.8760108, 0.0415560,
  0.0556434, -0.2040259, 1.0572252
]


// 0.008856  Initial CIE standard
// 216/24389 Intent of CIE standard, updated to standard in 2004
export const cieE = 216 / 24389;
// 903.3     Initial CIE standard
// 24389/27  Intent of CIE standard, updated to standard in 2004
export const cieK = 24389 / 27;


export interface referenceWhite {
  x: number
  y: number
  z: number
}
export const D65_ReferenceWhite = { x: 0.95047, y: 1, z: 1.08883 };

/**
 * CIELab color space.
 */
export class Lab {
  constructor(public L: number = 0, public a: number = 0, public b: number = 0) { }
  toString() { return `Lab(${this.L}, ${this.a}, ${this.b})`; }

  static fromsRGB(srgb: sRGB): Lab {
    return srgb.toXYZ().toLab();
  }

  static fromRGBA(rgba: number): Lab {
    return sRGB.fromRGBA(rgba).toXYZ().toLab();
  }


  deltaE(other: Lab): number {
    //return this.deltaE76(other);
    //return this.deltaE94(other);
    return this.deltaE00(other);
  }

  /**
   * 计算两个颜色值的距离
   * 参考： https://en.wikipedia.org/wiki/Color_difference#CIE76 
   * @param other 
   * @returns 
   */
  deltaE76(other: Lab): number {
    return Math.sqrt(
      Math.pow(this.L - other.L, 2) +
      Math.pow(this.a - other.a, 2) +
      Math.pow(this.b - other.b, 2)
    )
  }

  /**
   * 计算两个颜色的距离
   * 参考： https://en.wikipedia.org/wiki/Color_difference#CIE94
   * @param other 
   * @returns 
   */
  deltaE94(other: Lab): number {
    let self = this;
    const KL = 1;
    const K1 = 0.045;
    const K2 = 0.015;
    const wLightness = 1;
    const wChroma = 1;


    let deltaL = self.L - other.L;
    let c1 = Math.sqrt(self.a * self.a + self.b * self.b);
    let c2 = Math.sqrt(other.a * other.a + other.b * other.b);
    let deltaCab = c1 - c2;

    let deltaA = self.a - other.a;
    let deltaB = self.b - other.b;
    let deltaHab = Math.sqrt(deltaA * deltaA + deltaB * deltaB - deltaCab * deltaCab) || 0;

    let Sl = 1;
    let Sc = 1 + K1 * c1;
    let Sh = 1 + K2 * c1;

    return Math.sqrt(
      Math.pow(deltaL / (KL * Sl), 2) +
      Math.pow(deltaCab / (wChroma * Sc), 2) +
      Math.pow(deltaHab / Sh, 2)
    );

  }

  deltaE00(other: Lab) {
    let self = this;
    let x1 = { L: self.L, A: self.a, B: self.b };
    let x2 = { L: other.L, A: other.a, B: other.b };
    return new dE00(x1, x2, { hue: 1 }).getDeltaE();
  }

}

/**
 * XYZ color space
 */
export class XYZ {
  constructor(public x: number = 0, public y: number = 0, public z: number = 0) { }
  toString() { return `XYZ(${this.x}, ${this.y}, ${this.z})`; }

  tosRGB(): sRGB {
    let srgb = new sRGB();
    let lsrgb = new sRGB(); //linear sRGB
    let xyz = this;
    lsrgb.r = rD65[0] * xyz.x + rD65[1] * xyz.y + rD65[2] * xyz.z;
    lsrgb.g = rD65[3] * xyz.x + rD65[4] * xyz.y + rD65[5] * xyz.z;
    lsrgb.b = rD65[6] * xyz.x + rD65[7] * xyz.y + rD65[8] * xyz.z;
    srgb.r = lsrgb.r <= 0.0031308 ? 12.92 * lsrgb.r : 1.055 * Math.pow(lsrgb.r, 1 / 2.4) - 0.055;
    srgb.g = lsrgb.g <= 0.0031308 ? 12.92 * lsrgb.g : 1.055 * Math.pow(lsrgb.g, 1 / 2.4) - 0.055;
    srgb.b = lsrgb.b <= 0.0031308 ? 12.92 * lsrgb.b : 1.055 * Math.pow(lsrgb.b, 1 / 2.4) - 0.055;
    return srgb;
  }

  toLab(): Lab {
    let xyz = this;
    let xr = xyz.x / D65_ReferenceWhite.x;
    let yr = xyz.y / D65_ReferenceWhite.y;
    let zr = xyz.z / D65_ReferenceWhite.z;
    let fx = xr > cieE ? Math.pow(xr, 1 / 3) : (cieK * xr + 16) / 116;
    let fy = yr > cieE ? Math.pow(yr, 1 / 3) : (cieK * yr + 16) / 116;
    let fz = zr > cieE ? Math.pow(zr, 1 / 3) : (cieK * zr + 16) / 116;

    let L = 116 * fy - 16;
    let a = 500 * (fx - fy);
    let b = 200 * (fy - fz);
    L = Math.max(L, 0) // specular white can be over 100

    return new Lab(L, a, b);
  }
}

/**
 * Standard RGB color
 */
export class sRGB {
  constructor(public r: number = 0, public g: number = 0, public b: number = 0) { }
  toString() { return `sRGB(${this.r}, ${this.g}, ${this.b})` }

  /**
   * Convert to XYZ color space.
   * @returns 
   */
  toXYZ(): XYZ {
    let srgb = this;
    let lsrgb: sRGB = new sRGB(); //linear sRGB
    let xyz: XYZ = new XYZ();
    lsrgb.r = srgb.r <= 0.0405 ? srgb.r / 12.92 : Math.pow((srgb.r + 0.055) / 1.055, 2.4);
    lsrgb.g = srgb.g <= 0.0405 ? srgb.g / 12.92 : Math.pow((srgb.g + 0.055) / 1.055, 2.4);
    lsrgb.b = srgb.b <= 0.0405 ? srgb.b / 12.92 : Math.pow((srgb.b + 0.055) / 1.055, 2.4);
    xyz.x = lsrgb.r * D65[0] + lsrgb.g * D65[1] + lsrgb.b * D65[2];
    xyz.y = lsrgb.r * D65[3] + lsrgb.g * D65[4] + lsrgb.b * D65[5];
    xyz.z = lsrgb.r * D65[6] + lsrgb.g * D65[7] + lsrgb.b * D65[8];
    return xyz;
  }

  /**
   * Convert sRGBA to color hex.
   * @returns 
   */
  toHex(): string {
    let r = this.partToHex(this.r);
    let g = this.partToHex(this.g);
    let b = this.partToHex(this.b);
    return [r, g, b].join('');
  }

  /**
   * Convert single rgb part to HEX.
   * @param part 
   * @returns 
   */
  partToHex(part: number) {
    let i: number = Math.round(part * 255);
    i = i > 255 ? 255 : i;
    i = i < 0 ? 0 : i;
    return i.toString(16).padStart(2, '0');
  }

  /**
   * RGBA integer to sRGB
   * @param rgba 
   * @returns 
   */
  static fromRGBA(rgba: number) {
    let uint32 = new Uint32Array(1);
    uint32[0] = rgba;
    let uint8 = new Uint8Array(uint32.buffer);
    return new sRGB(uint8[0] / 255, uint8[1] / 255, uint8[2] / 255);
  }

  static white() : sRGB {
    return new sRGB(1, 1, 1);
  }

  static black() : sRGB {
    return new sRGB(0, 0, 0);
  }

}



