/**
 * @license
 * Copyright 2019 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

// Former goog.module ID: Blockly.utils.colour

/**
 * The richness of block colours, regardless of the hue.
 * Must be in the range of 0 (inclusive) to 1 (exclusive).
 */
let hsvSaturation = 0.45;

/**
 * Get the richness of block colours, regardless of the hue.
 *
 * @returns The current richness.
 * @internal
 */
export function getHsvSaturation(): number {
  return hsvSaturation;
}

/**
 * Set the richness of block colours, regardless of the hue.
 *
 * @param newSaturation The new richness, in the range of  0 (inclusive) to 1
 *     (exclusive)
 * @internal
 */
export function setHsvSaturation(newSaturation: number) {
  hsvSaturation = newSaturation;
}

/**
 * The intensity of block colours, regardless of the hue.
 * Must be in the range of 0 (inclusive) to 1 (exclusive).
 */
let hsvValue = 0.65;

/**
 * Get the intensity of block colours, regardless of the hue.
 *
 * @returns The current intensity.
 * @internal
 */
export function getHsvValue(): number {
  return hsvValue;
}

/**
 * Set the intensity of block colours, regardless of the hue.
 *
 * @param newValue The new intensity, in the range of  0 (inclusive) to 1
 *     (exclusive)
 * @internal
 */
export function setHsvValue(newValue: number) {
  hsvValue = newValue;
}

/**
 * Parses a colour from a string.
 * .parse('red') = '#ff0000'
 * .parse('#f00') = '#ff0000'
 * .parse('#ff0000') = '#ff0000'
 * .parse('0xff0000') = '#ff0000'
 * .parse('rgb(255, 0, 0)') = '#ff0000'
 *
 * @param str Colour in some CSS format.
 * @returns A string containing a hex representation of the colour, or null if
 *     can't be parsed.
 */
export function parse(str: string | number): string | null {
  str = `${str}`.toLowerCase().trim();
  let hex = names[str];
  if (hex) {
    // e.g. 'red'
    return hex;
  }
  hex = str.substring(0, 2) === '0x' ? '#' + str.substring(2) : str;
  hex = hex[0] === '#' ? hex : '#' + hex;
  if (/^#[0-9a-f]{6}$/.test(hex)) {
    // e.g. '#00ff88'
    return hex;
  }
  if (/^#[0-9a-f]{3}$/.test(hex)) {
    // e.g. '#0f8'
    return ['#', hex[1], hex[1], hex[2], hex[2], hex[3], hex[3]].join('');
  }
  const rgb = str.match(/^(?:rgb)?\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/);
  if (rgb) {
    // e.g. 'rgb(0, 128, 255)'
    const r = Number(rgb[1]);
    const g = Number(rgb[2]);
    const b = Number(rgb[3]);
    if (r >= 0 && r < 256 && g >= 0 && g < 256 && b >= 0 && b < 256) {
      return rgbToHex(r, g, b);
    }
  }
  return null;
}

/**
 * Converts a colour from RGB to hex representation.
 *
 * @param r Amount of red, int between 0 and 255.
 * @param g Amount of green, int between 0 and 255.
 * @param b Amount of blue, int between 0 and 255.
 * @returns Hex representation of the colour.
 */
export function rgbToHex(r: number, g: number, b: number): string {
  const rgb = (r << 16) | (g << 8) | b;
  if (r < 0x10) {
    return '#' + (0x1000000 | rgb).toString(16).substr(1);
  }
  return '#' + rgb.toString(16);
}

/**
 * Converts a colour to RGB.
 *
 * @param colour String representing colour in any colour format ('#ff0000',
 *     'red', '0xff000', etc).
 * @returns RGB representation of the colour.
 */
export function hexToRgb(colour: string): number[] {
  const hex = parse(colour);
  if (!hex) {
    return [0, 0, 0];
  }

  const rgb = parseInt(hex.substr(1), 16);
  const r = rgb >> 16;
  const g = (rgb >> 8) & 255;
  const b = rgb & 255;

  return [r, g, b];
}

/**
 * Converts an HSV triplet to hex representation.
 *
 * @param h Hue value in [0, 360].
 * @param s Saturation value in [0, 1].
 * @param v Brightness in [0, 255].
 * @returns Hex representation of the colour.
 */
export function hsvToHex(h: number, s: number, v: number): string {
  let red = 0;
  let green = 0;
  let blue = 0;
  if (s === 0) {
    red = v;
    green = v;
    blue = v;
  } else {
    const sextant = Math.floor(h / 60);
    const remainder = h / 60 - sextant;
    const val1 = v * (1 - s);
    const val2 = v * (1 - s * remainder);
    const val3 = v * (1 - s * (1 - remainder));
    switch (sextant) {
      case 1:
        red = val2;
        green = v;
        blue = val1;
        break;
      case 2:
        red = val1;
        green = v;
        blue = val3;
        break;
      case 3:
        red = val1;
        green = val2;
        blue = v;
        break;
      case 4:
        red = val3;
        green = val1;
        blue = v;
        break;
      case 5:
        red = v;
        green = val1;
        blue = val2;
        break;
      case 6:
      case 0:
        red = v;
        green = val3;
        blue = val1;
        break;
    }
  }
  return rgbToHex(Math.floor(red), Math.floor(green), Math.floor(blue));
}

/**
 * Blend two colours together, using the specified factor to indicate the
 * weight given to the first colour.
 *
 * @param colour1 First colour.
 * @param colour2 Second colour.
 * @param factor The weight to be given to colour1 over colour2.
 *     Values should be in the range [0, 1].
 * @returns Combined colour represented in hex.
 */
export function blend(
  colour1: string,
  colour2: string,
  factor: number,
): string | null {
  const hex1 = parse(colour1);
  if (!hex1) {
    return null;
  }
  const hex2 = parse(colour2);
  if (!hex2) {
    return null;
  }
  const rgb1 = hexToRgb(hex1);
  const rgb2 = hexToRgb(hex2);
  const r = Math.round(rgb2[0] + factor * (rgb1[0] - rgb2[0]));
  const g = Math.round(rgb2[1] + factor * (rgb1[1] - rgb2[1]));
  const b = Math.round(rgb2[2] + factor * (rgb1[2] - rgb2[2]));
  return rgbToHex(r, g, b);
}

/**
 * A map that contains the 16 basic colour keywords as defined by W3C:
 * https://www.w3.org/TR/2018/REC-css-color-3-20180619/#html4
 * The keys of this map are the lowercase "readable" names of the colours,
 * while the values are the "hex" values.
 */
export const names: {[key: string]: string} = {
  'aqua': '#00ffff',
  'black': '#000000',
  'blue': '#0000ff',
  'fuchsia': '#ff00ff',
  'gray': '#808080',
  'green': '#008000',
  'lime': '#00ff00',
  'maroon': '#800000',
  'navy': '#000080',
  'olive': '#808000',
  'purple': '#800080',
  'red': '#ff0000',
  'silver': '#c0c0c0',
  'teal': '#008080',
  'white': '#ffffff',
  'yellow': '#ffff00',
};

/**
 * Convert a hue (HSV model) into an RGB hex triplet.
 *
 * @param hue Hue on a colour wheel (0-360).
 * @returns RGB code, e.g. '#5ba65b'.
 */
export function hueToHex(hue: number): string {
  return hsvToHex(hue, hsvSaturation, hsvValue * 255);
}