/* eslint-disable max-len */
/* eslint-disable no-bitwise */
/**
* @external Math
*/
/**
* @memberof external:Math
* @param {Float} value
* @param {Float} precision
* @returns {Float}
*/
function toFixedNumeric (value, precision) {
  if (precision === undefined) precision = 0;
  return Math.round(value * (10 ** precision)) / (10 ** precision);
}
/**
 * Whether a value is `null` or `undefined`.
 * @param {any} val
 * @returns {boolean}
 */
const isNullish = (val) => {
  return val === null || val === undefined;
};
/**
 * Controls for all the input elements for the typing in color values.
 */
export default class ColorValuePicker {
  /**
   * @param {external:jQuery} picker
   * @param {external:jQuery.jPicker.Color} color
   * @param {external:jQuery.fn.$.fn.jPicker} bindedHex
   * @param {Float} alphaPrecision
   */
  constructor (picker, color, bindedHex, alphaPrecision) {
    const that = this; // private properties and methods
    const inputs = picker.querySelectorAll('td.Text input');
    // input box key down - use arrows to alter color
    /**
     *
     * @param {Event} e
     * @returns {Event|false|void}
     */
    function keyDown (e) {
      if (e.target.value === '' && e.target !== hex && ((!isNullish(bindedHex) && e.target !== bindedHex) || isNullish(bindedHex))) return undefined;
      if (!validateKey(e)) return e;
      switch (e.target) {
      case red:
        switch (e.keyCode) {
        case 38:
          red.value = setValueInRange.call(that, (red.value << 0) + 1, 0, 255);
          color.val('r', red.value, e.target);
          return false;
        case 40:
          red.value = setValueInRange.call(that, (red.value << 0) - 1, 0, 255);
          color.val('r', red.value, e.target);
          return false;
        }
        break;
      case green:
        switch (e.keyCode) {
        case 38:
          green.value = setValueInRange.call(that, (green.value << 0) + 1, 0, 255);
          color.val('g', green.value, e.target);
          return false;
        case 40:
          green.value = setValueInRange.call(that, (green.value << 0) - 1, 0, 255);
          color.val('g', green.value, e.target);
          return false;
        }
        break;
      case blue:
        switch (e.keyCode) {
        case 38:
          blue.value = setValueInRange.call(that, (blue.value << 0) + 1, 0, 255);
          color.val('b', blue.value, e.target);
          return false;
        case 40:
          blue.value = setValueInRange.call(that, (blue.value << 0) - 1, 0, 255);
          color.val('b', blue.value, e.target);
          return false;
        }
        break;
      case alpha:
        switch (e.keyCode) {
        case 38:
          alpha.value = setValueInRange.call(that, Number.parseFloat(alpha.value) + 1, 0, 100);
          color.val('a', toFixedNumeric((alpha.value * 255) / 100, alphaPrecision), e.target);
          return false;
        case 40:
          alpha.value = setValueInRange.call(that, Number.parseFloat(alpha.value) - 1, 0, 100);
          color.val('a', toFixedNumeric((alpha.value * 255) / 100, alphaPrecision), e.target);
          return false;
        }
        break;
      case hue:
        switch (e.keyCode) {
        case 38:
          hue.value = setValueInRange.call(that, (hue.value << 0) + 1, 0, 360);
          color.val('h', hue.value, e.target);
          return false;
        case 40:
          hue.value =setValueInRange.call(that, (hue.value << 0) - 1, 0, 360);
          color.val('h', hue.value, e.target);
          return false;
        }
        break;
      case saturation:
        switch (e.keyCode) {
        case 38:
          saturation.value = setValueInRange.call(that, (saturation.value << 0) + 1, 0, 100);
          color.val('s', saturation.value, e.target);
          return false;
        case 40:
          saturation.value = setValueInRange.call(that, (saturation.value << 0) - 1, 0, 100);
          color.val('s', saturation.value, e.target);
          return false;
        }
        break;
      case value:
        switch (e.keyCode) {
        case 38:
          value.value = setValueInRange.call(that, (value.value << 0) + 1, 0, 100);
          color.val('v', value.value, e.target);
          return false;
        case 40:
          value.value = setValueInRange.call(that, (value.value << 0) - 1, 0, 100);
          color.val('v', value.value, e.target);
          return false;
        }
        break;
      }
      return undefined;
    }
    // input box key up - validate value and set color
    /**
    * @param {Event} e
    * @returns {Event|void}
    * @todo Why is this returning an event?
    */
    function keyUp (e) {
      if (e.target.value === '' && e.target !== hex &&
        ((!isNullish(bindedHex) && e.target !== bindedHex) ||
        isNullish(bindedHex))) return undefined;
      if (!validateKey(e)) return e;
      switch (e.target) {
      case red:
        red.value = setValueInRange.call(that, red.value, 0, 255);
        color.val('r', red.value, e.target);
        break;
      case green:
        green.value = setValueInRange.call(that, green.value, 0, 255);
        color.val('g', green.value, e.target);
        break;
      case blue:
        blue.value = setValueInRange.call(that, blue.value, 0, 255);
        color.val('b', blue.value, e.target);
        break;
      case alpha:
        alpha.value = setValueInRange.call(that, alpha.value, 0, 100);
        color.val('a', toFixedNumeric((alpha.value * 255) / 100, alphaPrecision), e.target);
        break;
      case hue:
        hue.value = setValueInRange.call(that, hue.value, 0, 360);
        color.val('h', hue.value, e.target);
        break;
      case saturation:
        saturation.value = setValueInRange.call(that, saturation.value, 0, 100);
        color.val('s', saturation.value, e.target);
        break;
      case value:
        value.value = setValueInRange.call(that, value.value, 0, 100);
        color.val('v', value.value, e.target);
        break;
      case hex:
        hex.value = hex.value.replace(/[^a-fA-F\d]/g, '').toLowerCase().substring(0, 6);
        bindedHex && bindedHex.val(hex.value);
        color.val('hex', hex.value !== '' ? hex.value : null, e.target);
        break;
      case bindedHex:
        bindedHex.value = bindedHex.value.replace(/[^a-fA-F\d]/g, '').toLowerCase().substring(0, 6);
        hex.val(bindedHex.value);
        color.val('hex', bindedHex.value !== '' ? bindedHex.value : null, e.target);
        break;
      case ahex:
        ahex.value = ahex.value.replace(/[^a-fA-F\d]/g, '').toLowerCase().substring(0, 2);
        color.val('a', !isNullish(ahex.value) ? Number.parseInt(ahex.value, 16) : null, e.target);
        break;
      }
      return undefined;
    }
    // input box blur - reset to original if value empty
    /**
    * @param {Event} e
    * @returns {void}
    */
    function blur (e) {
      if (!isNullish(color.value)) {
        switch (e.target) {
        case red:
          color.value = 'r';
          red.value = color.value;
          break;
        case green:
          color.value = 'g';
          green.value = color.value;
          break;
        case blue:
          color.value = 'b';
          blue.value = color.value;
          break;
        case alpha:
          color.value = 'a';
          alpha.value = toFixedNumeric((color.value * 100) / 255, alphaPrecision);
          break;
        case hue:
          color.value = 'h';
          hue.value = color.value;
          break;
        case saturation:
          color.value = 's';
          saturation.value = color.value;
          break;
        case value:
          color.value = 'v';
          value.value = color.value;
          break;
        case hex:
        case bindedHex:
          color.value = 'hex';
          hex.value = color.value;
          bindedHex.value = color.value;
          break;
        case ahex:
          color.value = 'ahex';
          ahex.value = color.value.substring(6);
          break;
        }
      }
    }
    /**
    * @param {Event} e
    * @returns {boolean}
    */
    function validateKey (e) {
      switch (e.keyCode) {
      case 9:
      case 16:
      case 29:
      case 37:
      case 39:
        return false;
      case 'c'.charCodeAt():
      case 'v'.charCodeAt():
        if (e.ctrlKey) return false;
      }
      return true;
    }

    /**
    * Constrain value within range.
    * @param {Float|string} value
    * @param {Float} min
    * @param {Float} max
    * @returns {Float|string} Returns a number or numeric string
    */
    function setValueInRange (value, min, max) {
      if (value === '' || isNaN(value)) return min;
      if (value > max) return max;
      if (value < min) return min;
      return value;
    }
    /**
    * @param {external:jQuery} ui
    * @param {Element} context
    * @returns {void}
    */
    function colorChanged (ui, context) {
      const all = ui.val('all');
      if (context !== red) red.value = (!isNullish(all) ? all.r : '');
      if (context !== green) green.value = (!isNullish(all) ? all.g : '');
      if (context !== blue) blue.value = (!isNullish(all) ? all.b : '');
      if (alpha && context !== alpha) alpha.value = (!isNullish(all) ? toFixedNumeric((all.a * 100) / 255, alphaPrecision) : '');
      if (context !== hue) hue.value = (!isNullish(all) ? all.h : '');
      if (context !== saturation) saturation.value = (!isNullish(all) ? all.s : '');
      if (context !== value) value.value = (!isNullish(all) ? all.v : '');
      if (context !== hex && ((bindedHex && context !== bindedHex) || !bindedHex)) hex.value = (!isNullish(all) ? all.hex : '');
      if (bindedHex && context !== bindedHex && context !== hex) bindedHex.value = (!isNullish(all) ? all.hex : '');
      if (ahex && context !== ahex) ahex.value = (!isNullish(all) ? all.ahex.substring(6) : '');
    }
    /**
    * Unbind all events and null objects.
    * @returns {void}
    */
    function destroy () {
      red.removeEventListener('keyup', keyUp);
      green.removeEventListener('keyup', keyUp);
      blue.removeEventListener('keyup', keyUp);
      hue.removeEventListener('keyup', keyUp);
      saturation.removeEventListener('keyup', keyUp);
      value.removeEventListener('keyup', keyUp);
      hex.removeEventListener('keyup', keyUp);

      red.removeEventListener('blur', blur);
      green.removeEventListener('blur', blur);
      blue.removeEventListener('blur', blur);
      hue.removeEventListener('blur', blur);
      saturation.removeEventListener('blur', blur);
      value.removeEventListener('blur', blur);
      hex.removeEventListener('blur', blur);

      red.removeEventListener('keydown', keyDown);
      green.removeEventListener('keydown', keyDown);
      blue.removeEventListener('keydown', keyDown);
      hue.removeEventListener('keydown', keyDown);
      saturation.removeEventListener('keydown', keyDown);
      value.removeEventListener('keydown', keyDown);

      if (alpha !== null) {
        alpha.removeEventListener('keyup', keyUp);
        alpha.removeEventListener('blur', blur);
        alpha.removeEventListener('keydown', keyDown);
      }
      if (ahex !== null) {
        ahex.removeEventListener('keyup', keyUp);
        ahex.removeEventListener('blur', blur);
      }
      if (bindedHex !== null) {
        bindedHex.removeEventListener('keyup', keyUp);
        bindedHex.removeEventListener('blur', blur);
      }
      color.unbind(colorChanged);
      red = null;
      green = null;
      blue = null;
      alpha = null;
      hue = null;
      saturation = null;
      value = null;
      hex = null;
      ahex = null;
    }
    let
      red = inputs[3],
      green = inputs[4],
      blue = inputs[5],
      alpha = inputs.length > 7 ? inputs[6] : null,
      hue = inputs[0],
      saturation = inputs[1],
      value = inputs[2],
      hex = inputs[(inputs.length > 7) ? 7 : 6],
      ahex = inputs.length > 7 ? inputs[8] : null;
    Object.assign(that, { destroy });
    red.addEventListener('keyup', keyUp);
    green.addEventListener('keyup', keyUp);
    blue.addEventListener('keyup', keyUp);
    hue.addEventListener('keyup', keyUp);
    saturation.addEventListener('keyup', keyUp);
    value.addEventListener('keyup', keyUp);
    hex.addEventListener('keyup', keyUp);

    red.addEventListener('blur', blur);
    green.addEventListener('blur', blur);
    blue.addEventListener('blur', blur);
    hue.addEventListener('blur', blur);
    saturation.addEventListener('blur', blur);
    value.addEventListener('blur', blur);
    hex.addEventListener('blur', blur);

    red.addEventListener('keydown', keyDown);
    green.addEventListener('keydown', keyDown);
    blue.addEventListener('keydown', keyDown);
    hue.addEventListener('keydown', keyDown);
    saturation.addEventListener('keydown', keyDown);
    value.addEventListener('keydown', keyDown);

    if (alpha !== null) {
      alpha.addEventListener('keyup', keyUp);
      alpha.addEventListener('blur', blur);
      alpha.addEventListener('keydown', keyDown);
    }
    if (ahex !== null) {
      ahex.addEventListener('keyup', keyUp);
      ahex.addEventListener('blur', blur);
    }
    if (bindedHex !== null) {
      bindedHex.addEventListener('keyup', keyUp);
      bindedHex.addEventListener('blur', blur);
    }
    color.bind(colorChanged);
  }
}
