import { clamp } from '../utils';
import { alpha as alphaType, number } from './numbers';
import { percent } from './units';
import { Color, RGBA, HSLA, NumberMap, ValueType } from '../types';
import { sanitize, singleColorRegex } from '../utils';

/*
  Get value from function string
  "translateX(20px)" -> "20px"
*/
export const getValueFromFunctionString = (value: string) =>
  value.substring(value.indexOf('(') + 1, value.lastIndexOf(')'));

const clampRgbUnit = clamp(0, 255);

// Prefer speed over completeness
const isRgba = (v: Color): v is RGBA => (v as RGBA).red !== undefined;
const isHsla = (v: Color): v is HSLA => (v as HSLA).hue !== undefined;

/**
 * Creates a function that will split color
 * values from string into an object of properties
 * `mapArrayToObject(['red', 'green', 'blue'])('rgba(0,0,0)')`
 */
const splitColorValues = (terms: string[]) => {
  return (v: string | RGBA | HSLA) => {
    if (typeof v !== 'string') return v;
    const values: NumberMap = {};
    const valuesArray = getValueFromFunctionString(v).split(/,\s*/); // Split '0,0' into ['0','0']

    // 4 because there are four props in each color type
    for (let i = 0; i < 4; i++) {
      // If this value is undefined return 1 - this is usually undefined alpha
      values[terms[i]] =
        valuesArray[i] !== undefined ? parseFloat(valuesArray[i]) : 1;
    }

    return values;
  };
};

// Templates
const rgbaTemplate = ({ red, green, blue, alpha = 1 }: RGBA) =>
  `rgba(${red}, ${green}, ${blue}, ${alpha})`;

const hslaTemplate = ({ hue, saturation, lightness, alpha = 1 }: HSLA) =>
  `hsla(${hue}, ${saturation}, ${lightness}, ${alpha})`;

// Value types
export const rgbUnit: ValueType = {
  ...number,
  transform: (v: number) => Math.round(clampRgbUnit(v))
};

function isColorString(color: string, colorType: '#' | 'hsl' | 'rgb') {
  return color.startsWith(colorType) && singleColorRegex.test(color);
}

export const rgba: ValueType = {
  test: v => (typeof v === 'string' ? isColorString(v, 'rgb') : isRgba(v)),
  parse: splitColorValues(['red', 'green', 'blue', 'alpha']),
  // TODO: Look into perhaps a mutative approach that doesn't create new objects
  transform: ({ red, green, blue, alpha = 1 }: RGBA) =>
    rgbaTemplate({
      red: rgbUnit.transform(red),
      green: rgbUnit.transform(green),
      blue: rgbUnit.transform(blue),
      alpha: sanitize(alphaType.transform(alpha))
    })
};

export const hsla: ValueType = {
  test: v => (typeof v === 'string' ? isColorString(v, 'hsl') : isHsla(v)),
  parse: splitColorValues(['hue', 'saturation', 'lightness', 'alpha']),
  transform: ({ hue, saturation, lightness, alpha = 1 }: HSLA) =>
    hslaTemplate({
      hue: Math.round(hue),
      saturation: percent.transform(sanitize(saturation)),
      lightness: percent.transform(sanitize(lightness)),
      alpha: sanitize(alphaType.transform(alpha))
    })
};

export const hex: ValueType = {
  ...rgba,
  test: v => typeof v === 'string' && isColorString(v, '#'),
  parse: (v: string): RGBA => {
    let r = '';
    let g = '';
    let b = '';

    // If we have 6 characters, ie #FF0000
    if (v.length > 4) {
      r = v.substr(1, 2);
      g = v.substr(3, 2);
      b = v.substr(5, 2);

      // Or we have 3 characters, ie #F00
    } else {
      r = v.substr(1, 1);
      g = v.substr(2, 1);
      b = v.substr(3, 1);
      r += r;
      g += g;
      b += b;
    }

    return {
      red: parseInt(r, 16),
      green: parseInt(g, 16),
      blue: parseInt(b, 16),
      alpha: 1
    };
  }
};

export const color: ValueType = {
  test: (v: any) =>
    (typeof v === 'string' && singleColorRegex.test(v)) ||
    isRgba(v) ||
    isHsla(v),
  parse: (v: any) => {
    if (rgba.test(v)) {
      return rgba.parse(v);
    } else if (hsla.test(v)) {
      return hsla.parse(v);
    } else if (hex.test(v)) {
      return hex.parse(v);
    }

    return v;
  },
  transform: (v: Color) => {
    if (isRgba(v)) {
      return rgba.transform(v);
    } else if (isHsla(v)) {
      return hsla.transform(v);
    }

    return v;
  }
};
