var util = require('../../util');
var DOMutil = require('../../DOMutil');
var Component = require('./Component');
var DataStep = require('../DataStep');

/**
 * A horizontal time axis
 * @param {Object} [options]        See DataAxis.setOptions for the available
 *                                  options.
 * @constructor DataAxis
 * @extends Component
 * @param body
 */
function DataAxis (body, options, svg, linegraphOptions) {
  this.id = util.randomUUID();
  this.body = body;

  this.defaultOptions = {
    orientation: 'left',  // supported: 'left', 'right'
    showMinorLabels: true,
    showMajorLabels: true,
    icons: true,
    majorLinesOffset: 7,
    minorLinesOffset: 4,
    labelOffsetX: 10,
    labelOffsetY: 2,
    iconWidth: 20,
    width: '40px',
    visible: true,
    alignZeros: true,
    left:{
      range: {min:undefined,max:undefined},
      format: {decimals:undefined},
      title: {text:undefined,style:undefined}
    },
    right:{
      range: {min:undefined,max:undefined},
      format: {decimals:undefined},
      title: {text:undefined,style:undefined}
    }
  };

  this.linegraphOptions = linegraphOptions;
  this.linegraphSVG = svg;
  this.props = {};
  this.DOMelements = { // dynamic elements
    lines: {},
    labels: {},
    title: {}
  };

  this.dom = {};

  this.range = {start:0, end:0};

  this.options = util.extend({}, this.defaultOptions);
  this.conversionFactor = 1;

  this.setOptions(options);
  this.width = Number(('' + this.options.width).replace("px",""));
  this.minWidth = this.width;
  this.height = this.linegraphSVG.offsetHeight;
  this.hidden = false;

  this.stepPixels = 25;
  this.zeroCrossing = -1;
  this.amountOfSteps = -1;

  this.lineOffset = 0;
  this.master = true;
  this.svgElements = {};
  this.iconsRemoved = false;


  this.groups = {};
  this.amountOfGroups = 0;

  // create the HTML DOM
  this._create();

  var me = this;
  this.body.emitter.on("verticalDrag", function() {
    me.dom.lineContainer.style.top = me.body.domProps.scrollTop + 'px';
  });
}

DataAxis.prototype = new Component();


DataAxis.prototype.addGroup = function(label, graphOptions) {
  if (!this.groups.hasOwnProperty(label)) {
    this.groups[label] = graphOptions;
  }
  this.amountOfGroups += 1;
};

DataAxis.prototype.updateGroup = function(label, graphOptions) {
  this.groups[label] = graphOptions;
};

DataAxis.prototype.removeGroup = function(label) {
  if (this.groups.hasOwnProperty(label)) {
    delete this.groups[label];
    this.amountOfGroups -= 1;
  }
};


DataAxis.prototype.setOptions = function (options) {
  if (options) {
    var redraw = false;
    if (this.options.orientation != options.orientation && options.orientation !== undefined) {
      redraw = true;
    }
    var fields = [
      'orientation',
      'showMinorLabels',
      'showMajorLabels',
      'icons',
      'majorLinesOffset',
      'minorLinesOffset',
      'labelOffsetX',
      'labelOffsetY',
      'iconWidth',
      'width',
      'visible',
      'left',
      'right',
      'alignZeros'
    ];
    util.selectiveExtend(fields, this.options, options);

    this.minWidth = Number(('' + this.options.width).replace("px",""));

    if (redraw === true && this.dom.frame) {
      this.hide();
      this.show();
    }
  }
};


/**
 * Create the HTML DOM for the DataAxis
 */
DataAxis.prototype._create = function() {
  this.dom.frame = document.createElement('div');
  this.dom.frame.style.width = this.options.width;
  this.dom.frame.style.height = this.height;

  this.dom.lineContainer = document.createElement('div');
  this.dom.lineContainer.style.width = '100%';
  this.dom.lineContainer.style.height = this.height;
  this.dom.lineContainer.style.position = 'relative';

  // create svg element for graph drawing.
  this.svg = document.createElementNS('http://www.w3.org/2000/svg',"svg");
  this.svg.style.position = "absolute";
  this.svg.style.top = '0px';
  this.svg.style.height = '100%';
  this.svg.style.width = '100%';
  this.svg.style.display = "block";
  this.dom.frame.appendChild(this.svg);
};

DataAxis.prototype._redrawGroupIcons = function () {
  DOMutil.prepareElements(this.svgElements);

  var x;
  var iconWidth = this.options.iconWidth;
  var iconHeight = 15;
  var iconOffset = 4;
  var y = iconOffset + 0.5 * iconHeight;

  if (this.options.orientation === 'left') {
    x = iconOffset;
  }
  else {
    x = this.width - iconWidth - iconOffset;
  }

  var groupArray = Object.keys(this.groups);
  groupArray.sort(function (a,b) {
    return (a < b ? -1 : 1);
  })

  for (var i = 0; i < groupArray.length; i++) {
    var groupId = groupArray[i];
    if (this.groups[groupId].visible === true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] === true)) {
      this.groups[groupId].drawIcon(x, y, this.svgElements, this.svg, iconWidth, iconHeight);
      y += iconHeight + iconOffset;
    }
  }

  DOMutil.cleanupElements(this.svgElements);
  this.iconsRemoved = false;
};

DataAxis.prototype._cleanupIcons = function() {
  if (this.iconsRemoved === false) {
    DOMutil.prepareElements(this.svgElements);
    DOMutil.cleanupElements(this.svgElements);
    this.iconsRemoved = true;
  }
}

/**
 * Create the HTML DOM for the DataAxis
 */
DataAxis.prototype.show = function() {
  this.hidden = false;
  if (!this.dom.frame.parentNode) {
    if (this.options.orientation === 'left') {
      this.body.dom.left.appendChild(this.dom.frame);
    }
    else {
      this.body.dom.right.appendChild(this.dom.frame);
    }
  }

  if (!this.dom.lineContainer.parentNode) {
    this.body.dom.backgroundHorizontal.appendChild(this.dom.lineContainer);
  }
};

/**
 * Create the HTML DOM for the DataAxis
 */
DataAxis.prototype.hide = function() {
  this.hidden = true;
  if (this.dom.frame.parentNode) {
    this.dom.frame.parentNode.removeChild(this.dom.frame);
  }

  if (this.dom.lineContainer.parentNode) {
    this.dom.lineContainer.parentNode.removeChild(this.dom.lineContainer);
  }
};

/**
 * Set a range (start and end)
 * @param end
 * @param start
 * @param end
 */
DataAxis.prototype.setRange = function (start, end) {
  if (this.master === false && this.options.alignZeros === true && this.zeroCrossing != -1) {
    if (start > 0) {
      start = 0;
    }
  }
  this.range.start = start;
  this.range.end = end;
};

/**
 * Repaint the component
 * @return {boolean} Returns true if the component is resized
 */
DataAxis.prototype.redraw = function () {
  var resized = false;
  var activeGroups = 0;
  
  // Make sure the line container adheres to the vertical scrolling.
  this.dom.lineContainer.style.top = this.body.domProps.scrollTop + 'px';

  for (var groupId in this.groups) {
    if (this.groups.hasOwnProperty(groupId)) {
      if (this.groups[groupId].visible === true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] === true)) {
        activeGroups++;
      }
    }
  }
  if (this.amountOfGroups === 0 || activeGroups === 0) {
    this.hide();
  }
  else {
    this.show();
    this.height = Number(this.linegraphSVG.style.height.replace("px",""));

    // svg offsetheight did not work in firefox and explorer...
    this.dom.lineContainer.style.height = this.height + 'px';
    this.width = this.options.visible === true ? Number(('' + this.options.width).replace("px","")) : 0;

    var props = this.props;
    var frame = this.dom.frame;

    // update classname
    frame.className = 'vis-data-axis';

    // calculate character width and height
    this._calculateCharSize();

    var orientation = this.options.orientation;
    var showMinorLabels = this.options.showMinorLabels;
    var showMajorLabels = this.options.showMajorLabels;

    // determine the width and height of the elements for the axis
    props.minorLabelHeight = showMinorLabels ? props.minorCharHeight : 0;
    props.majorLabelHeight = showMajorLabels ? props.majorCharHeight : 0;

    props.minorLineWidth = this.body.dom.backgroundHorizontal.offsetWidth - this.lineOffset - this.width + 2 * this.options.minorLinesOffset;
    props.minorLineHeight = 1;
    props.majorLineWidth = this.body.dom.backgroundHorizontal.offsetWidth - this.lineOffset - this.width + 2 * this.options.majorLinesOffset;
    props.majorLineHeight = 1;

    //  take frame offline while updating (is almost twice as fast)
    if (orientation === 'left') {
      frame.style.top = '0';
      frame.style.left = '0';
      frame.style.bottom = '';
      frame.style.width = this.width + 'px';
      frame.style.height = this.height + "px";
      this.props.width = this.body.domProps.left.width;
      this.props.height = this.body.domProps.left.height;
    }
    else { // right
      frame.style.top = '';
      frame.style.bottom = '0';
      frame.style.left = '0';
      frame.style.width = this.width + 'px';
      frame.style.height = this.height + "px";
      this.props.width = this.body.domProps.right.width;
      this.props.height = this.body.domProps.right.height;
    }

    resized = this._redrawLabels();
    resized = this._isResized() || resized;

    if (this.options.icons === true) {
      this._redrawGroupIcons();
    }
    else {
      this._cleanupIcons();
    }

    this._redrawTitle(orientation);
  }
  return resized;
};

/**
 * Repaint major and minor text labels and vertical grid lines
 * @private
 */
DataAxis.prototype._redrawLabels = function () {
  var resized = false;
  DOMutil.prepareElements(this.DOMelements.lines);
  DOMutil.prepareElements(this.DOMelements.labels);
  var orientation = this.options['orientation'];

  // get the range for the slaved axis
  var step;
  if (this.master === false) {
    var stepSize, rangeStart, rangeEnd, minimumStep;
    if (this.zeroCrossing !== -1 && this.options.alignZeros === true) {
      if (this.range.end > 0) {
        stepSize = this.range.end / this.zeroCrossing; // size of one step
        rangeStart = this.range.end - this.amountOfSteps * stepSize;
        rangeEnd = this.range.end;
      }
      else {
        // all of the range (including start) has to be done before the zero crossing.
        stepSize = -1 * this.range.start / (this.amountOfSteps - this.zeroCrossing); // absolute size of a step
        rangeStart = this.range.start;
        rangeEnd = this.range.start + stepSize * this.amountOfSteps;
      }
    }
    else {
      rangeStart = this.range.start;
      rangeEnd = this.range.end;
    }
    minimumStep = this.stepPixels;
  }
  else {
    // calculate range and step (step such that we have space for 7 characters per label)
    minimumStep = this.props.majorCharHeight;
    rangeStart = this.range.start;
    rangeEnd = this.range.end;
  }

  this.step = step = new DataStep(
    rangeStart,
    rangeEnd,
    minimumStep,
    this.dom.frame.offsetHeight,
    this.options[this.options.orientation].range,
    this.master === false && this.options.alignZeros       // does the step have to align zeros? only if not master and the options is on
  );


  // the slave axis needs to use the same horizontal lines as the master axis.
  if (this.master === true) {
    this.stepPixels = ((this.dom.frame.offsetHeight) / step.marginRange) * step.step;
    this.amountOfSteps = Math.ceil(this.dom.frame.offsetHeight / this.stepPixels);
  }
  else {
    // align with zero
    if (this.options.alignZeros === true && this.zeroCrossing !== -1) {
      // distance is the amount of steps away from the zero crossing we are.
      let distance = (step.current - this.zeroCrossing * step.step) / step.step;
      this.step.shift(distance);
    }
  }

  // value at the bottom of the SVG
  this.valueAtBottom = step.marginEnd;

  // Get the number of decimal places
  var decimals;
  if(this.options[orientation].format !== undefined) {
    decimals = this.options[orientation].format.decimals;
  }

  this.maxLabelSize = 0;
  var y = 0;            // init value
  var stepIndex = 0;    // init value
  var isMajor = false;  // init value
  while (stepIndex < this.amountOfSteps) {
    y = Math.round(stepIndex * this.stepPixels);
    isMajor = step.isMajor();

    if (stepIndex > 0 && stepIndex !== this.amountOfSteps) {
      if (this.options['showMinorLabels'] && isMajor === false || this.master === false && this.options['showMinorLabels'] === true) {
        this._redrawLabel(y - 2, step.getCurrent(decimals), orientation, 'vis-y-axis vis-minor', this.props.minorCharHeight);
      }

      if (isMajor && this.options['showMajorLabels'] && this.master === true ||
        this.options['showMinorLabels'] === false && this.master === false && isMajor === true) {
        if (y >= 0) {
          this._redrawLabel(y - 2, step.getCurrent(decimals), orientation, 'vis-y-axis vis-major', this.props.majorCharHeight);
        }
        this._redrawLine(y, orientation, 'vis-grid vis-horizontal vis-major', this.options.majorLinesOffset, this.props.majorLineWidth);
      }
      else {
        this._redrawLine(y, orientation, 'vis-grid vis-horizontal vis-minor', this.options.minorLinesOffset, this.props.minorLineWidth);
      }
    }

    // get zero crossing
    if (this.master === true && step.current === 0) {
      this.zeroCrossing = stepIndex;
    }

    step.next();
    stepIndex += 1;
  }

  // get zero crossing if it's the last step
  if (this.master === true && step.current === 0) {
    this.zeroCrossing = stepIndex;
  }

  this.conversionFactor = this.stepPixels / step.step;

  // Note that title is rotated, so we're using the height, not width!
  var titleWidth = 0;
  if (this.options[orientation].title !== undefined && this.options[orientation].title.text !== undefined) {
    titleWidth = this.props.titleCharHeight;
  }
  var offset = this.options.icons === true ? Math.max(this.options.iconWidth, titleWidth) + this.options.labelOffsetX + 15 : titleWidth + this.options.labelOffsetX + 15;

  // this will resize the yAxis to accommodate the labels.
  if (this.maxLabelSize > (this.width - offset) && this.options.visible === true) {
    this.width = this.maxLabelSize + offset;
    this.options.width = this.width + "px";
    DOMutil.cleanupElements(this.DOMelements.lines);
    DOMutil.cleanupElements(this.DOMelements.labels);
    this.redraw();
    resized = true;
  }
  // this will resize the yAxis if it is too big for the labels.
  else if (this.maxLabelSize < (this.width - offset) && this.options.visible === true && this.width > this.minWidth) {
    this.width = Math.max(this.minWidth,this.maxLabelSize + offset);
    this.options.width = this.width + "px";
    DOMutil.cleanupElements(this.DOMelements.lines);
    DOMutil.cleanupElements(this.DOMelements.labels);
    this.redraw();
    resized = true;
  }
  else {
    DOMutil.cleanupElements(this.DOMelements.lines);
    DOMutil.cleanupElements(this.DOMelements.labels);
    resized = false;
  }

  return resized;
};

DataAxis.prototype.convertValue = function (value) {
  var invertedValue = this.valueAtBottom - value;
  var convertedValue = invertedValue * this.conversionFactor;
  return convertedValue;
};

DataAxis.prototype.screenToValue = function (x) {
  return this.valueAtBottom - (x / this.conversionFactor);
};

/**
 * Create a label for the axis at position x
 * @private
 * @param y
 * @param text
 * @param orientation
 * @param className
 * @param characterHeight
 */
DataAxis.prototype._redrawLabel = function (y, text, orientation, className, characterHeight) {
  // reuse redundant label
  var label = DOMutil.getDOMElement('div',this.DOMelements.labels, this.dom.frame); //this.dom.redundant.labels.shift();
  label.className = className;
  label.innerHTML = text;
  if (orientation === 'left') {
    label.style.left = '-' + this.options.labelOffsetX + 'px';
    label.style.textAlign = "right";
  }
  else {
    label.style.right = '-' + this.options.labelOffsetX + 'px';
    label.style.textAlign = "left";
  }

  label.style.top = y - 0.5 * characterHeight + this.options.labelOffsetY + 'px';

  text += '';

  var largestWidth = Math.max(this.props.majorCharWidth,this.props.minorCharWidth);
  if (this.maxLabelSize < text.length * largestWidth) {
    this.maxLabelSize = text.length * largestWidth;
  }
};

/**
 * Create a minor line for the axis at position y
 * @param y
 * @param orientation
 * @param className
 * @param offset
 * @param width
 */
DataAxis.prototype._redrawLine = function (y, orientation, className, offset, width) {
  if (this.master === true) {
    var line = DOMutil.getDOMElement('div',this.DOMelements.lines, this.dom.lineContainer);//this.dom.redundant.lines.shift();
    line.className = className;
    line.innerHTML = '';

    if (orientation === 'left') {
      line.style.left = (this.width - offset) + 'px';
    }
    else {
      line.style.right = (this.width - offset) + 'px';
    }

    line.style.width = width + 'px';
    line.style.top = y + 'px';
  }
};

/**
 * Create a title for the axis
 * @private
 * @param orientation
 */
DataAxis.prototype._redrawTitle = function (orientation) {
  DOMutil.prepareElements(this.DOMelements.title);

  // Check if the title is defined for this axes
  if (this.options[orientation].title !== undefined && this.options[orientation].title.text !== undefined) {
    var title = DOMutil.getDOMElement('div', this.DOMelements.title, this.dom.frame);
    title.className = 'vis-y-axis vis-title vis-' + orientation;
    title.innerHTML = this.options[orientation].title.text;

    // Add style - if provided
    if (this.options[orientation].title.style !== undefined) {
      util.addCssText(title, this.options[orientation].title.style);
    }

    if (orientation === 'left') {
      title.style.left = this.props.titleCharHeight + 'px';
    }
    else {
      title.style.right = this.props.titleCharHeight + 'px';
    }

    title.style.width = this.height + 'px';
  }

  // we need to clean up in case we did not use all elements.
  DOMutil.cleanupElements(this.DOMelements.title);
};




/**
 * Determine the size of text on the axis (both major and minor axis).
 * The size is calculated only once and then cached in this.props.
 * @private
 */
DataAxis.prototype._calculateCharSize = function () {
  // determine the char width and height on the minor axis
  if (!('minorCharHeight' in this.props)) {
    var textMinor = document.createTextNode('0');
    var measureCharMinor = document.createElement('div');
    measureCharMinor.className = 'vis-y-axis vis-minor vis-measure';
    measureCharMinor.appendChild(textMinor);
    this.dom.frame.appendChild(measureCharMinor);

    this.props.minorCharHeight = measureCharMinor.clientHeight;
    this.props.minorCharWidth = measureCharMinor.clientWidth;

    this.dom.frame.removeChild(measureCharMinor);
  }

  if (!('majorCharHeight' in this.props)) {
    var textMajor = document.createTextNode('0');
    var measureCharMajor = document.createElement('div');
    measureCharMajor.className = 'vis-y-axis vis-major vis-measure';
    measureCharMajor.appendChild(textMajor);
    this.dom.frame.appendChild(measureCharMajor);

    this.props.majorCharHeight = measureCharMajor.clientHeight;
    this.props.majorCharWidth = measureCharMajor.clientWidth;

    this.dom.frame.removeChild(measureCharMajor);
  }

  if (!('titleCharHeight' in this.props)) {
    var textTitle = document.createTextNode('0');
    var measureCharTitle = document.createElement('div');
    measureCharTitle.className = 'vis-y-axis vis-title vis-measure';
    measureCharTitle.appendChild(textTitle);
    this.dom.frame.appendChild(measureCharTitle);

    this.props.titleCharHeight = measureCharTitle.clientHeight;
    this.props.titleCharWidth = measureCharTitle.clientWidth;

    this.dom.frame.removeChild(measureCharTitle);
  }
};

module.exports = DataAxis;