
/*!
 * Stylus - Object
 * Copyright (c) Automattic <developer.wordpress.com>
 * MIT Licensed
 */

/**
 * Module dependencies.
 */

var Node = require('./node')
  , nodes = require('./')
  , nativeObj = {}.constructor;

module.exports = class Object extends Node {
  /**
   * Initialize a new `Object`.
   *
   * @api public
   */

  constructor() {
    super();
    this.vals = {};
    this.keys = {};
  }

  /**
   * Set `key` to `val`.
   *
   * @param {String} key
   * @param {Node} val
   * @return {Object} for chaining
   * @api public
   */

  setValue(key, val) {
    this.vals[key] = val;
    return this;
  };

  /**
   * Alias for `setValue` for compatible API
   */

  get set() {
    return this.setValue;
  }

  /**
   * Set `key` to `val`.
   *
   * @param {String} key
   * @param {Node} val
   * @return {Object} for chaining
   * @api public
   */

  setKey(key, val) {
    this.keys[key] = val;
    return this;
  };

  /**
   * Return length.
   *
   * @return {Number}
   * @api public
   */

  get length() {
    return nativeObj.keys(this.vals).length;
  };

  /**
   * Get `key`.
   *
   * @param {String} key
   * @return {Node}
   * @api public
   */

  get(key) {
    return this.vals[key] || nodes.null;
  };

  /**
   * Has `key`?
   *
   * @param {String} key
   * @return {Boolean}
   * @api public
   */

  has(key) {
    return key in this.vals;
  };

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

  operate(op, right) {
    switch (op) {
      case '.':
      case '[]':
        return this.get(right.hash);
      case '==':
        var vals = this.vals
          , a
          , b;
        if ('object' != right.nodeName || this.length != right.length)
          return nodes.false;
        for (var key in vals) {
          a = vals[key];
          b = right.vals[key];
          if (a.operate(op, b).isFalse)
            return nodes.false;
        }
        return nodes.true;
      case '!=':
        return this.operate('==', right).negate();
      default:
        return super.operate(op, right);
    }
  };

  /**
   * Return Boolean based on the length of this object.
   *
   * @return {Boolean}
   * @api public
   */

  toBoolean() {
    return new nodes.Boolean(this.length);
  };

  /**
   * Convert object to string with properties.
   *
   * @return {String}
   * @api private
   */

  toBlock() {
    var str = '{'
      , key
      , val;

    for (key in this.vals) {
      val = this.get(key);
      if ('object' == val.first.nodeName) {
        str += key + ' ' + val.first.toBlock();
      } else {
        switch (key) {
          case '@charset':
            str += key + ' ' + val.first.toString() + ';';
            break;
          default:
            str += key + ':' + toString(val) + ';';
        }
      }
    }

    str += '}';

    return str;

    function toString(node) {
      if (node.nodes) {
        return node.nodes.map(toString).join(node.isList ? ',' : ' ');
      } else if ('literal' == node.nodeName && ',' == node.val) {
        return '\\,';
      }
      return node.toString();
    }
  };

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

  clone(parent) {
    var clone = new Object;
    clone.lineno = this.lineno;
    clone.column = this.column;
    clone.filename = this.filename;

    var key;
    for (key in this.vals) {
      clone.vals[key] = this.vals[key].clone(parent, clone);
    }

    for (key in this.keys) {
      clone.keys[key] = this.keys[key].clone(parent, clone);
    }

    return clone;
  };

  /**
   * Return a JSON representation of this node.
   *
   * @return {Object}
   * @api public
   */

  toJSON() {
    return {
      __type: 'Object',
      vals: this.vals,
      keys: this.keys,
      lineno: this.lineno,
      column: this.column,
      filename: this.filename
    };
  };

  /**
   * Return "{ <prop>: <val> }"
   *
   * @return {String}
   * @api public
   */

  toString() {
    var obj = {};
    for (var prop in this.vals) {
      obj[prop] = this.vals[prop].toString();
    }
    return JSON.stringify(obj);
  };

};
