
/*!
 * Stylus - Unit
 * Copyright(c) 2010 LearnBoost <dev@learnboost.com>
 * MIT Licensed
 */

/**
 * Module dependencies.
 */

var Node = require('./node')
  , nodes = require('./');

/**
 * Initialize a new `Unit` with the given `val` and unit `type`
 * such as "px", "pt", "in", etc.
 *
 * @param {String} val
 * @param {String} type
 * @api public
 */

var Unit = module.exports = function Unit(val, type){
  Node.call(this);
  this.val = val;
  this.type = type;
};

/**
 * Inherit from `Node.prototype`.
 */

Unit.prototype.__proto__ = Node.prototype;

/**
 * Return Boolean based on the unit value.
 *
 * @return {Boolean}
 * @api public
 */

Unit.prototype.toBoolean = function(){
  return nodes.Boolean(this.type
      ? true
      : this.val);
};

/**
 * Return unit string.
 *
 * @return {String}
 * @api public
 */

Unit.prototype.toString = function(){
  var n = this.val;
  if ('px' == this.type) n = n.toFixed(0);
  return n + (this.type || '');
};

/**
 * Return a clone of this node.
 * 
 * @return {Node}
 * @api public
 */

Unit.prototype.clone = function(){
  var clone = new Unit(this.val, this.type);
  clone.lineno = this.lineno;
  clone.filename = this.filename;
  return clone;
};

/**
 * Operate on `right` with the given `op`.
 *
 * @param {String} op
 * @param {Node} right
 * @return {Node}
 * @api public
 */

Unit.prototype.operate = function(op, right){
  var type = this.type || right.first.type;

  // swap color
  if ('rgba' == right.nodeName || 'hsla' == right.nodeName) {
    return right.operate(op, this);
  }

  // operate
  if (this.shouldCoerce(op)) {
    // percentages
    if (('-' == op || '+' == op) && '%' == right.type) {
      right.val = this.val * (right.val / 100);
    } else {
      right = this.coerce(right.first);
    }

    switch (op) {
      case '-':
        return new Unit(this.val - right.val, type);
      case '+':                               
        return new Unit(this.val + right.val, type);
      case '/':                               
        return new Unit(this.val / right.val, type);
      case '*':                               
        return new Unit(this.val * right.val, type);
      case '%':
        return new Unit(this.val % right.val, type);
      case '**':
        return new Unit(Math.pow(this.val, right.val), type);
      case '..':
      case '...':
        var start = this.val
          , end = right.val
          , expr = new nodes.Expression
          , inclusive = '..' == op;
        do {
          expr.push(new nodes.Unit(start));
        } while (inclusive ? ++start <= end : ++start < end);
        return expr;
    }
  }

  return Node.prototype.operate.call(this, op, right);
};

/**
 * Coerce `other` unit to the same type as `this` unit.
 *
 * Supports:
 *
 *    mm -> cm | in
 *    cm -> mm | in
 *    in -> mm | cm
 *    
 *    ms -> s
 *    s  -> ms
 *    
 *    Hz  -> kHz
 *    kHz -> Hz
 *
 * @param {Unit} other
 * @return {Unit}
 * @api public
 */

Unit.prototype.coerce = function(other){
  if ('unit' == other.nodeName) {
    var a = this
      , b = other;
    switch (a.type) {
      case 'mm':
        switch (b.type) {
          case 'cm':
            return new nodes.Unit(b.val * 2.54, 'mm');
          case 'in':
            return new nodes.Unit(b.val * 25.4, 'mm');
        }
      case 'cm':
        switch (b.type) {
          case 'mm':
            return new nodes.Unit(b.val / 10, 'cm');
          case 'in':
            return new nodes.Unit(b.val * 2.54, 'cm');
        }
      case 'in':
        switch (b.type) {
          case 'mm':
            return new nodes.Unit(b.val / 25.4, 'in');
          case 'cm':
            return new nodes.Unit(b.val / 2.54, 'in');
        }
      case 'ms':
        switch (b.type) {
          case 's':
            return new nodes.Unit(b.val * 1000, 'ms');
        }
      case 's':
        switch (b.type) {
          case 'ms':
            return new nodes.Unit(b.val / 1000, 's');
        }
      case 'Hz':
        switch (b.type) {
          case 'kHz':
            return new nodes.Unit(b.val * 1000, 'Hz');
        }
      case 'kHz':
        switch (b.type) {
          case 'Hz':
            return new nodes.Unit(b.val / 1000, 'kHz');
        }
      default:
        return new nodes.Unit(b.val, a.type);
    }
  } else if ('string' == other.nodeName) {
    var val = parseInt(other.val, 10);
    if (isNaN(val)) Node.prototype.coerce.call(this, other);
    return new nodes.Unit(val);
  } else {
    return Node.prototype.coerce.call(this, other);
  }
};
