/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global Query: true, query_class_dict: true, inherits: true,
         exports, QueryFactory, RSVP, sequence */

/**
 * The ComplexQuery inherits from Query, and compares one or several metadata
 * values.
 *
 * @class ComplexQuery
 * @extends Query
 * @param  {Object} [spec={}] The specifications
 * @param  {String} [spec.operator="AND"] The compare method to use
 * @param  {String} spec.key The metadata key
 * @param  {String} spec.value The value of the metadata to compare
 */
function ComplexQuery(spec, key_schema) {
  Query.call(this);

  /**
   * Logical operator to use to compare object values
   *
   * @attribute operator
   * @type String
   * @default "AND"
   * @optional
   */
  this.operator = spec.operator || "AND";

  /**
   * The sub Query list which are used to query an item.
   *
   * @attribute query_list
   * @type Array
   * @default []
   * @optional
   */
  this.query_list = spec.query_list || [];
  /*jslint unparam: true*/
  this.query_list = this.query_list.map(
    // decorate the map to avoid sending the index as key_schema argument
    function (o, i) { return QueryFactory.create(o, key_schema); }
  );
  /*jslint unparam: false*/

}
inherits(ComplexQuery, Query);

/**
 * #crossLink "Query/match:method"
 */
ComplexQuery.prototype.match = function (item) {
  var operator = this.operator;
  if (!(/^(?:AND|OR|NOT)$/i.test(operator))) {
    operator = "AND";
  }
  return this[operator.toUpperCase()](item);
};

/**
 * #crossLink "Query/toString:method"
 */
ComplexQuery.prototype.toString = function () {
  var str_list = ["("], this_operator = this.operator;
  this.query_list.forEach(function (query) {
    str_list.push(query.toString());
    str_list.push(this_operator);
  });
  str_list[str_list.length - 1] = ")"; // replace last operator
  return str_list.join(" ");
};

/**
 * #crossLink "Query/serialized:method"
 */
ComplexQuery.prototype.serialized = function () {
  var s = {
    "type": "complex",
    "operator": this.operator,
    "query_list": []
  };
  this.query_list.forEach(function (query) {
    s.query_list.push(query.serialized());
  });
  return s;
};
ComplexQuery.prototype.toJSON = ComplexQuery.prototype.serialized;

/**
 * Comparison operator, test if all sub queries match the
 * item value
 *
 * @method AND
 * @param  {Object} item The item to match
 * @return {Boolean} true if all match, false otherwise
 */
ComplexQuery.prototype.AND = function (item) {
  var j, promises = [];
  for (j = 0; j < this.query_list.length; j += 1) {
    promises.push(this.query_list[j].match(item));
  }

  function cancel() {
    var i;
    for (i = 0; i < promises.length; i += 1) {
      if (typeof promises.cancel === 'function') {
        promises.cancel();
      }
    }
  }

  return new RSVP.Promise(function (resolve, reject) {
    var i, count = 0;
    function resolver(value) {
      if (!value) {
        resolve(false);
      }
      count += 1;
      if (count === promises.length) {
        resolve(true);
      }
    }

    function rejecter(err) {
      reject(err);
      cancel();
    }

    for (i = 0; i < promises.length; i += 1) {
      promises[i].then(resolver, rejecter);
    }
  }, cancel);
};

/**
 * Comparison operator, test if one of the sub queries matches the
 * item value
 *
 * @method OR
 * @param  {Object} item The item to match
 * @return {Boolean} true if one match, false otherwise
 */
ComplexQuery.prototype.OR =  function (item) {
  var j, promises = [];
  for (j = 0; j < this.query_list.length; j += 1) {
    promises.push(this.query_list[j].match(item));
  }

  function cancel() {
    var i;
    for (i = 0; i < promises.length; i += 1) {
      if (typeof promises.cancel === 'function') {
        promises.cancel();
      }
    }
  }

  return new RSVP.Promise(function (resolve, reject) {
    var i, count = 0;
    function resolver(value) {
      if (value) {
        resolve(true);
      }
      count += 1;
      if (count === promises.length) {
        resolve(false);
      }
    }

    function rejecter(err) {
      reject(err);
      cancel();
    }

    for (i = 0; i < promises.length; i += 1) {
      promises[i].then(resolver, rejecter);
    }
  }, cancel);
};

/**
 * Comparison operator, test if the sub query does not match the
 * item value
 *
 * @method NOT
 * @param  {Object} item The item to match
 * @return {Boolean} true if one match, false otherwise
 */
ComplexQuery.prototype.NOT = function (item) {
  return sequence([function () {
    return this.query_list[0].match(item);
  }, function (answer) {
    return !answer;
  }]);
};

query_class_dict.complex = ComplexQuery;

exports.ComplexQuery = ComplexQuery;