/**************************************************************************************************
 * TimeSlider which supports analysis and forecast timesteps at different intervals, enabled and
 * disabled timesteps, timestep-skipping by increments when the number of timesteps is too large,
 * a current time indicator, and controls for moving the start of the timeseries forward or back to
 * shorten or extend the animation period. Also supports basic TimeSlider ops such as dragging,
 * clicking, snapping, and animation of the time slider.
 *
 * Constructor requires:
 * divArg - the div into which this component will render itself. Size may be set in CSS.
 * sliderArg - an image to use for the actual slider
 * nowMarkerArg - an image to use to indicate the current time on the bar
 * enabledTickArg - an image to use for all of the ticks which are enabled (selectable)
 * disabledTickArg - an image to use for all of the ticks which may be disabled (non-selectable)
 * maxTicksArg - the maximum number of enabled ticks or timesteps before the timeseries is broken
 *               into intervals. This is useful to keep a manageable number of animation frames
 *               regardless of the length of the timeseries being animated.
 * skipIncrsArg - an increasing array of integers which could be used as intervals in the timeseries.
 *                The smallest value which will result in a number of enabled ticks less than the
 *                maxTicksArg is used as the tick-skipping interval for the timeseries.
 * forecastIncrArg - The number of artificial timesteps, of the typical timestep duration, needed to
 *                   fill in the time between forecast timesteps. For example, if forecasts are 30min
 *                   apart, but the analysis timesteps are usually 5min apart, the value would be 6.
 */
dojo.require("dojo.style");
function TimeSlider( /* div */ divArg, /* slider image */ sliderArg,
                               /* now marker image */ nowMarkerArg,
                               /* enabled tick image */ enabledTickArg,
                               /* disabled tick image */ disabledTickArg,
                               /* max enabled ticks */ maxTicksArg,
                               /* tick-skipping increments */ skipIncrsArg,
                               /* enabled forecast tick increment */ forecastIncrArg ) {
  var div = divArg;
  div.style.height = "62px";
  var ticks;
  var visibleTickDivs = new Array();
  this.currTickIndex;
  var nowTickIndex, startTickIndex;
  var slider = sliderArg;
  slider.style.position = "absolute";
  slider.style.zIndex = "2";
  dojo.event.connect(slider, "onmousedown", this, "startMove");
  dojo.event.connect(div, "onmousemove", this, "processMove");
  dojo.event.connect(div, "onmouseup", this, "stopMove");
  // dojo.event.connect(div, "onmouseout", this, "stopMove");
  var dragging = false;
  var dragStartClick, dragStartLeft;
  var enabledTick = enabledTickArg;
  var disabledTick = disabledTickArg;
  var maxTicks = maxTicksArg;
  var skipIncrs = skipIncrsArg;
  var skipInterval;
  var forecastIncr = forecastIncrArg;
  var startMilliseconds;
  var pixelsPerMillisecond;
  var divOffsetLeft;
  var divOffsetTop;
  var value = 0;

  var leftBound = 40;
  var rightBound = 40;
  var self = this;

  // put the "now" marker in the div
  var nowMarker = nowMarkerArg;
  nowMarker.style.position = "absolute";
  div.appendChild( nowMarker );

  /**
   * Public accessor for the private startTickIndex property.
   */
  this.getStartTickIndex = function() {
    return startTickIndex;
  }

  /**
   * Public accessor for the private skipInterval property.
   */
  this.getSkipInterval = function() {
    return skipInterval;
  }

  /**
   * (Re)initializes the time slider from the given times array and now index. This includes:
   * - Computing the start time, if not already known
   * - Computing the skip interval, so as to limit the number of analysis timesteps to the max ticks
   * - Creating tick marks for all enabled timesteps (and disabled forecast timesteps)
   * - Creating "grooves" to link disabled forecast timesteps
   * - Wiring up events for dragging the slider and clicking on tick marks
   *
   * ticksArg - the new Array of times at which ticks should be exist
   * nowTickArg - the integer index of the tick in the ticksArg Array which corresponds to the boundary
   *              between analysis and forecast ticks
   */
  this.reInit = function( /* times Array */ ticksArg, /* int */ nowTickArg ) {
    ticks = ticksArg;
    nowTickIndex = nowTickArg;

    // Clear the old slider
    for( var i = 0; i < visibleTickDivs.length; i++ ) {
      div.removeChild( visibleTickDivs[i] );
    }
    visibleTickDivs = new Array();

    // If it isn't already set, figure out the tick skip interval
    if( ! skipInterval ) {
      startTickIndex = Math.max(0, nowTickIndex - maxTicks);
      calcSkipInterval();
      if( (nowTickIndex-startTickIndex) % skipInterval != 0 ) {
        startTickIndex = startTickIndex - skipInterval + (nowTickIndex-startTickIndex) % skipInterval;
      }
    }

    // Create new objects for each tick
    var sliderWidth = dojo.style.getContentBoxWidth(div.id);
    divOffsetLeft = dojo.style.getAbsoluteX(div.id, true);
    divOffsetTop = dojo.style.getAbsoluteY(div.id, true);
    startMilliseconds = ticks[ startTickIndex ].getTime();
    pixelsPerMillisecond = ( sliderWidth - leftBound - rightBound) /
                    (ticks[ ticks.length-1 ].getTime() - startMilliseconds );
    var tickDate;
    var lastGrooveIndex = nowTickIndex;
    for( var i = startTickIndex; i < ticks.length; i += skipInterval ) {
      var xOfCenter = divOffsetLeft + leftBound + Math.round( (ticks[i].getTime() - startMilliseconds) * pixelsPerMillisecond );

      // Create the div into which to put the tick mark and the label, add it to our list, and
      // add it to the component div, so subelements will be sized upon insertion and can be layed out.
      var tickDiv = document.createElement("div");
      div.appendChild( tickDiv );
      visibleTickDivs[ visibleTickDivs.length ] = tickDiv;

      // Create the tick mark
      var tick;
      if( i == nowTickIndex || ( ( Math.abs(nowTickIndex - i) ) % skipInterval == 0 ) && ( i < nowTickIndex || (i-nowTickIndex)%forecastIncr == 0 ) ) {
        tick = enabledTick.cloneNode(true);
        dojo.event.connect(tick, "onclick", this, "tickClicked");
        tick.title = ticks[i].getUTCHours() + ":" + ((ticks[i].getUTCMinutes()<10)? "0" + ticks[i].getUTCMinutes(): ticks[i].getUTCMinutes());
      } else {
        tick = disabledTick.cloneNode(true);
        tick.title = "Artificial Timestep"
      }
      tick.id = "tick" + i;
      tick.style.visibility = "visible";
      tick.style.position = "absolute";
      tick.style.zIndex = "1";
      tickDiv.appendChild( tick );
      tick.style.left =  xOfCenter - Math.round( tick.width / 2) + "px";
      tick.style.top = divOffsetTop + 12 + "px";

      // Create the tick labels for the first, now, and forecast times
      if( i == startTickIndex || i == nowTickIndex ||
          ( i > nowTickIndex && (i - nowTickIndex) % forecastIncr == 0 ) ) {
        var tickLabel = document.createElement("div");
        tickLabel.className = "tickLabel";
        tickLabel.id = "tickLabel" + i;
        tickLabel.style.position = "absolute";
        tickLabel.style.top = divOffsetTop + 34 + "px";
        if( i <= nowTickIndex ) {
          var labelText = ticks[i].getUTCHours() + ":" + ((ticks[i].getUTCMinutes()<10)? "0" + ticks[i].getUTCMinutes(): ticks[i].getUTCMinutes());
          if( ! tickDate || tickDate != ticks[i].getUTCDate() ) {
            tickDate = (ticks[i].getUTCMonth() + 1) + "/" + ticks[i].getUTCDate();
            labelText += "<BR> " + tickDate;
          }
          tickLabel.innerHTML = labelText;
          tickDiv.appendChild(tickLabel);
          tickLabel.style.left = xOfCenter - tickLabel.offsetWidth / 2 + "px";
        } else {
          // See if there is enough space for the forecast tick labels
          // 120 minutes * pixelsPerMilliseconds < min pixel width required
          if( 7200000 * pixelsPerMillisecond > 250 ) {
            var fcstMins = (ticks[i].getTime() - ticks[nowTickIndex].getTime()) / 60000;
            tickLabel.innerHTML = "+" + fcstMins + " min";
            tickDiv.appendChild(tickLabel);
            tickLabel.style.left = xOfCenter - tickLabel.offsetWidth + "px";
          }
          // Create a background "groove" to tie together the artificial forecast ticks
          var lastMillisLeft = divOffsetLeft + leftBound +
                               Math.round((ticks[lastGrooveIndex + 1].getTime() -
                                           startMilliseconds) * pixelsPerMillisecond);
          var timeBarGroove = document.createElement("div");
          timeBarGroove.appendChild( document.createComment( " IE bug hack " ) );
          timeBarGroove.style.position = "absolute";
          timeBarGroove.style.top = divOffsetTop + 11 + tick.offsetHeight / 2 + "px";
          timeBarGroove.style.left = lastMillisLeft + "px";
          timeBarGroove.style.width = xOfCenter - lastMillisLeft + "px";
          timeBarGroove.style.zIndex = "0";
          timeBarGroove.className = "forecastTimeBarGroove";
          div.appendChild(timeBarGroove);
          timeBarGroove.style.top = stripPx( timeBarGroove.style.top ) - timeBarGroove.offsetHeight / 2 + "px";
          visibleTickDivs[ visibleTickDivs.length ] = timeBarGroove;
          lastGrooveIndex = i;
        }
      }

      // If this is the now tick, set the location of the "now" marker
      if( i == nowTickIndex ) {
        var xOfNow = divOffsetLeft + leftBound + Math.round( (new Date().getTime() - startMilliseconds) * pixelsPerMillisecond );
        nowMarker.style.left = xOfNow - Math.round( nowMarker.width/2 ) + "px";
        nowMarker.style.top = divOffsetTop + "px";
      }
    }
  }

  /**
   * Calculates the number of ticks to skip, if any, to yield a number of enabled ticks less than
   * the maxTicks. The value must be the smallest of the skipIncrs provided in the constructor.
   */
  function calcSkipInterval() {
    for( var i = 0; i < skipIncrs.length; i++ ) {
      if( (nowTickIndex - startTickIndex) / skipIncrs[i] <= maxTicks ) {
        skipInterval = skipIncrs[i];
        break;
      }
    }
  }

  /**
   * Draws the main slider over the tick with the given index.
   *
   * index - the index of the time over which to draw the main slider
   */
  this.setCurrentIndex = function( index ) {
    // alert("setting index to " + index );
    this.currTickIndex = index;
    slider.style.left = divOffsetLeft + leftBound +
                        Math.round( ( ticks[index].getTime() - startMilliseconds) * pixelsPerMillisecond -
                                   slider.width / 2) + "px";
    slider.style.top = divOffsetTop + 10 + "px";
    slider.title = dojo.byId("tick"+index).title;
  }

  /**
   * Moves the starting tick forwards or backwards in time by the given number of ticks. This causes
   * a reinitialization of the time bar and potentially changes the tick interval.
   *
   * index - a positive of negative number of ticks to shift the start of the timeseries. If the start
   *         is already at the first tick negative shifts will be ignored. If the start is already at
   *         the now tick, positive shifts will be ignored
   */
  this.shiftStartTick = function( /* number of ticks */ index ) {
    var newStartTickIndex = startTickIndex + index;
    if( newStartTickIndex > 0 && newStartTickIndex < nowTickIndex ) {
      startTickIndex = newStartTickIndex;
      calcSkipInterval();
      while( (nowTickIndex-startTickIndex) % skipInterval != 0 && startTickIndex > 0 ) {
        if( index > 0 ) {
          startTickIndex++;
        } else {
          startTickIndex--
        }
        calcSkipInterval();
      }
      if( startTickIndex == 0 ) {
        startTickIndex += (nowTickIndex-startTickIndex) % skipInterval;
      }
      this.reInit( ticks, nowTickIndex );
      this.setCurrentIndex( this.currTickIndex );
    }
  }

  /**
   * Handles the cases where the user clicks on one of the tick marks. Determines which tick was
   * clicked and sets the current time slider to that tick.
   *
   * event - the mouseClicked event which triggered this call
   */
  this.tickClicked = function( event ) {
    this.setCurrentIndex( parseInt( event.currentTarget.id.substring(4), 10 ) );
    dojo.event.browser.stopEvent(event);
  }

  /**
   * Handles an onmousepressed event on the slider by storing its initial mouse position and setting
   * the "dragging" flag.
   *
   * event - the onmousepressed event which triggered this call
   */
  this.startMove = function( event ) {
    // alert("started move" );
    if( dragging ) {
      return this.processMove( event );
    }
    dojo.event.browser.stopEvent(event);
    dragStartClick = "" + event.clientX;
    dragStartLeft = stripPx( slider.style.left );
    div.style.cursor = "pointer";

    dragging = true;
    return false;
  }

  /**
   * Handles an onmousedragged event in the map by moving the innerDiv by the distance between
   * the current mouse position and the original mouse position (at the start of the drag). This
   * could be done on-the-fly during dragging but doesn't seem worth the performance hit.
   *
   * event - the onmousedragged event which triggered this call
   */
  this.processMove = function( event ) {
    // alert("moving" );
    if( dragging ) {
      dojo.event.browser.stopEvent(event);
      slider.style.left = dragStartLeft + (event.clientX - dragStartClick) + "px";
      return false;
    }
  }

  /**
   * Handles an onmousereleased event in the map by unsetting the dragging flag and asking the
   * layers to load any new tiles they might need for future drags.
   *
   * event - the onmousereleased event which triggered this call
   */
  this.stopMove = function( event ) {
    if( dragging ) {
      this.processMove( event );
      dojo.event.browser.stopEvent(event);
      div.style.cursor = "";
      dragging = false;
      var millis = startMilliseconds + ( stripPx(slider.style.left) + slider.width / 2 - divOffsetLeft - leftBound ) / pixelsPerMillisecond;
      for( var i = startTickIndex; i < ticks.length-skipInterval; i += skipInterval ) {
        if( Math.abs( millis - ticks[i].getTime() ) < Math.abs( millis - ticks[i+skipInterval].getTime() )) {
          this.setCurrentIndex(i);
          return false;
        }
      }
      this.setCurrentIndex( ticks.length-1 );
      return false;
    }
  }
}