/*
 * $Id: chart_graphDrawer.js,v 1.1 2010/05/05 13:43:55 obo Exp $
 */  
dojo.declare("swx.inv.ChartGraphDrawer", null, {
  
/*************************************************************************************************
 * Constructor                                                                                   *
 *************************************************************************************************/
  constructor: function(chartLayout, mainGroup, isVML) {  // all arrays and complex objects should be declared here

    this._chartLayout = chartLayout;       // save the chart
    
    this._mainGroup = mainGroup;
    this._isVML = isVML;
    
    this.graphShapes = {};
    
    // save slider shapes
    this._sliderShapes = {};
    
    this._init();
  },
  
/*************************************************************************************************
 * Static members ( You have to use this.statics.XXXX )                                          *
 *************************************************************************************************/
  statics: {
  	
    BAR_COLOR         : "black",   // bars and outline of candles
    CANDLE_UP_COLOR   : "green",
    CANDLE_DOWN_COLOR : "red",
    
    SMALL_CHART_GRAY_COLOR      : "#888888",
    SMALL_CHART_GRAY_FILL_COLOR : [200, 200, 200, 0.5],
    SMALL_CHART_COLOR           : "black",
    SMALL_CHART_FILL_COLOR      : [80, 142, 178, 0.5]
   
  },
  
/*************************************************************************************************
 * Private init function. Used to initialise everything                                          *
 *************************************************************************************************/
  _init: function() {
  },
  
  cleanForDraw: function() {
    var savedThis = this;

    // cleanMainLayer : 
    var thingsToClean = [ "idBars", "idCandles", "idx1", "idx2", "idLineUp", "idLineDown", 
                          "idFill", "oldClose", "maxMin" ];
    for (var i=1; i<=10; i++) { // Add all the technical indicators
      thingsToClean.push("ti"+i);
    }
    dojo.forEach(thingsToClean, function(n) {
      if (savedThis.graphShapes[n] != null) {
      	savedThis._mainGroup.remove(savedThis.graphShapes[n]);
      	savedThis.graphShapes[n] = null;
      }
    });
  },
  
  /*************************************************************************************************/
  // Creates a polyline with the values
  
  drawLine: function(values, group, myColor, fillColor, crispEdges) {
    if (values == null || values.length == 0) return [null, null]; // nothing to draw!
    
    if (values.length == 2) { // only 1 point, we have to add 2 pixels to see the chart!
      values = [values[0], values[1], values[0]+2, values[1]];
    }
    var ret = [];
    if (fillColor) {
      var bottomOfGraph = this._chartLayout.marginTop + this._chartLayout.paddingTop 
                          + this._chartLayout.graphHeight + this._chartLayout.paddingBottom;
      // Add first and last points to fill the graph with a color
      var newValues = [values[0], bottomOfGraph].concat(values, 
                         [values[values.length - 2], 
                         bottomOfGraph]);
      var p1 = this._myCreatePolyline(newValues, group);
      p1.setFill( fillColor );
      if(crispEdges == true) p1.getNode().setAttribute("shape-rendering","crispEdges");
      ret.push( p1 );

    }
    var p2 = this._myCreatePolyline(values, group);
    p2.setStroke({color: myColor, width: 1});
    if(crispEdges == true) p2.getNode().setAttribute("shape-rendering","crispEdges");
    ret.push( p2 );    
    return ret;
  },
  
  /*************************************************************************************************/
  // Creates a polyline with the values
  
  drawIntraday2ColorLine: function(values, group, oldClose, upColor, downColor, fillColor, crispEdges) {
    if (values == null || values.length == 0) return [null, null]; // nothing to draw!
    
    if (values.length == 2) { // only 1 point, we have to add 2 pixels to see the chart!
      values = [values[0], values[1], values[0]+2, values[1]];
    }
    var ret = [];
    if (fillColor) {
      var bottomOfGraph = this._chartLayout.marginTop + this._chartLayout.paddingTop 
                          + this._chartLayout.graphHeight + this._chartLayout.paddingBottom;
                         
      // Add first and last points to fill the graph with a color
      var newValues = [values[0], bottomOfGraph].concat(values, 
                         [values[values.length - 2], 
                         bottomOfGraph]);
      ret.push( this._myCreatePolyline(newValues, group).setFill( fillColor ) );
    }
    
    // now do up/down graph
    var pathUp = null;
    var pathDown = null;
    
    var pointsUp = [];
    var pointsDown = [];
    
    var moveTo = this._isVML ? "m" : "M";
    var sep    = this._isVML ? "," : " ";
    var lineTo = this._isVML ? "l" : "L";
    
    var top = null;
    for (var i=0; i<values.length; i+=2) {
      if (oldClose > 0 && values[i+1] > oldClose) { // 0 is on top of graph! (this is pixels)
        if (!pathDown) pathDown = new StringBuffer();
        if (top == null) { // first point of the graph
          pathDown.append(moveTo).append(values[i]).append(sep).append(values[i+1]).append(" ")
                .append(lineTo).append(values[i]).append(sep).append(values[i+1]);
          top = false;
        } else if (top) { // passed top/bottom : move to new point
          if (pathUp) { // finish pathDown to the limit (oldClose)
            pathUp.append(sep).append(values[i]).append(sep).append(oldClose);
          }
          // draw line from oldClose to actual point
          pathDown.append(" ").append(moveTo).append(values[i]).append(sep).append(oldClose).append(" ")
                .append(lineTo).append(values[i]).append(sep).append(values[i+1]);
          top = false;
        } else {
          pathDown.append(sep).append(values[i]).append(sep).append(values[i+1]);
        }
      } else {
        if (!pathUp) pathUp = new StringBuffer();
        if (top == null) { // first point of the graph
          pathUp.append(moveTo).append(values[i]).append(sep).append(values[i+1]).append(" ")
                .append(lineTo).append(values[i]).append(sep).append(values[i+1]);
          top = true;
        } else if (!top) { // passed top/bottom : move to new point
          if (pathDown) { // finish pathDown to the limit (oldClose)
            pathDown.append(sep).append(values[i]).append(sep).append(oldClose);
          }
          // draw line from oldClose to actual point
          pathUp.append(" ").append(moveTo).append(values[i]).append(sep).append(oldClose).append(" ")
                .append(lineTo).append(values[i]).append(sep).append(values[i+1]);
          top = true;
        } else {
          pathUp.append(sep).append(values[i]).append(sep).append(values[i+1]);
        }
      }
    }
    
    if (pathUp) {
      var p = null;
      if (this._isVML) { // create a vml path myself! (FASTER!)
        p = group.createPolyline();
        p.rawNode.path.v = pathUp.toString()+" e";
      } else {
        p = group.createPath(pathUp.toString());
      }     
      if(crispEdges == true) p.getNode().setAttribute("shape-rendering","crispEdges");      
      p.setStroke({color: upColor, width: 1});
      ret.push( p );
    } else {
      ret.push( null );
    }
    
    if (pathDown) {
      var p = null;
      if (this._isVML) { // create a vml path myself! (FASTER!)
        p = group.createPolyline();
        p.rawNode.path.v = pathDown.toString()+" e";
      } else {
        p = group.createPath(pathDown.toString());
      }
      if(crispEdges == true) p.getNode().setAttribute("shape-rendering","crispEdges");
      p.setStroke({color: downColor, width: 1});
      ret.push( p );
    } else {
      ret.push( null );
    }
    
    return ret;
  },

  _myCreatePolyline: function(values, surf) { // faster than normal polyline!
    var p = null;
    if (this._isVML) { // create a vml path myself! (FASTER!)
      var path = "m" + values[0]+","+values[1]+" l"+values.join(",") +" e";
      p = surf.createPolyline();
      p.rawNode.path.v = path;
    } else {
      var path = "M" + values[0]+" "+values[1]+" L"+values.join(" ");
      p = surf.createPath(path);
    }
    return p;
  },
  
  _drawOnlyBars: function(values) {  // only vert bars
    var g = this._mainGroup.createGroup();
    var path = new StringBuffer();
    if (this._isVML) { // cerate a vml path myself! (FASTER!)
      dojo.forEach(values, function(val) {
      	if (val.min) {
          path.append(" m").append(val.px).append(",").append(val.min).append(" l").append(val.px).append(",").append(val.max);
      	}
      });
      var p = g.createPath().setStroke({color: this.statics.BAR_COLOR});
      swx.inv.ChartDrawer.prototype.statics.setVmlPath(p, path.toString());
    } else {
      dojo.forEach(values, function(val) {
        if (val.min) {
          path.append(" M").append(val.px).append(" ").append(val.min).append(" v").append(val.max-val.min);
        }
      });
      g.createPath(path.toString()).setStroke({color: this.statics.BAR_COLOR});
    }
    return g;
  },
  
  drawBars: function(values) {
    var d = new Date();
    var g = this._drawOnlyBars(values);

    var candleHalfWidth = this._getCandleHalfWidth(values);

    if (candleHalfWidth) {
      var path = new StringBuffer();
      if (this._isVML) { // create a vml path myself! ( 40 times FASTER!)
        dojo.forEach(values, function(val) {
       	  if (val.min) {
            path.append(" m").append(val.px).append(",").append(val.open).append(" l").append(val.px-candleHalfWidth)
                .append(",").append(val.open ).append(" m").append(val.px).append(",").append(val.close).append(" l")
                .append(val.px+candleHalfWidth).append(",").append(val.close);
          }
        });
        var p = g.createPath().setStroke({color: this.statics.BAR_COLOR});
        swx.inv.ChartDrawer.prototype.statics.setVmlPath(p, path.toString());
      } else {
        dojo.forEach(values, function(val) {
          if (val.min) {
            path.append(" M").append(val.px).append(" ").append(val.open).append(" h-").append(candleHalfWidth )
                .append(" M").append(val.px).append(" ").append(val.close).append(" h").append(candleHalfWidth);
          }
        });
        g.createPath(path.toString()).setStroke({color: this.statics.BAR_COLOR});
      }
    }
    // remove antialias for this!
    g.getNode().setAttribute("shape-rendering","crispEdges");
    this.graphShapes.idBars = g;
    
  },
  
  drawCandles: function(values) {
    var path1 = "";
    var path2 = "";
    var d = new Date();
    var g = this._drawOnlyBars(values);

    var candleHalfWidth = this._getCandleHalfWidth(values);

    if (candleHalfWidth) {
      var path1 = new StringBuffer();
      var path2 = new StringBuffer();
      if (this._isVML) { // cerate a vml path myself! (FASTER!)
        dojo.forEach(values, function(val) {
          if (val.min) {
            if (val.close > val.open) {  // going down !! (pixel wise going up)
              path1.append(" m").append(val.px-candleHalfWidth).append(",").append(val.open).append(" l").append(val.px+candleHalfWidth).append(",")
                   .append(val.open).append(",").append(val.px+candleHalfWidth).append(",").append(val.close).append(",").append(val.px-candleHalfWidth)
                   .append(",").append(val.close).append(",").append(val.px-candleHalfWidth).append(",").append(val.open);
            } else {
              path2.append(" m").append(val.px-candleHalfWidth).append(",").append(val.open).append(" l").append(val.px+candleHalfWidth).append(",")
                   .append(val.open ).append(",").append(val.px+candleHalfWidth).append(",").append(val.close).append(",").append(val.px-candleHalfWidth)
                   .append(",").append(val.close).append(",").append(val.px-candleHalfWidth).append(",").append(val.open);
            }
          }
        });
        var p1 = g.createPath().setFill(this.statics.CANDLE_DOWN_COLOR).setStroke({color: this.statics.BAR_COLOR});
        swx.inv.ChartDrawer.prototype.statics.setVmlPath(p1, path1.toString());
        var p2 = g.createPath().setFill(this.statics.CANDLE_UP_COLOR).setStroke({color: this.statics.BAR_COLOR});
        swx.inv.ChartDrawer.prototype.statics.setVmlPath(p2, path2.toString());
      } else {
        dojo.forEach(values, function(val) {
          if (val.min) {
            if (val.close > val.open) {  // going down !! (pixel wise going up)
              path1.append(" M").append(val.px-candleHalfWidth).append(" ").append(val.open).append(" h").append(2*candleHalfWidth).append(" V")
                   .append(val.close).append(" h-").append(2*candleHalfWidth).append(" V").append(val.open);
            } else {
              path2.append(" M").append(val.px-candleHalfWidth).append(" ").append(val.close).append(" h").append(2*candleHalfWidth).append(" V")
                   .append(val.open).append(" h-").append(2*candleHalfWidth).append(" V").append(val.close);
            }
          }
        });
        g.createPath(path1.toString()).setFill(this.statics.CANDLE_DOWN_COLOR).setStroke({color: this.statics.BAR_COLOR});
        g.createPath(path2.toString()).setFill(this.statics.CANDLE_UP_COLOR).setStroke({color: this.statics.BAR_COLOR});
      }
    }

    // remove antialias for this!
    g.getNode().setAttribute("shape-rendering","crispEdges");
    this.graphShapes.idCandles = g;
  },
  
  _getCandleHalfWidth: function(values) {
    var pxRange = values.length;
    var pxRange = pxRange * 600 / (values[values.length-1].px - values[0].px)
    
    var candleHalfWidth = null;
    if (pxRange < 50) {
      candleHalfWidth = 4;
    } else if (pxRange < 100) {
      candleHalfWidth = 3;
    } else if (pxRange < 160) {
      candleHalfWidth = 2;
    } else if (pxRange < 280) {
      candleHalfWidth = 1;
    } else {
      // don't draw them
    }
    return candleHalfWidth;
  },
  
  drawSmallChart: function(displayed_values, zsurface_gray, zsurface_color) {    
    var newValues = [displayed_values.id.smallGraphLine[0], this._chartLayout.smallGraphHeight];  // first point at the bottom

    for (var i=0; i<displayed_values.id.smallGraphLine.length;) {
      newValues.push(displayed_values.id.smallGraphLine[i++]);

      // 6 and 3 make 3 pixels padding on small chart (top + bottom)
      newValues.push(Math.floor( (displayed_values.id.smallGraphLine[i++] - this._chartLayout.paddingTop) * (this._chartLayout.smallGraphHeight-6) / 
                                (this._chartLayout.graphHeight)  + 4));
    }
    newValues.push(displayed_values.id.smallGraphLine[displayed_values.id.smallGraphLine.length - 2]);
    newValues.push(this._chartLayout.smallGraphHeight);

    if (this._sliderShapes.grayFilled) {
      zsurface_gray.remove(this._sliderShapes.grayFilled);
      zsurface_gray.remove(this._sliderShapes.grayLined);
    }
    if (this._sliderShapes.colorFilled) {
      zsurface_color.remove(this._sliderShapes.colorFilled);
      zsurface_color.remove(this._sliderShapes.colorLined);
    }

    // we make one gray
    this._sliderShapes.grayFilled = this._myCreatePolyline(
                                     newValues, zsurface_gray).setFill( this.statics.SMALL_CHART_GRAY_FILL_COLOR
                                   );  // a bit transparent
    this._sliderShapes.grayLined  = this._myCreatePolyline(
                                     newValues.slice(2, newValues.length -2), zsurface_gray).setStroke({color: this.statics.SMALL_CHART_GRAY_COLOR}
                                   );

    // and one colored
    this._sliderShapes.colorFilled = this._myCreatePolyline(
                                      newValues, zsurface_color).setFill( this.statics.SMALL_CHART_FILL_COLOR
                                    );  // a bit transparent
    this._sliderShapes.colorLined  = this._myCreatePolyline(
                                      newValues.slice(2, newValues.length -2), zsurface_color).setStroke({color: this.statics.SMALL_CHART_COLOR}
                                    );
  }  

});

