
var Color = require('color');

/**
 * Basic RGBA adjusters.
 */

exports.red = rgbaAdjuster('red');
exports.blue = rgbaAdjuster('blue');
exports.green = rgbaAdjuster('green');
exports.alpha = exports.a = rgbaAdjuster('alpha');

/**
 * RGB adjuster.
 */

exports.rgb = function () {
  // TODO
};

/**
 * Basic HSLWB adjusters.
 */

exports.hue = exports.h = hslwbAdjuster('hue');
exports.saturation = exports.s = hslwbAdjuster('saturation');
exports.lightness = exports.l = hslwbAdjuster('lightness');
exports.whiteness = exports.w = hslwbAdjuster('whiteness');
exports.blackness = exports.b = hslwbAdjuster('blackness');

/**
 * Blend adjuster.
 *
 * @param {Color} color
 * @param {Object} args
 */

exports.blend = function (color, args) {
  var targetAlpha = color.alpha();

  // Reset the alpha value to one. This is required because color.mix mixes
  // the alpha value as well as rgb values. For blend() purposes, that's not
  // what we want.
  color.alpha(1);

  var other = new Color(args[0].value);
  var percentage = 1 - parseInt(args[1].value, 10) / 100;

  // Finally set the alpha value of the mixed color to the target value.
  color.mix(other, percentage).alpha(targetAlpha);
};

/**
 * Tint adjuster.
 *
 * @param {Color} color
 * @param {Object} args
 */

exports.tint = function (color, args) {
  args.unshift({ type: 'argument', value: 'white' });
  exports.blend(color, args);
};

/**
 * Share adjuster.
 *
 * @param {Color} color
 * @param {Object} args
 */

exports.shade = function (color, args) {
  args.unshift({ type: 'argument', value: 'black' });
  exports.blend(color, args);
};

/**
 * Contrast adjuster.
 *
 * @param {Color} color
 * @param {Object} args
 */
exports.contrast = function (color, args) {
  if (args.length == 0) args.push({ type: 'argument', value: '100%' });
  var percentage = 1 - parseInt(args[0].value, 10) / 100;
  var max = color.luminosity() < .5 ? new Color({ h:color.hue(), w:100, b:0 }) : new Color({ h:color.hue(), w:0, b:100 });
  var min = max;
  var minRatio = 4.5;
  if (color.contrast(max) > minRatio) {
    var min = binarySearchBWContrast(minRatio, color, max);
    var targetMinAlpha = min.alpha();
    // Set the alpha to 1 to avoid mix()-ing the alpha value.
    min.alpha(1);
    // mixes the colors then sets the alpha back to the target alpha.
    min.mix(max, percentage).alpha(targetMinAlpha);
  }
  color.hwb(min.hwb());
};

/**
 * Generate a value or percentage of modifier.
 *
 * @param {String} prop
 * @return {Function}
 */

function rgbaAdjuster (prop) {
  return function (color, args) {
    var mod;
    if (args[0].type == 'modifier') mod = args.shift().value;

    var val = args[0].value;
    if (val.indexOf('%') != -1) {
      val = parseInt(val, 10) / 100;
      if (!mod) {
        val = val * (prop == 'alpha' ? 1 : 255);
      } else if (mod != '*') {
        val = color[prop]() * val;
      }
    } else {
      val = Number(val);
    }

    color[prop](modify(color[prop](), val, mod));
  };
}

/**
 * Generate a basic HSLWB adjuster.
 *
 * @param {String} prop
 * @return {Function}
 */

function hslwbAdjuster (prop) {
  return function (color, args) {
    var mod;
    if (args[0].type == 'modifier') mod = args.shift().value;
    var val = parseFloat(args[0].value, 10);
    color[prop](modify(color[prop](), val, mod));
  };
}

/**
 * Return the percentage of a `number` for a given percentage `string`.
 *
 * @param {Number} number
 * @param {String} string
 * @return {Number}
 */

function percentageOf (number, string) {
  var percent = parseInt(string, 10) / 100;
  return number * percent;
}

/**
 * Modify a `val` by an `amount` with an optional `modifier`.
 *
 * @param {Number} val
 * @param {Number} amount
 * @param {String} modifier (optional)
 */

function modify (val, amount, modifier) {
  switch (modifier) {
    case '+': return val + amount;
    case '-': return val - amount;
    case '*': return val * amount;
    default: return amount;
  }
}

/**
 * Return the color closest to `color` between `color` and `max` that has a contrast ratio higher than `minRatio`
 *  assumes `color` and `max` have identical hue
 *
 * @param {Number} minRatio
 * @param {Color} color
 * @param {Color} max
 **/

function binarySearchBWContrast (minRatio, color, max) {
  var hue = color.hue();
  var min = color.clone();
  var minW = color.whiteness();
  var minB = color.blackness();
  var maxW = max.whiteness();
  var maxB = max.blackness();
  while (Math.abs(minW - maxW) > 1 || Math.abs(minB - maxB) > 1) {
    var midW = Math.round((maxW + minW) / 2);
    var midB = Math.round((maxB + minB) / 2);
    min.whiteness(midW);
    min.blackness(midB);
    if (min.contrast(color) > minRatio) {
      maxW = midW;
      maxB = midB;
    } else {
      minW = midW;
      minB = midB;
    }
  }
  return min
}
