/*
 * $Id: chart_main.js,v 1.4 2010/08/12 11:39:06 obo Exp $
 */  
dojo.require("dojo.number");
dojo.require("dojo.date.locale");

dojo.declare("swx.inv.Chart", null, {
  
/*************************************************************************************************
 * Constructor      
 * expirationDate, contractType, strikePrice: Additional FQS PK (primary key) parameters, used 
 *            to locate the security inside Eurex app                               
 * labelPrecision: number of decimal places on Y axis labels
 * tradingStartTime, tradingEndTime: Allows to override start/end time of intraday chart
 *************************************************************************************************/
  constructor: function(id, key, domain, type, chartLayout, customConfig,
      skipLoading, skipMovie, expirationDate, contractType, strikePrice, labelPrecision,
      tradingStartTime, tradingEndTime) {
  
    // FQS PK (primary key) parameters, used to locate the security, which chart we would like to plot 
    // SecurityId/ProductSymbol is common parameter for ssecom/stoxx/eurex
    this.id = id;         // save the id
    
    // Eurex only, for the rest those below remains undefined
    this.expirationDate = expirationDate;
    this.contractType = contractType;
    this.strikePrice = strikePrice;
    // end of PK parameters
    
    // Override default start/end times of intraday chart
    this.tradingStartTime = tradingStartTime;
    this.tradingEndTime = tradingEndTime;

    // Save the key used to find HTML elements
    this.key = key;       
    
    if (!domain)
      domain = "1d"; // intraday by default
    this.domain = domain; // save the domain (number of trading days to show)
        
    if (!type) type = this.statics.SIMPLE; // simple type by default
    this.type = type;     // save the type (simple, advanced)
    
    if (type != this.statics.SIMPLE)
      selectedTimeRange = domain;
    
    if (!chartLayout)
      this._chartLayout = siteChartConfig.getMINI_LAYOUT(); // mini layout by default
    else
      this._chartLayout = chartLayout;     // object containing layout of the chart
    
    /*
     * override defaul labelPrecision property from chartConfig; precision varies between
     * various Eurex products and product groups
     */
    //console.log('passed labelPrecision = ['+labelPrecision+']');
    if(labelPrecision){
      //console.log('set labelPrecision to '+labelPrecision);
      if(labelPrecision == "AUTO"){
        // trigger default behaviour (auto) of axis renderer by resetting 
        // chartLayout.labelPrecision property (null in constructor means 'no-change')
        this._chartLayout.labelPrecision = null;
      } else        
        this._chartLayout.labelPrecision = labelPrecision ;
    }
    
    this._completeLayout(); // complete the layout
    
    this.skipMovie = skipMovie; // true if we do not want to update the chart
    
    this.useIntradayValues = true; // changed afterwards (for simple chart)
    
    this.values_days = {};   // containing day ticks (for advanced graphs)
    this.values_inter = {}; 
    this.values_inter_zoom = {};
    this.values_intra = {};  // containing intraday ticks (max 10 days, for advanced graphs)
    this.values_zoomed = {};  // containing intraday ticks like values_intra but each day 
                              // is an object member (only advanced graphs)
    
    this.values_domain     = {};  // values of the full domain being drawn
    this.values_displayed  = {};  // values of the drawn points (for advanced graphs)
    this.graph_values      = {};  // Graph values being displayed
    
    
    this.showedLines = {}; // save which line is shown (for advanced graphs)
    this.showedLines.id = this.id; // chart line is always displayed 
    
    this.showedEvents = {}; // For advanced charts (news, dividends, ...)
    this.events = {}; // Save all the downloaded events (capital events/news, for advanced graphs)
    
    // Save the start and stop percents for advanced graphs
    this.displayedMinPercent = 0;
    this.displayedMaxPercent = 1;
    
    // The data updater (uses ajax to load data at the start)
    this._chartDataUpdater = new swx.inv.ChartDataUpdater(this);
    // The drawer
    this.chartDrawer = new swx.inv.ChartDrawer(type == this.statics.SIMPLE, domain, key, this._chartLayout);
    
    // The ValuesHelper
    if (type == this.statics.SIMPLE) {
      this.valuesHelper = new swx.inv.ChartValuesHelper(this, type == this.statics.SIMPLE);
    } else {
      this.valuesHelper = new swx.inv.ChartValuesAdvancedHelper(this);
    }
    
    // boolean to know if the functions are active or not. (For advanced charts)
    this._functionsActive = false;
    
    // resize the holding DIV according to chart configuration
    var chartNode = dojo.query('#chart'+this.key);
    if(!isNaN(this._chartLayout.chartWidth))
      chartNode.style('width', this._chartLayout.chartWidth);
    if(!isNaN(this._chartLayout.chartHeight) )
      chartNode.style('height', this._chartLayout.chartHeight);    

    // adjust the position of loading/'no data' message
    // 
    // Save layout ptr first
    var currentLayout = this._chartLayout;
    var adjusterFun = function(node){
      // do not move the element around if its is hidden (dojo.coords returns empty w/h values)
      if(node.style.display == 'none') 
        return; 
      var loadingMsgCoords = dojo.coords(node);
      var leftOffset = Math.round(Math.max(currentLayout.graphWidth  - loadingMsgCoords.w,0)/2+currentLayout.marginLeft);
      var topOffset  = Math.round(Math.max(currentLayout.graphHeight - loadingMsgCoords.h,0)/2+currentLayout.marginTop);
       
      if(!isNaN(topOffset))
        dojo.style(node,'top', topOffset);
      if(!isNaN(leftOffset) )
        dojo.style(node,'left', leftOffset);    
    };
    dojo.query('#gfxLoading'+this.key).forEach(adjusterFun);
    
    // 'no data' node is not rendered by defaultbut we still need to get its size
    // the solution is to render it but do not display (hide) so we can get its dimensions
    // without showing it to user
    dojo.query('#gfxEmpty'+this.key).forEach(function(node){      
      dojo.style(node,'visibility', 'hidden');
      dojo.style(node,'display', 'block');
      adjusterFun(node);
      dojo.style(node,'display', 'none');
      dojo.style(node,'visibility', '');
    });

    // Set up the timer if needed (used to get the new data)
    // we only use a static one to save number of timers on a page!
    if (this.statics._timer == null)
      this.statics._startTimer();
    
    // Custom Configuration (for advanced charts)
    if (customConfig)
      chartChangeGraphFromCustomConfig(this, customConfig, true);
    
    // get the initial values and run a function with the ajax response
    //  TODO: probably we need to change following line
    if (!skipLoading) this.getAjaxValues("id", this.id);

    this.statics.allCharts.push(this);
  },
  
/*************************************************************************************************
 * Static members ( You have to use this.statics.XXXX )                                          *
 *************************************************************************************************/
  statics: {
    // different types (SIMPLE: only show static data, ADVANCED: have some interactivity)
    SIMPLE   : 1,
    ADVANCED : 2,
    
    // different grains for data
    INTRADAY : "intraday",
    INTERMEDIATE : "intermediate",
    HISTORICAL : "historic",
    
    _timer: null, // a time to get the new data from (unique for all graphs to save browser's memory
    
    _startTimer: function() { // this is only called once at the first instance created of this class
      this._timer = setInterval(function() {
        for (var cNb=0; cNb<swx.inv.Chart.prototype.statics.allCharts.length; cNb++) {
          if (swx.inv.Chart.prototype.statics.allCharts[cNb].quotePlayer) {
            swx.inv.Chart.prototype.statics.allCharts[cNb].quotePlayer.timerTick();
          }
        }
      }, 1000);
    },
    allCharts : []
  },
  
/*************************************************************************************************
 * Private init function. Used to init the chart                                                 *
 *************************************************************************************************/
  _completeLayout: function() {
    this._chartLayout.surfaceWidth  = this._chartLayout.marginLeft + this._chartLayout.paddingLeft 
                                    + this._chartLayout.graphWidth  + this._chartLayout.paddingRight
                                    + this._chartLayout.marginRight;
    this._chartLayout.surfaceHeight = this._chartLayout.marginTop + this._chartLayout.paddingTop
                                    + this._chartLayout.graphHeight + this._chartLayout.paddingBottom 
                                    + this._chartLayout.marginBottom;
    
    // IE and FF cheats!
    if (dojo.isIE) {
      this._chartLayout.marginLeft -= 2;
    } else {
      this._chartLayout.marginTop += 0.5;  // sometimes the top border disapears in FF?
    }
  },
/*************************************************************************************************
 * Get ajax values (also used on the Market overview page                                        *
 *************************************************************************************************/
  
  getAjaxValues: function(lineType, id, newDate) {
    if (this.type == this.statics.ADVANCED) {
      var domain = this.domain;
      this._chartDataUpdater.calculateAndDownloadDataIfNeeded(
        domain, function () {changePeriod(domain);}
      );
    } else {
      this._chartDataUpdater.calculateAndDownloadDataIfNeeded(
        this.domain, null, newDate
      );
    }
  },
  
/*************************************************************************************************
 * Public draw function. Used to draw the chart                                                  *
 *************************************************************************************************/
  draw: function(onlyUpdateIfPossible) {
    if (this.type == this.statics.SIMPLE) {
      // draw simple graph
      
      if (this.values_domain.id && this.values_domain.id.vals) {
        var local_graph_values = this.valuesHelper.getSimpleGraphValuesAndUpdateRange(this.values_domain.id, 
                                                  onlyUpdateIfPossible,this._chartLayout.nonDiagonalShape);
        // to save time, only redraw line (If updating and limits are the same)
        // this is detected by the getSimpleGraphValuesAndUpdateRange
      
        this.chartDrawer.drawSimple(local_graph_values, this.values_domain, local_graph_values.onlyDrawLine);
      }else{
        // no values
        dojo.style("gfxEmpty"+this.key, "display", "");
      }
      
    } else { // ADVANCED
            
      // set the values_displayed depending on the displayedMin and displayedMax percents
      this.values_displayed = this.valuesHelper.cutValuesByPercent(
        this.values_domain, this.showedLines, this.displayedMinPercent, this.displayedMaxPercent
      );
           
      if (this.values_displayed.id.valuesType != swx.inv.Chart.prototype.statics.INTRADAY 
         && this.values_displayed.id.vals.length <= 10) {
         
        // We get here because we are showing historical data of 10 days or less. 
        // We would like to show intraday values
        var newValues = zoomIntoIntradayFromHistorical(onlyUpdateIfPossible);
        if (newValues == null) { 
          return;  // we have to download some, we will come back here
        }
        // Replace the old ones by the new ones
        this.values_displayed = newValues;
      
      } else if (this.values_displayed.id.vals.length >10 
                && this.values_displayed.id.vals.length<=25) {
      
        var newValues = zoomIntoIntermediate(onlyUpdateIfPossible);
        if (newValues == null) { 
          return;  // we have to download some, we will come back here
        }
        // Replace the old ones by the new ones
        this.values_displayed = newValues;
      }
      
      if (this.values_displayed.id.valuesType == swx.inv.Chart.prototype.statics.INTRADAY
          || this.values_displayed.id.valuesType == swx.inv.Chart.prototype.statics.INTERMEDIATE) {
        this.chartDrawer.sliderHelper.setDailyTrackLine();
      } else {
        this.chartDrawer.sliderHelper.setHistoricalTrackLine();
      }
    
      // mix the values_displayed together if we have more than one lin to show
      // this method modifies the values_displayed passed as parameter
      this.valuesHelper.mixValues(this.values_displayed, this.values_domain.id.oldClose);
    
      this.graph_values.id = this.valuesHelper.getAdvancedGraphValues(this.values_displayed.id, false, !onlyUpdateIfPossible, this.values_domain.id.oldClose);
      if (this.showedLines.idx1) { 
        this.graph_values.idx1 = this.valuesHelper.getAdvancedGraphValues(this.values_displayed.idx1, true, false);
      } else {
        this.graph_values.idx1 = null;
      }
      if (this.showedLines.idx2) { 
        this.graph_values.idx2 = this.valuesHelper.getAdvancedGraphValues(this.values_displayed.idx2, true, false);
      } else {
        this.graph_values.idx2 = null;
      }
      for (var i=1; i<=10; i++) {
        if (this.showedLines["ti"+i] && this.values_displayed["ti"+i]) { 
          if (this.values_displayed["ti"+i].isAddon) {
            this.graph_values["ti"+i] = this.valuesHelper.getAdvancedAddonGraphValues(this.showedLines["ti"+i], this.values_displayed["ti"+i]);
          } else {
            this.graph_values["ti"+i] = this.valuesHelper.getAdvancedGraphValues(this.values_displayed["ti"+i], true, false);
          }
        } else {
          this.graph_values["ti"+i] = null;
        }
      }
      
      this.chartDrawer.drawAdvanced(this.graph_values, this.values_displayed);
      if (!onlyUpdateIfPossible) {
        // For the small graph in the slider, we show the whole domain (not only displayMin and displayMax percents)
        var full_domain_graph_values = {};
        full_domain_graph_values.id = this.valuesHelper.getAdvancedGraphValues(this.values_domain.id, false, true, this.values_domain.id.oldClose);
        
        this.chartDrawer.drawSmallChart(full_domain_graph_values);
      }
      this.chartDrawer.drawVolume(this.graph_values.id.points, this.values_displayed.id);
      
      this.chartDrawer.drawEvents(this.values_displayed.id, this.events, this.showedEvents);

    }
    
  },
  
  drawPreview: function() {
    this.values_displayed = this.valuesHelper.cutValuesByPercent(
      this.values_domain, this.showedLines, this.displayedMinPercent, this.displayedMaxPercent, true
    );

    var preview_graph_values = this.valuesHelper.getPreviewGraphValues(this.values_displayed.id);
    
    this.chartDrawer.drawPreview(preview_graph_values);
  },
  
  // Only for simple charts
  changeDomain: function(domain) {
    this.domain = domain;
    this.chartDrawer.changeDomain(domain);
  },
  
  changeId: function(id, newDate) {
    this.id = id;
    this.quotePlayer.clearUpdateEvents(); // stop updates for this chart.
    this.quotePlayer = null; // stop the player for this id. A new one will be made
    this.chartDrawer.clean();
    this._chartDataUpdater.downloadedData["id"] = null;
    this.values_intra["id"] = null;
    this.values_days["id"] = null;
    this.getAjaxValues("id", id, newDate);
  },
  
  /** 
   * Does not really destroy the object, but stops all timers and updates, we can then
   * delete anything related to this chart. 
   */
  destroy: function() {
    if (this.quotePlayer) {
      this.quotePlayer.clearUpdateEvents(); // stop updates for this chart.
      this.quotePlayer = null; // stop the player for this id.
    }
  }
  
});    // end of Chart main class

// Some functions used for small and large charts

  function chartGetNiceDateFromEventDate(eventDate) {
    return chartGetNiceDate(eventDate) + " (" + chartGetNiceHourMinutes(eventDate) + ")";
  };
  
  function chartGetNiceHourMinutes(eventDate) {
    var date = tSd(eventDate);
    var hours = eventDate.getHours();
    if (hours < 10) hours = "0" + hours;
    var minutes = eventDate.getMinutes();
    if (minutes < 10) minutes = "0" + minutes;
    return hours +":"+ minutes;
  };
  
  function chartGetNiceDate(eventDate) {
    var day = eventDate.getDate();
    if (day < 10) day = "0" + day;
    var month = eventDate.getMonth()+1;
    if (month < 10) month = "0" + month;
    return day +"."+ month +"."+ eventDate.getFullYear();
  };
  
  function chartGetNiceNumber(number, fixed) {
    // convert to number, dojo.number.parse does not work as we always have en_us locale for numbers
    // and check if its a valid number
    if (number/1 == NaN)
      return "";
    
    var numberFormatted = null;
    var index = null;
    if (fixed!=null && fixed/1 >= 0) {
      // first make sure that we are operation on Number object
      numberFormatted = ""+(number/1).toFixed(fixed/1);
    } else {
      numberFormatted = number + "";
    }
    index = numberFormatted.indexOf(".");
    if (index == -1) index = numberFormatted.length;

    // format this number to ###'###'##0.00 (dojo uses the locale to set the grouping seperator!)

    while (index > 3) {
      index -= 3;
      numberFormatted = numberFormatted.substring(0, index) + "'" + 
      numberFormatted.substring(index);
    }

    return numberFormatted;
  }

  // These functions handle the seamless transition of the chart over the daylight-saving 
  // transitions.
  function tSd(date) { 
    var x = new Date(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(),
        date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds());
    x.setTime(x.getTime() + dSd(x) + 3600000);
    return x; 
  } 
  
  function dSd(date) {
    return is_dsd(date) ? 3600000 : 0;
  }   
    
  function is_dsd(date) {
    return ((date.getTime() > getDateAtTimePoint(0,2,2,-1).getTime()) &&
            (date.getTime() < getDateAtTimePoint(0,9,2,-1).getTime()));
  }
  
  function getDateAtTimePoint(d, m, h, p) {
    var week = (p < 0) ? 7 * (p + 1) : 7 * (p - 1);
    var nm = (p < 0) ? m + 1 : m;
    var x = new Date(new Date().getUTCFullYear(), nm, 1, h, 0, 0);
    var dOff = 0;
    if (p < 0)
      x.setTime(x.getTime() - 86400000);
    if (x.getDay() != d) {
      dOff = (x.getDay() < d) ? (d - x.getDay()) : 0 - (x.getDay() - d);
      if (p < 0 && dOff > 0)
        week -= 7;
      if (p > 0 && dOff < 0)
        week += 7;
      x.setTime(x.getTime() + ((dOff + week) * 86400000));
    }
    return x;
  }
