/**
 * @constructor  DataStep
 * The class DataStep is an iterator for data for the lineGraph. You provide a start data point and an
 * end data point. The class itself determines the best scale (step size) based on the
 * provided start Date, end Date, and minimumStep.
 *
 * If minimumStep is provided, the step size is chosen as close as possible
 * to the minimumStep but larger than minimumStep. If minimumStep is not
 * provided, the scale is set to 1 DAY.
 * The minimumStep should correspond with the onscreen size of about 6 characters
 *
 * Alternatively, you can set a scale by hand.
 * After creation, you can initialize the class by executing first(). Then you
 * can iterate from the start date to the end date via next(). You can check if
 * the end date is reached with the function hasNext(). After each step, you can
 * retrieve the current date via getCurrent().
 * The DataStep has scales ranging from milliseconds, seconds, minutes, hours,
 * days, to years.
 *
 * Version: 1.2
 *
 * @param {Date} [start]         The start date, for example new Date(2010, 9, 21)
 *                               or new Date(2010, 9, 21, 23, 45, 00)
 * @param {Date} [end]           The end date
 * @param {Number} [minimumStep] Optional. Minimum step size in milliseconds
 */
function DataStep(start, end, minimumStep, containerHeight, customRange, alignZeros) {
  // variables
  this.current = 0;

  this.autoScale = true;
  this.stepIndex = 0;
  this.step = 1;
  this.scale = 1;

  this.marginStart;
  this.marginEnd;
  this.deadSpace = 0;

  this.majorSteps = [1,     2,    5,  10];
  this.minorSteps = [0.25,  0.5,  1,  2];

  this.alignZeros = alignZeros;

  this.setRange(start, end, minimumStep, containerHeight, customRange);
}



/**
 * Set a new range
 * If minimumStep is provided, the step size is chosen as close as possible
 * to the minimumStep but larger than minimumStep. If minimumStep is not
 * provided, the scale is set to 1 DAY.
 * The minimumStep should correspond with the onscreen size of about 6 characters
 * @param {Number} [start]      The start date and time.
 * @param {Number} [end]        The end date and time.
 * @param {Number} [minimumStep] Optional. Minimum step size in milliseconds
 */
DataStep.prototype.setRange = function(start, end, minimumStep, containerHeight, customRange) {
  this._start = customRange.min === undefined ? start : customRange.min;
  this._end = customRange.max === undefined ? end : customRange.max;

  if (this._start == this._end) {
    this._start -= 0.75;
    this._end += 1;
  }

  if (this.autoScale == true) {
    this.setMinimumStep(minimumStep, containerHeight);
  }

  this.setFirst(customRange);
};

/**
 * Automatically determine the scale that bests fits the provided minimum step
 * @param {Number} [minimumStep]  The minimum step size in milliseconds
 */
DataStep.prototype.setMinimumStep = function(minimumStep, containerHeight) {
  // round to floor
  var size = this._end - this._start;
  var safeSize = size * 1.2;
  var minimumStepValue = minimumStep * (safeSize / containerHeight);
  var orderOfMagnitude = Math.round(Math.log(safeSize)/Math.LN10);

  var minorStepIdx = -1;
  var magnitudefactor = Math.pow(10,orderOfMagnitude);

  var start = 0;
  if (orderOfMagnitude < 0) {
    start = orderOfMagnitude;
  }

  var solutionFound = false;
  for (var i = start; Math.abs(i) <= Math.abs(orderOfMagnitude); i++) {
    magnitudefactor = Math.pow(10,i);
    for (var j = 0; j < this.minorSteps.length; j++) {
      var stepSize = magnitudefactor * this.minorSteps[j];
      if (stepSize >= minimumStepValue) {
        solutionFound = true;
        minorStepIdx = j;
        break;
      }
    }
    if (solutionFound == true) {
      break;
    }
  }
  this.stepIndex = minorStepIdx;
  this.scale = magnitudefactor;
  this.step = magnitudefactor * this.minorSteps[minorStepIdx];
};



/**
 * Round the current date to the first minor date value
 * This must be executed once when the current date is set to start Date
 */
DataStep.prototype.setFirst = function(customRange) {
  if (customRange === undefined) {
    customRange = {};
  }

  var niceStart = customRange.min === undefined ? this._start - (this.scale * 2 * this.minorSteps[this.stepIndex]) : customRange.min;
  var niceEnd = customRange.max === undefined ? this._end + (this.scale * this.minorSteps[this.stepIndex]) : customRange.max;

  this.marginEnd = customRange.max === undefined ? this.roundToMinor(niceEnd) : customRange.max;
  this.marginStart = customRange.min === undefined ? this.roundToMinor(niceStart) : customRange.min;

  // if we need to align the zero's we need to make sure that there is a zero to use.
  if (this.alignZeros == true && (this.marginEnd - this.marginStart) % this.step != 0) {
    this.marginEnd += this.marginEnd % this.step;
  }

  this.deadSpace = this.roundToMinor(niceEnd) - niceEnd + this.roundToMinor(niceStart) - niceStart;
  this.marginRange = this.marginEnd - this.marginStart;


  this.current = this.marginEnd;
};

DataStep.prototype.roundToMinor = function(value) {
  var rounded = value - (value % (this.scale * this.minorSteps[this.stepIndex]));
  if (value % (this.scale * this.minorSteps[this.stepIndex]) > 0.5 * (this.scale * this.minorSteps[this.stepIndex])) {
    return rounded + (this.scale * this.minorSteps[this.stepIndex]);
  }
  else {
    return rounded;
  }
}


/**
 * Check if the there is a next step
 * @return {boolean}  true if the current date has not passed the end date
 */
DataStep.prototype.hasNext = function () {
  return (this.current >= this.marginStart);
};

/**
 * Do the next step
 */
DataStep.prototype.next = function() {
  var prev = this.current;
  this.current -= this.step;

  // safety mechanism: if current time is still unchanged, move to the end
  if (this.current == prev) {
    this.current = this._end;
  }
};

/**
 * Do the next step
 */
DataStep.prototype.previous = function() {
  this.current += this.step;
  this.marginEnd += this.step;
  this.marginRange = this.marginEnd - this.marginStart;
};



/**
 * Get the current datetime
 * @return {String}  current The current date
 */
DataStep.prototype.getCurrent = function(decimals) {
  var toPrecision = '' + Number(this.current).toPrecision(5);
  // If decimals is specified, then limit or extend the string as required
  if(decimals !== undefined && !isNaN(Number(decimals))) {
    // If string includes exponent, then we need to add it to the end
    var exp = "";
    var index = toPrecision.indexOf("e");
    if(index != -1) {
      // Get the exponent
      exp = toPrecision.slice(index);
      // Remove the exponent in case we need to zero-extend
      toPrecision = toPrecision.slice(0, index);
    }
    index = Math.max(toPrecision.indexOf(","), toPrecision.indexOf("."));
    if(index === -1) {
      // No decimal found - if we want decimals, then we need to add it
      if(decimals !== 0) {
        toPrecision += '.';
      }
      // Calculate how long the string should be
      index = toPrecision.length + decimals;
    }
    else if(decimals !== 0) {
      // Calculate how long the string should be - accounting for the decimal place
      index += decimals + 1;
    }
    if(index > toPrecision.length) {
      // We need to add zeros!
      for(var cnt = index - toPrecision.length; cnt > 0; cnt--) {
        toPrecision += '0';
      }
    }
    else {
      // we need to remove characters
      toPrecision = toPrecision.slice(0, index);
    }
    // Add the exponent if there is one
    toPrecision += exp;
  }
  else {
    if (toPrecision.indexOf(",") != -1 || toPrecision.indexOf(".") != -1) {
      // If no decimal is specified, and there are decimal places, remove trailing zeros
      for (var i = toPrecision.length - 1; i > 0; i--) {
        if (toPrecision[i] == "0") {
          toPrecision = toPrecision.slice(0, i);
        }
        else if (toPrecision[i] == "." || toPrecision[i] == ",") {
          toPrecision = toPrecision.slice(0, i);
          break;
        }
        else {
          break;
        }
      }
    }
  }

  return toPrecision;
};



/**
 * Snap a date to a rounded value.
 * The snap intervals are dependent on the current scale and step.
 * @param {Date} date   the date to be snapped.
 * @return {Date} snappedDate
 */
DataStep.prototype.snap = function(date) {

};

/**
 * Check if the current value is a major value (for example when the step
 * is DAY, a major value is each first day of the MONTH)
 * @return {boolean} true if current date is major, else false.
 */
DataStep.prototype.isMajor = function() {
  return (this.current % (this.scale * this.majorSteps[this.stepIndex]) == 0);
};

module.exports = DataStep;
