/*
 * $Id: chart_axisDrawer.js,v 1.8 2010/03/10 13:18:24 ssk Exp $
 */  
dojo.declare("swx.inv.ChartAxisGridDrawer", null, {
  
/*************************************************************************************************
 * Constructor                                                                                   *
 *************************************************************************************************/
  constructor: function(key, chartLayout, mainGroup, isVML,
                        border, isSimple) {  // all arrays and complex objects should be declared here

    this._key = key;
    this._chartLayout = chartLayout;       // save the chart
    this._mainGroup = mainGroup; 
    this._isVML = isVML;
    this._border = border;
    this._isSimple = isSimple;
    
    this._axisDivNode = null;  // the DOM node that will contain all the axis divs
    this._axisGroup = null;    // everything that has to do with the axis
    
    this._init();
  },
  
/*************************************************************************************************
 * Static members ( You have to use this.statics.XXXX )                                          *
 *************************************************************************************************/
  statics: {
    // Just some int used to be set and compared again.
    UNIT_YEARS  : 1,
    UNIT_MONTHS : 2,
    UNIT_WEEKS  : 3,
    UNIT_DAYS   : 4,
    UNIT_HOURS  : 5,
    
    // MONTHS used in the x tick labels come from outside (with translations)
    
    GRID_COLOR      : "#dddddd",  // Grid basic color
    DARK_GRID_COLOR : "#888888",  // Grid color if day has changed in intraday view
    GRID_BACKGROUND : "#F7F7F7", // Color used one out of two blocks
    
    CROSSHAIR_COLOR : "#808080", // Color of the huge crosshair going across the whole chart
    
    

    /**
     * Get the amount between each vertical tick to display maxTicks on the graph
     * @param range is the vertical range to cover
     * @param maxTicks is the maximum number of ticks that we want
     */
    getUnitTickForRange: function(range, maxTicks) {
      if (range == 0) {
      	return [0.1, 0];
      }
      var unit = null;
      var ideal = range / maxTicks;
      var fixed = Math.floor(Math.log(ideal) / Math.LN10);
      var p = Math.pow(10, fixed);

      fixed = -fixed;
      var u = ideal / p;
      if (u <= 1) {
        unit = 1;
      } else if (u <= 2) {
        unit = 2;
      } else if (u <= 2.5) {
        unit = 2.5;
        fixed++;
      } else if (u <= 5) {
        unit = 5;
      } else {
        unit = 10;
        fixed--;
      }
      unit *= p;
      if (fixed < 0) fixed = 0;
      if (fixed == 1) fixed = 2; // at least 2 decimal places, not only one (if needed)
      return [unit, fixed];
    }
    
  },
  
/*************************************************************************************************
 * Private init function. Used to initialise everything                                          *
 *************************************************************************************************/
  _init: function() {
    // add the div that will contain all the divs for the axis
    this._axisDivNode = dojo.byId("chartGraphAxis"+this._key);

    // The axis group ( not anti-aliased )
    this._axisGroup = this._mainGroup.createGroup();  // create a new one
    this._axisGroup.getNode().setAttribute("shape-rendering","crispEdges");
    
    var graph_right = this._chartLayout.marginLeft + this._chartLayout.paddingLeft 
                    + this._chartLayout.graphWidth + this._chartLayout.paddingRight;
                     
    this._rightLimit = graph_right - 1;
    if (dojo.isIE) this._rightLimit += 2; // IE Cheat again
    
    this._leftLimit = this._chartLayout.marginLeft + 1;
    if (dojo.isIE) this._leftLimit += 2; // IE Cheat again
    
    this._graph_top = this._chartLayout.marginTop;
    // do not go to the top of the chart in advanced mode (we have an information line)
    if (!this._isSimple) this._graph_top += 20;
    
    this._graph_bottom = this._chartLayout.marginTop + this._chartLayout.paddingTop 
                       + this._chartLayout.graphHeight + this._chartLayout.paddingBottom;
                       
    // Draw the full width / height tracker lines for advanced Charts (-1000 pixels not to see it)
    if (! this._isSimple) {
      this._trackLineGroup = this._mainGroup.createGroup();
      this._trackLineGroup.getNode().setAttribute("shape-rendering","crispEdges");
      dojo.style(this._trackLineGroup.getNode(), "visibility", "hidden");
      this._vertTrackLine = 
        this._trackLineGroup.createLine( { x1: -1000, y1: this._graph_top, x2: -1000, y2: this._graph_bottom } ).setStroke(this.statics.CROSSHAIR_COLOR);
      this._horiTrackLine = 
        this._trackLineGroup.createLine( { x1: this._chartLayout.marginLeft, y1: -1000, x2: graph_right, y2: -1000 }  ).setStroke(this.statics.CROSSHAIR_COLOR);
    }
      
  },
  
  /*************************************************************************************************
   * Clean the grid and the tick labels
   */
  cleanForDraw: function() {
    // Clean axis :   ( remove Divs and grid lines )
    while (this._axisDivNode.hasChildNodes())
    {
      this._axisDivNode.removeChild(this._axisDivNode.firstChild);
    }
    this._axisGroup.clear();
  },
  
  /*************************************************************************************************/
  /**
   * Draw the grid and the tick labels for these values.
   * @param values : the values we want the grid for
   * @param isPercent : true if we want to display percent instead of price
   */

  drawAxis: function(values, isPercent, skipHorizontalLines) {
  
    var path = new StringBuffer();
    var fatPath = new StringBuffer();
    
  /********* HORIZONTAL AXIS ***************************/
    if (values.vals) {
      var xTicks = (values.vals.length - 1);  // number of x points
      var dRange = (values.vals[xTicks].d - values.vals[0].d);
  
      var maxXticksForThisChart = this._chartLayout.graphWidth / 15; // a tick every 15 pixels
      
      var unit = null;
      if (dRange < 1000 * 60 * 60 * 3 * maxXticksForThisChart) {
        // factor 3 to avoid display change when selecting two days separated by week-end 
        unit = this.statics.UNIT_HOURS;
      } else if (dRange < 1000 * 60 * 60 * 24 * maxXticksForThisChart) { 
        unit = this.statics.UNIT_DAYS;
      } else if (dRange < 1000 * 60 * 60 * 24 * 3 * maxXticksForThisChart) {
        unit = this.statics.UNIT_WEEKS;
      } else if (dRange < 1000 * 60 * 60 * 24 * 30 * maxXticksForThisChart) {
        unit = this.statics.UNIT_MONTHS;
      } else {
        unit = this.statics.UNIT_YEARS;
      }
      
      // hide axis while drawing
      dojo.style(this._axisDivNode, "visibility", "hidden");
      dojo.style(this._axisGroup.rawNode, "visibility", "hidden");
      
      var leftBlockForBackgroundColor = null; // first block has no background color
      var oldRight = 0;
      var first = true;
      var newMonth = null;
      var newYear = null;
 
      if (unit == this.statics.UNIT_HOURS) { // a line every hour
        for (var i=0; i<values.vals.length; i++) {
          val = values.vals[i];
          if (val.newDay && !this._isSimple) {
            txt = val.dayDate+"."+CHART_MONTH_LABELS[val.month]+"."+ (val.fullyear+"").substring(2,4);
            var obj = this._manageXAxis(i/xTicks, txt, fatPath, oldRight, leftBlockForBackgroundColor);
            oldRight = obj.r;
            leftBlockForBackgroundColor = obj.l;
          } else if (val.newHour) {
            txt = val.hour;
            var obj = this._manageXAxis(i/xTicks, txt, path, oldRight, leftBlockForBackgroundColor);
            oldRight = obj.r;
            leftBlockForBackgroundColor = obj.l;
          }
          first = false;
        }
      } else if (unit == this.statics.UNIT_DAYS) { // a line every day
        for (var i=0; i<values.vals.length; i++) {
          val = values.vals[i];
          var txt = val.dayDate;
          if (first || (val.newMonth && !this._isSimple)) {
            newMonth = " "+CHART_MONTH_LABELS[val.month];
          }
          if ((first || val.newYear) && !this._isSimple) {
            newYear = " "+ (val.fullyear+"").substring(2,4);
          }
          if (val.newDay) {
            if (newMonth) txt += newMonth;
            if (newYear)  txt += newYear;
            var obj = this._manageXAxis(i/xTicks, txt, path, oldRight, leftBlockForBackgroundColor);
            oldRight = obj.r;
            leftBlockForBackgroundColor = obj.l;
            if (obj.d) { // this year and month were displayed, do not display these ones again
              newMonth = null;
              newYear = null;
            }
            first = false;
          }
        }
      } else if (unit == this.statics.UNIT_WEEKS) {
        for (var i=0; i<values.vals.length; i++) {
          val = values.vals[i];
          if (first || (val.newMonth && !this._isSimple)) {
            newMonth = CHART_MONTH_LABELS[val.month];
          }
          if ((first || val.newYear) && !this._isSimple) {
            newYear = (val.fullyear+"").substring(2,4);
          }
          if (val.newWeek) {
            var txt = val.date.getDate();
            if (newMonth) txt += " "+newMonth;
            if (newYear) txt += " "+newYear;
            var obj = this._manageXAxis(i/xTicks, txt, path, oldRight, leftBlockForBackgroundColor);
            oldRight = obj.r;
            leftBlockForBackgroundColor = obj.l;
            if (obj.d) { // this year and month were displayed, do not display these ones again
              newMonth = null;
              newYear = null;
            }
            first = false;
          }
          
        }
      } else if (unit == this.statics.UNIT_MONTHS) {
        for (var i=0; i<values.vals.length; i++) {
          val = values.vals[i];
          if ((first || val.newYear) && !this._isSimple) {
            newYear = " "+ (val.fullyear+"").substring(2,4);
          }
          if (val.newMonth) {
            var txt = CHART_MONTH_LABELS[val.month];
            if (newYear) txt += newYear;
            var obj = this._manageXAxis(i/xTicks, txt, path, oldRight, leftBlockForBackgroundColor);
            oldRight = obj.r;
            leftBlockForBackgroundColor = obj.l;
            if (obj.d) { // this year was displayed, do not display this one again
              newYear = null;
            }
            first = false;
          }
        }
      } else if (unit == this.statics.UNIT_YEARS) {
        for (var i=0; i<values.vals.length; i++) {
          val = values.vals[i];
          if (val.newYear) {
            var txt = val.fullyear;
            var obj = this._manageXAxis(i/xTicks, txt, path, oldRight, leftBlockForBackgroundColor);
            oldRight = obj.r;
            leftBlockForBackgroundColor = obj.l;
          }
        }
      }
      
      if (leftBlockForBackgroundColor) { // If a last block exists
    
        this._axisGroup.createRect(
          { x: leftBlockForBackgroundColor, y: this._graph_top, width: this._rightLimit - leftBlockForBackgroundColor, height: this._graph_bottom - this._graph_top }
        ).setFill(this.statics.GRID_BACKGROUND);
        leftBlockForBackgroundColor = null; // do not fill the bext block
      }
      
    }
    
  /********* VERTICAL AXIS **********************************************/
    var leftSpace = this._chartLayout.marginLeft + this._chartLayout.paddingLeft;
    var topSpace  = this._chartLayout.marginTop + this._chartLayout.paddingTop;
    var chartEdgeRight = this._chartLayout.marginLeft + this._chartLayout.paddingLeft +
      this._chartLayout.graphWidth + this._chartLayout.paddingRight + this._chartLayout.marginRight;
    
    if (! isNaN(values.gRange) ) {
      if (!isPercent) { // real prices
        var unit_fixed = this.statics.getUnitTickForRange(values.gRange, this._getMaxVertTicks());
        unit = unit_fixed[0];
        var fixed = unit_fixed[1];

        for (var i=0; ; i+= unit) {
          var y = (Math.ceil(values.gMin / unit) * unit + i).toFixed(fixed);
          if (y > values.gMax) break; // no more place!
          var py = Math.floor(topSpace + this._chartLayout.graphHeight * ( 1 - (y - values.gMin) / values.gRange));

          var txt = chartGetNiceNumber(y,this._chartLayout.labelMaxDecimalPlaces) ;
        
          if (!skipHorizontalLines) {
            if (this._isVML) {
              path.append(" m").append(this._chartLayout.marginLeft).append(",").append(py).append(" l").append(chartEdgeRight).append(",").append(py);
            } else {
              path.append(" M").append(this._chartLayout.marginLeft).append(" ").append(py).append(" h").append(chartEdgeRight-this._chartLayout.marginLeft);
            }
          }

          // Set the x,y-position of chart y-axis labels
          var yAxisLabelXpos = this._chartLayout.axisOnLeft ? 0 : chartEdgeRight;
          var divNode = document.createElement("div");
          dojo.addClass(divNode, "axisDiv");
          if (this._chartLayout.axisOnLeft)
            yAxisLabelXpos += dojo.isIE ? 0 : 0;
          else
            yAxisLabelXpos += dojo.isIE ? 3 : 2;
          dojo.style(divNode, "left", yAxisLabelXpos);
          divNode.appendChild(document.createTextNode(txt));
          this._axisDivNode.appendChild(divNode);
          var yAxisLabelYpos = py - (dojo.coords(divNode).h / 2);
          if (!dojo.isIE && yAxisLabelYpos < -5)
            yAxisLabelYpos = -5;
          dojo.style(divNode, "top", yAxisLabelYpos);
        }
      } else { // percentage prices
        var startPrice = null;
        var index = 0;
        while (startPrice == null) {
          startPrice = values.vals[index++].y;
        }
        // mix id with idx etc... 
        if (!this._isSimple) { // cheat for advanced charts
          var index = advancedChart.valuesHelper.findStartPrices(advancedChart.values_displayed);
          if (index != -1) {
            startPrice = advancedChart.values_displayed.id.vals[index].y;
          }
        }
        var gPercentRange = values.gRange / startPrice * 100;
      
        var unit_fixed = this.statics.getUnitTickForRange(gPercentRange, 10);
        unit = unit_fixed[0];
        var fixed = unit_fixed[1];
        
        var minPercentToDisplay = Math.floor( (100 * values.gMin / startPrice - 100) / unit ) * unit;
        for (var i=minPercentToDisplay; ; i+= unit) {
          var y = startPrice / 100 * (100 + i);
          if (y < values.gMin) continue; // do not display this value yet (too low...)
          if (y > values.gMax) break; // no more place!

          var py = Math.floor(topSpace + this._chartLayout.graphHeight * ( 1 - (y - values.gMin) / values.gRange));
          if (!skipHorizontalLines || i != 0) { // for 100% we draw a darker line
            if (this._isVML) {
              path.append(" m").append(this._chartLayout.marginTop).append(",").append(py).append(" l").append(chartEdgeRight).append(",").append(py);
            } else {
              path.append(" M").append(this._chartLayout.marginTop).append(" ").append(py).append(" h").append(chartEdgeRight-this._chartLayout.marginLeft);
            }
          }
          if (i==0) { // for 100% we draw a darker line
            if (this._isVML) {
              fatPath.append(" m").append(this._chartLayout.marginTop).append(",").append(py).append(" l").append(chartEdgeRight).append(",").append(py);
            } else {
              fatPath.append(" M").append(this._chartLayout.marginTop).append(" ").append(py).append(" h").append(chartEdgeRight-this._chartLayout.marginLeft);
            }
          }

          var txt = (100 + i).toFixed(fixed) + "%";
          var divNode = document.createElement("div");
          dojo.addClass(divNode, "axisDiv");
          var lef = 2; // add 2 pixels space between line and label
          if (dojo.isIE) lef += 1; // Cheat for IE (not enough right)
          dojo.style(divNode, "left", chartEdgeRight + lef);
          divNode.appendChild(document.createTextNode(txt));
          this._axisDivNode.appendChild(divNode);  // has to be appended to use coords
          var h = dojo.coords(divNode).h;
          if (dojo.isIE) h -= 2; // cheat for IE (a bit too high)
          dojo.style(divNode, "top", py - h/2);
        }
      }
      
    }
    
    if (this._isVML) {
      var p = this._axisGroup.createPath().setStroke({color: this.statics.GRID_COLOR});
      swx.inv.ChartDrawer.prototype.statics.setVmlPath(p, path.toString());
    } else {
      this._axisGroup.createPath(path.toString()).setStroke({color: this.statics.GRID_COLOR});
    }
    
    if ("" != fatPath.toString()) {
      if (this._isVML) {
        var p = this._axisGroup.createPath().setStroke({color: this.statics.DARK_GRID_COLOR});
        swx.inv.ChartDrawer.prototype.statics.setVmlPath(p, fatPath.toString());
      } else {
        this._axisGroup.createPath(fatPath.toString()).setStroke({color: this.statics.DARK_GRID_COLOR});
      }
    }
    
    // show axis again
    dojo.style(this._axisDivNode, "visibility", "");
    dojo.style(this._axisGroup.rawNode, "visibility", "");
    
    this._border.moveToFront();
  },
  
  /**
   * Get the maximum number of vertical ticks. This depends on the graph height and is a maximum of 10
   */
  _getMaxVertTicks: function() {
    var maxTicks = Math.round(this._chartLayout.graphHeight/14);
    if (maxTicks > 10) maxTicks = 10;
    return maxTicks;
  },

  /**
   * Draw the x axis ticks and labels (time)
   * @param widthRatio = the index of this point / number of points. It is used to find the px position on the graph
   * @param txt is the label to display for this tick
   * @param path is a StringBuffer containing the lines of the grid
   * @oldRight is the position of the last tick. It is used to know if we display this one or not. To not overwrite the old one
   */
  _manageXAxis: function(widthRatio, txt, path, oldRight, leftBlockForBackgroundColor) {
    
    var graph_bottom = this._graph_bottom;
    
    var px = Math.floor(this._chartLayout.marginLeft + this._chartLayout.paddingLeft + this._chartLayout.graphWidth * widthRatio);

    var displayed = true;
    // axisGroup.createText is too slow :( ( we use divs )
    var divNode = document.createElement("div");
    dojo.addClass(divNode, "axisDiv");
    var divTop = graph_bottom + this._chartLayout.marginBottom;
    if (dojo.isIE) divTop += 1; // IE cheat
    dojo.style(divNode, "top", divTop);
    divNode.appendChild(document.createTextNode(txt));
    this._axisDivNode.appendChild(divNode);

    var w = dojo.coords(divNode).w;
    var left = px - w/2;
    if (dojo.isIE) left += 2; // IE Cheat again
    // if too much left... put it more right
    if (left < this._leftLimit) left = this._leftLimit;
    // if too much right... put it more left
    if (left + w > this._rightLimit) left = this._rightLimit - w;
    
    var newLeft = Math.floor(left); 
    
    // check if it is ok to display this text
    if (oldRight > 0 && newLeft < oldRight) {    // do not display this text, it will overwrite the old one!
      this._axisDivNode.removeChild(divNode);
      displayed = false;
    } else { 
      // here we know the label will be displayed (pull the grid line lower)
      graph_bottom += this._chartLayout.marginBottom;
      dojo.style(divNode, "left", newLeft);
      oldRight = newLeft + w + 2; // leave at least 2 px space
    }
    
    if (this._isVML) {
      path.append(" m").append(px).append(",").append(graph_bottom).append(" l").append(px).append(",").append(this._graph_bottom);
    } else {
      path.append(" M").append(px).append(" ").append(graph_bottom).append(" v").append(this._graph_bottom - graph_bottom);
    }
    
    if (leftBlockForBackgroundColor) {
      this._axisGroup.createRect(
        { x: leftBlockForBackgroundColor, y: this._graph_top, width: px - leftBlockForBackgroundColor, height: this._graph_bottom - this._graph_top }
      ).setFill(this.statics.GRID_BACKGROUND);
      leftBlockForBackgroundColor = null; // do not fill the bext block
    } else {
      leftBlockForBackgroundColor = px; // fill the next block
    }
    // oldRight is used to know if we can display the next tick without overwriting
    // displayed is used to say if the tick was displayed or not (if not, we want to display the month and year again next time)
    return { r:oldRight, d:displayed, l: leftBlockForBackgroundColor};
  },
  
  /**************************************************************************************************************************/
  
  drawOldClosing: function(oldClosingValue, oldClosingStrokeColor) {
    
    var graph_right = this._chartLayout.marginLeft + this._chartLayout.paddingLeft 
                    + this._chartLayout.graphWidth + this._chartLayout.paddingRight;
                      
    var path = new StringBuffer();
    var shape;
    if (this._isVML) {
      path.append(" m").append(this._chartLayout.marginLeft).append(",").append(oldClosingValue).append(" l").append(graph_right).append(",").append(oldClosingValue);
    } else {
      path.append(" M").append(this._chartLayout.marginLeft).append(" ").append(oldClosingValue).append(" h").append(graph_right-this._chartLayout.marginLeft);
    }
    if (this._isVML) {
      shape = this._mainGroup.createPath() ; //.setStroke({color: this.statics.OLD_CLOSE_COLOR});
      swx.inv.ChartDrawer.prototype.statics.setVmlPath(shape, path.toString());
    } else {
      shape = this._mainGroup.createPath(path.toString()); //.setStroke({color: this.statics.OLD_CLOSE_COLOR});
    }    
    shape.setStroke(oldClosingStrokeColor);
    shape.moveToFront();
    shape.getNode().setAttribute("shape-rendering","crispEdges");
    return shape;
  },
  
  /**************************************************************************************************************************/
  
  drawMaxMinPoints: function(values) {
    var leftSpace = this._chartLayout.marginLeft + this._chartLayout.paddingLeft;
    var topSpace  = this._chartLayout.marginTop  + this._chartLayout.paddingTop;
    
    var pxForyMin = Math.floor(leftSpace + this._chartLayout.graphWidth * values.iForyMin / (values.vals.length - 1));
    var pyForyMin = Math.floor(topSpace + this._chartLayout.graphHeight * (values.gMax - values.yMin) / values.gRange);
    
    if (isNaN(pyForyMin)) return null; // no min and max
    
    var pxForyMax = Math.floor(leftSpace + this._chartLayout.graphWidth * values.iForyMax / (values.vals.length - 1));
    var pyForyMax = Math.floor(topSpace + this._chartLayout.graphHeight * (values.gMax - values.yMax) / values.gRange);
    
    var path = new StringBuffer();
    if (this._isVML) {
      path.append(" m").append(pxForyMin-5).append(",").append(pyForyMin).append(" l").append(pxForyMin+5).append(",").append(pyForyMin)
          .append(" m").append(pxForyMax-5).append(",").append(pyForyMax).append(" l").append(pxForyMax+5).append(",").append(pyForyMax);
    } else {
      path.append(" M").append(pxForyMin-5).append(" ").append(pyForyMin).append(" h10")
          .append(" M").append(pxForyMax-5).append(",").append(pyForyMax).append(" h10");
    }

    var divNode = document.createElement("div");
    dojo.addClass(divNode, "axisDiv");
    dojo.style(divNode, "top", pyForyMin);
    divNode.appendChild(document.createTextNode(chartGetNiceNumber(values.yMin, 2)));
    this._axisDivNode.appendChild(divNode);
    var w = dojo.coords(divNode).w;
    var left = pxForyMin - w/2; 
    if (left < this._leftLimit) left = this._leftLimit;
    if (left + w > this._rightLimit) left = this._rightLimit - w;
    var newLeft = Math.floor(left);
    dojo.style(divNode, "left", newLeft);

    divNode = document.createElement("div");
    dojo.addClass(divNode, "axisDiv");
    dojo.style(divNode, "top", pyForyMax - 12);
    divNode.appendChild(document.createTextNode(chartGetNiceNumber(values.yMax, 2)));
    this._axisDivNode.appendChild(divNode);
    
    var left = pxForyMax - w/2; 
    if (left < this._leftLimit) left = this._leftLimit;
    if (left + w > this._rightLimit) left = this._rightLimit - w;
    var newLeft = Math.floor(left);
    dojo.style(divNode, "left", newLeft);
    var shape = null;
    if (this._isVML) {
      var shape = this._mainGroup.createPath().setStroke({width: 1, color: swx.inv.ChartDrawer.prototype.statics.ID_COLOR});
      swx.inv.ChartDrawer.prototype.statics.setVmlPath(shape, path.toString());
    } else {
      shape = this._mainGroup.createPath(path.toString()).setStroke({width: 1, color: swx.inv.ChartDrawer.prototype.statics.ID_COLOR});
    }
    shape.moveToFront();
    shape.getNode().setAttribute("shape-rendering","crispEdges");
    return shape;
  }
  
});
    
  
/*****************************************/
/**
 * StringBuffer class. Because appending strings is sooooo slooooow in IE!
 * With this cheat it goes much faster.
 */
function StringBuffer() { 
   this.buffer = []; 
 } 

 StringBuffer.prototype.append = function append(string) { 
   this.buffer.push(string); 
   return this; 
 }; 

 StringBuffer.prototype.toString = function toString() { 
   return this.buffer.join(""); 
 };

