//@include lib_Class.js

/**
 * Utility sort callback functions to sort arrays.
 *
 * @requires sol.commom.DateUtils
 */
sol.define("sol.common.ObjectSortUtils", {
  singleton: true,

  default: function (a, b) {
    if (a === b) {
      return 0;
    } else {
      return a < b ? -1 : 1;
    }
  },

  date: function (a, b) {
    return sol.common.DateUtils.isoToMoment(a)
      .diff(sol.common.DateUtils.isoToMoment(b));
  },

  number: function (a, b) {
    a = parseFloat(a);
    b = parseFloat(b);
    return sol.common.ObjectSortUtils.default(a, b);
  }

});

/**
 * @author MHe (ELO Digital Office GmbH)
 *
 * @requires sol.common.ObjectUtils
 * @requires sol.common.ObjectSortUtils
 */
sol.define("sol.common.mixins.ObjectSort", {
  mixin: true,

  /**
     * Sort an array by specify sortCriteria.
     *
     * Imagine you have the following array:
     *
     *     [ { data: { propA: "A", propB: "B"} }, {data: { propA: "A", propB: "B"} }]
     *
     * If you want to sort by propA you can use the following sortCriteria structure.
     *  { prop: "data.propA" }
     *
     * Now the array will be sort by the object prop `propA` with ObjectSortUtils.default sort function.
     *
     * The sortCriteria will be used ObjectUtils.getProp function to determine the object property,
     * so the object could be sort by deeper structures as well as flat structures
     *
     * If you want simple sort an array by a prop you can easily pass the object keypath
     * instead of a sortCritiera object
     *
     *    sortCriterias: ["data.propA"]
     *
     * A sortCriteria has furthermore options. To use another sorting strategy you can pass
     * another function from ObjectSortUtils. Currently supported functions are default, date, number
     *
     *  sortCriterias: [{ prop: "data.propDate", type: "date" }]
     *
     * @param {Array<Object>} arr The array to sort
     * @param {Array<Object>} sortCriterias determine the props to sort
     * @param {*} options object
     * @param {Boolean} [options.clone=true] If you want to sort the original object set this option to false. Default is true
     *
     *
     * @returns sorted array
     */
  sortArray: function (arr, sortCriterias, options) {
    var arrayToSort = arr, me = this,
        ObjectUtils = sol.common.ObjectUtils,
        ObjectSortUtils = sol.common.ObjectSortUtils,
        propSortAlgorithm = function (a, b) {
          var result, aValue, bValue, sortFunction, index = 0,
              currentCriteria = sortCriterias[index];

          do {
            aValue = ObjectUtils.getProp(a, currentCriteria.prop);
            bValue = ObjectUtils.getProp(b, currentCriteria.prop);

            // Call dynamically the passed sort function. If the type doesn't exist
            // default sort function will be used
            me.logger.info(["current type {0}, {1}", currentCriteria.type, ObjectSortUtils[currentCriteria.type]]);
            sortFunction = ObjectSortUtils[currentCriteria.type] || ObjectSortUtils.default;
            result = sortFunction.call(me, aValue, bValue);

            currentCriteria = sortCriterias[++index];
            // perform sort criteria as soon as sort criteria are available
            // so we can sort after multiple criteria
          } while (result === 0 && currentCriteria);

          return result;
        };

    options = options || {};
    options.clone = options.clone || true;

    if (!arr) {
      throw Error("`arr` is not defined");
    }

    if (options.clone) {
      // make a copy of the arr to sort - avoid potentially side effects
      arrayToSort = ObjectUtils.clone(arr);
    }

    me.logger.debug(["sort criteria {0}", JSON.stringify(sortCriterias)]);

    me.hasSortCriteria(sortCriterias)
        && arrayToSort.sort(propSortAlgorithm);

    return arrayToSort;
  },

  hasSortCriteria: function (sortCriterias) {
    return sortCriterias && sortCriterias.length > 0;
  },

  generateSort: function (sorts) {
    var sortStatements = [], statement,
        ObjectUtils = sol.common.ObjectUtils;

    if (sorts && !sol.common.ObjectUtils.isArray(sorts)) {
      throw "`options.sort` must be an array! current type: " + typeof sorts;
    }

    ObjectUtils.forEach(sorts, function (el) {
      if (ObjectUtils.isObject(el)) {
        if (!el.prop) {
          throw Error("`options.sort` entry need attribute `prop`. Example: { 'prop': 'name' }");
        }

        statement = el;
      } else if (ObjectUtils.isString(el)) {
        statement = { prop: el };
      } else {
        throw Error("`options.sort` entry could not parse " + JSON.stringify(el));
      }

      sortStatements.push(statement);
    });

    return sortStatements;
  }
});