/**
 * The query to use to filter a list of objects.
 * This is an abstract class.
 *
 * @class Query
 * @constructor
 */
var Query = newClass(function() {
  /**
   * Creates a new item list with matching item only
   *
   * @method exec
   * @param  {Array} item_list The list of object
   * @param  {Object} [option={}] Some operation option
   * @param  {String} [option.wildcard_character="%"] The wildcard character
   * @param  {Array} [option.select_list=undefined] A object keys to retrieve
   * @param  {Array} [option.sort_on=[]] Couples of object keys
   *                                     and "ascending" or "descending"
   * @param  {Array} [option.limit=undefined] Couple of integer, first is an
   *                                          index and second is the length.
   * @return {Array} The new item list
   */
  this.exec = function (item_list, option) {};

  /**
   * Test if an item matches this query
   * @method match
   * @param  {Object} item The object to test
   * @return {Boolean} true if match, false otherwise
   */
  this.match = function (item, wildcard_character) {};


  /**
   * Convert this query to a parsable string.
   * @method toString
   * @return {String} The string version of this query
   */
  this.toString = function () {};

  /**
   * Convert this query to an jsonable object in order to be remake thanks to
   * QueryFactory class.
   *
   * @method serialized
   * @return {Object} The jsonable object
   */
  this.serialized = function () {};
}, {"static_methods": {
  // XXX
  "filterListSelect": function (select_option, list) {
    list.forEach(function (item, index) {
      var new_item = {};
      select_option.forEach(function (key) {
        new_item[key] = item[key];
      });
      list[index] = new_item;
    });
  },
  // XXX
  "sortOn": function (sort_on_option, list) {
    var sort_index;
    for (sort_index = sort_on_option.length - 1; sort_index >= 0;
         sort_index += 1) {
      list.sort(sortFunction(
        sort_on_option[sort_index][0],
        sort_on_option[sort_index][1]
      ));
    }
  },
  "parseStringToQuery": parseStringToQuery
}});

_export("Query", Query);


function query(query, object_list) {
  var wildcard_character = typeof query.wildcard_character === "string" ?
    query.wildcard_character : "%",
  // A list of methods according to operators
    operator_actions = {
      "=": function (value1, value2) {
        value1 = value1.toString();
        return value1.match(convertToRegexp(
          value2, wildcard_character
        )) || false && true;
      },
      '!=':  function (value1, value2) {
        value1 = value1.toString();
        return !(value1.match(convertToRegexp(
          value2, wildcard_character
        )));
      },
      '<':   function (value1, value2) { return value1 < value2; },
      '<=':  function (value1, value2) { return value1 <= value2; },
      '>':   function (value1, value2) { return value1 > value2; },
      '>=':  function (value1, value2) { return value1 >= value2; },
      'AND': function (item, query_list) {
        var i;
        for (i=0; i<query_list.length; ++i) {
          if (! itemMatchesQuery (item, query_list[i])) {
            return false;
          }
        }
        return true;
      },
      'OR':  function (item, query_list) {
        var i;
        for (i=0; i<query_list.length; ++i) {
          if (itemMatchesQuery (item, query_list[i])) {
            return true;
          }
        }
        return false;
      },
      'NOT': function (item, query_list) {
        return !itemMatchesQuery(item, query_list[0]);
      }
    },
    convertToRegexp = function (string) {
      return subString('^' + string.replace(
        new RegExp(
          '([\\{\\}\\(\\)\\^\\$\\&\\.\\*\\?\\\/\\+\\|\\[\\]\\-\\\\])'.
            replace(wildcard_character ?
                    "\\" + wildcard_character : undefined, ""),
          "g"
        ),
        "\\$1"
      ) + '$',(wildcard_character||undefined), '.*');
    },
    subString = function (string, substring, newsubstring) {
      var res = '', i = 0;
      if (substring === undefined) {
        return string;
      }
      while (true) {
        var tmp = string.indexOf(substring, i);
        if (tmp === -1) {
          break;
        }
        for (; i < tmp; i += 1) {
          res += string[i];
        }
        res += newsubstring;
        i += substring.length;
      }
      for (; i < string.length; i += 1) {
        res += string[i];
      }
      return res;
    },
  itemMatchesQuery = function (item, query_object) {
    var i;
    if (query_object.type === 'complex') {
      return operator_actions[query_object.operator](
        item, query_object.query_list
      );
    } else {
      if (query_object.id) {
        if (typeof item[query_object.id] !== 'undefined') {
          return operator_actions[query_object.operator](
            item[query_object.id], query_object.value
          );
        } else {
          return false;
        }
      } else {
        return true;
      }
    }
  },
  select = function (list, select_list) {
    var i;
    if (select_list.length === 0) {
      return;
    }
    for (i=0; i<list.length; ++i) {
      var list_value = {}, k;
      for (k=0; k<select_list.length; ++k) {
        list_value[select_list[k]] =
          list[i][select_list[k]];
      }
      list[i] = list_value;
    }
  },
  sortFunction = function (key, asc) {
    if (asc === 'descending') {
      return function (a,b) {
        return a[key] < b[key] ? 1 : a[key] > b[key] ? -1 : 0;
      };
    }
    return function (a,b) {
      return a[key] > b[key] ? 1 : a[key] < b[key] ? -1 : 0;
    };
  },
  mergeList = function (list, list_to_merge, index) {
    var i,j;
    for (i = index,j = 0; i < list_to_merge.length + index; ++i, ++j) {
      list[i] = list_to_merge[j];
    }
  },
  sort = function (list, sort_list) {
    var i, tmp, key, asc, sortAndMerge = function() {
      sort(tmp,sort_list.slice(1));
      mergeList(list,tmp,i-tmp.length);
      tmp = [list[i]];
    };
    if (list.length < 2) {
      return;
    }
    if (sort_list.length === 0) {
      return;
    }
    key = sort_list[0][0];
    asc = sort_list[0][1];
    list.sort (sortFunction (key,asc));
    tmp = [list[0]];
    for (i = 1; i < list.length; ++i) {
      if (tmp[0][key] === list[i][key]) {
        tmp.push(list[i]);
      } else {
        sortAndMerge();
      }
    }
    sortAndMerge();
  },
  limit = function (list, limit_list) {
    var i;
    if (typeof limit_list[0] !== 'undefined') {
      if (typeof limit_list[1] !== 'undefined') {
        if (list.length > limit_list[1] + limit_list[0]) {
          list.length = limit_list[1] + limit_list[0];
        }
        list.splice(0,limit_list[0]);
      } else {
        list.length = limit_list[0];
      }
    }
  },
  ////////////////////////////////////////////////////////////
  result_list = [], result_list_tmp = [], j;
  object_list = object_list || [];
  if (query.query === undefined) {
    result_list = object_list;
  } else {
    for (j=0; j<object_list.length; ++j) {
      if ( itemMatchesQuery (
        object_list[j], scope.ComplexQueries.parse (query.query)
      )) {
        result_list.push(object_list[j]);
      }
    }
  }
  if (query.filter) {
    select(result_list,query.filter.select_list || []);
    sort(result_list,query.filter.sort_on || []);
    limit(result_list,query.filter.limit || []);
  }
  return result_list;
}