// Utility functions for ordering and stacking of items
var EPSILON = 0.001; // used when checking collisions, to prevent round-off errors

/**
 * Order items by their start data
 * @param {Item[]} items
 */
exports.orderByStart = function(items) {
  items.sort(function (a, b) {
    return a.data.start - b.data.start;
  });
};

/**
 * Order items by their end date. If they have no end date, their start date
 * is used.
 * @param {Item[]} items
 */
exports.orderByEnd = function(items) {
  items.sort(function (a, b) {
    var aTime = ('end' in a.data) ? a.data.end : a.data.start,
        bTime = ('end' in b.data) ? b.data.end : b.data.start;

    return aTime - bTime;
  });
};

/**
 * Adjust vertical positions of the items such that they don't overlap each
 * other.
 * @param {Item[]} items
 *            All visible items
 * @param {{item: {horizontal: number, vertical: number}, axis: number}} margin
 *            Margins between items and between items and the axis.
 * @param {boolean} [force=false]
 *            If true, all items will be repositioned. If false (default), only
 *            items having a top===null will be re-stacked
 */
exports.stack = function(items, margin, force) {
  if (force) {
    // reset top position of all items
    for (var i = 0; i < items.length; i++) {
      items[i].top = null;
    }
  }

  // calculate new, non-overlapping positions
  for (var i = 0; i < items.length; i++) {
    var item = items[i];
    if (item.stack && item.top === null) {
      // initialize top position
      item.top = margin.axis;

      do {
        // TODO: optimize checking for overlap. when there is a gap without items,
        //       you only need to check for items from the next item on, not from zero
        var collidingItem = null;
        for (var j = 0, jj = items.length; j < jj; j++) {
          var other = items[j];
          if (other.top !== null && other !== item && other.stack && exports.collision(item, other, margin.item, other.options.rtl)) {
            collidingItem = other;
            break;
          }
        }

        if (collidingItem != null) {
          // There is a collision. Reposition the items above the colliding element
          item.top = collidingItem.top + collidingItem.height + margin.item.vertical;
        }
      } while (collidingItem);
    }
  }
};


/**
 * Adjust vertical positions of the items without stacking them
 * @param {Item[]} items
 *            All visible items
 * @param {{item: {horizontal: number, vertical: number}, axis: number}} margin
 *            Margins between items and between items and the axis.
* @param {subgroups[]} subgroups
 *            All subgroups
 */
 exports.nostack = function(items, margin, subgroups, stackSubgroups) {
  for (var i = 0; i < items.length; i++) {
    if (items[i].data.subgroup == undefined) {
      items[i].top = margin.item.vertical;
    } else if (items[i].data.subgroup !== undefined && stackSubgroups) {
      var newTop = 0;
      for (var subgroup in subgroups) {
        if (subgroups.hasOwnProperty(subgroup)) {
          if (subgroups[subgroup].visible == true && subgroups[subgroup].index < subgroups[items[i].data.subgroup].index) {
            newTop += subgroups[subgroup].height;
            subgroups[items[i].data.subgroup].top = newTop;
          }
        }
      }
      items[i].top = newTop + 0.5 * margin.item.vertical;
    }
  }
  if (!stackSubgroups) {
    exports.stackSubgroups(items, margin, subgroups)
  }
};

/**
 * Adjust vertical positions of the subgroups such that they don't overlap each
 * other.
 * @param {subgroups[]} subgroups
 *            All subgroups
 * @param {{item: {horizontal: number, vertical: number}, axis: number}} margin
 *            Margins between items and between items and the axis.
 */
exports.stackSubgroups = function(items, margin, subgroups) {
  for (var subgroup in subgroups) {
    if (subgroups.hasOwnProperty(subgroup)) {


      subgroups[subgroup].top = 0;
      do {
        // TODO: optimize checking for overlap. when there is a gap without items,
        //       you only need to check for items from the next item on, not from zero
        var collidingItem = null;
        for (var otherSubgroup in subgroups) {
          if (subgroups[otherSubgroup].top !== null && otherSubgroup !== subgroup && subgroups[subgroup].index > subgroups[otherSubgroup].index && exports.collisionByTimes(subgroups[subgroup], subgroups[otherSubgroup])) {
            collidingItem = subgroups[otherSubgroup];
            break;
          }
        }

        if (collidingItem != null) {
          // There is a collision. Reposition the subgroups above the colliding element
          subgroups[subgroup].top = collidingItem.top + collidingItem.height;
        }
      } while (collidingItem);
    }
  }
  for (var i = 0; i < items.length; i++) {
    if (items[i].data.subgroup !== undefined) {
      items[i].top = subgroups[items[i].data.subgroup].top + 0.5 * margin.item.vertical;
    }
  }
}

/**
 * Test if the two provided items collide
 * The items must have parameters left, width, top, and height.
 * @param {Item} a          The first item
 * @param {Item} b          The second item
 * @param {{horizontal: number, vertical: number}} margin
 *                          An object containing a horizontal and vertical
 *                          minimum required margin.
 * @param {boolean} rtl
 * @return {boolean}        true if a and b collide, else false
 */
exports.collision = function(a, b, margin, rtl) {
  if (rtl) {
    return  ((a.right - margin.horizontal + EPSILON)  < (b.right + b.width) &&
    (a.right + a.width + margin.horizontal - EPSILON) > b.right &&
    (a.top - margin.vertical + EPSILON)              < (b.top + b.height) &&
    (a.top + a.height + margin.vertical - EPSILON)   > b.top);
  } else {
    return ((a.left - margin.horizontal + EPSILON)   < (b.left + b.width) &&
    (a.left + a.width + margin.horizontal - EPSILON) > b.left &&
    (a.top - margin.vertical + EPSILON)              < (b.top + b.height) &&
    (a.top + a.height + margin.vertical - EPSILON)   > b.top);
  }
};

/**
 * Test if the two provided objects collide
 * The objects must have parameters start, end, top, and height.
 * @param {Object} a          The first Object
 * @param {Object} b          The second Object
 * @return {boolean}        true if a and b collide, else false
 */
exports.collisionByTimes = function(a, b) {
  return (
    (a.start <= b.start && a.end >= b.start && a.top < (b.top + b.height) && (a.top + a.height) > b.top ) ||
    (b.start <= a.start && b.end >= a.start && b.top < (a.top + a.height) && (b.top + b.height) > a.top )
  )
}