// Copyright (c) 2008, 2012, Oracle and/or its affiliates. 
// All rights reserved. 

/**
 * Abstract Base Class for Gauge component.
 * @class
 * @constructor
 * @extends {DvtContainer}
 */
var DvtGauge = function() {}

DvtObj.createSubclass(DvtGauge, DvtContainer, "DvtGauge");

/**
 * Initializes the component.
 * @param {DvtContext} context The rendering context.
 * @param {string} callback The function that should be called to dispatch component events.
 * @param {object} callbackObj The optional object instance on which the callback function is defined.
 * @param {object} options The object containing options specifications for this component.
 * @protected
 */
DvtGauge.prototype.Init = function(context, callback, callbackObj, options) {
  DvtGauge.superclass.Init.call(this, context);
  this._callback = callback;
  this._callbackObj = callbackObj;
  this.setOptions(options);
  
  // Create the resource bundle
  // TODO pass in the locale
  this.Bundle = new DvtGaugeBundle();
  
  // Create the event handler and add event listeners
  this._eventHandler = new DvtEventManager(context);
  this._eventHandler.addListeners(this);
  
  // Make sure the object has an id for clipRect naming
  this.setId("gauge" + 1000 + Math.floor(Math.random()*10000));
}

/**
 * Specifies the non-data options for this component.
 * @param {object} options The object containing options specifications for this component.
 * @protected
 */
DvtGauge.prototype.setOptions = function(options) {
  this.Options = options; // subclasses should override to combine with the defaults.
}

/**
 * Renders the component with the specified data.  If no data is supplied to a component
 * that has already been rendered, the component will be rerendered to the specified size.
 * @param {object} data The object containing data for this component.
 * @param {number} width The width of the component.
 * @param {number} height The height of the component.
 */
DvtGauge.prototype.render = function(data, width, height) 
{  
  // Update if new data has been provided. Clone to avoid modifying the provided object.
  this.Data = data ? DvtJSONUtils.clone(data) : this.Data; 

  var oldShapes = this.__shapes;
  this.__shapes = [];
  
  // Render the gauge
  var container = new DvtContainer(this.getContext());
  this.addChild(container);
  this.Render(container, width, height);
  
  this._setAnimation(container, data, oldShapes, width, height);
  
  // Queue a render with the context
  this.getContext().queueRender();
  
}

/**
 * Renders the component at the specified size.
 * @param {DvtContainer} container The container to render within.
 * @param {number} width The width of the component.
 * @param {number} height The height of the component.
 */
DvtGauge.prototype.Render = function(container, width, height) 
{  
  // subclasses should override
}

/**
 * Checks animation settings for the gauge and creates and plays animation on display
 * or animation on data change.
 * @param {DvtContainer} container The container to render within.
 * @param {object} data The object containing data for this component.
 * @param {Array} oldShapes The array of DvtShapes that can be animated
 * @param {number} width The width of the component.
 * @param {number} height The height of the component.
 */
DvtGauge.prototype._setAnimation = function(container, data, oldShapes, width, height) {
  // Stop any animation in progress before starting new animation
  if(this._animation) {
    this._animationStopped = true;  // TODO Rename
    this._animation.stop();
  }
  
  var bBlackBoxUpdate = false;
  var animationOnDataChange = this.Options.animationOnDataChange;
  var animationOnDisplay = this.Options.animationOnDisplay;
  var animationDuration = this.Options.animationDuration/1000;
  
  if (!animationOnDisplay && !animationOnDataChange)
    return;
  
  var bounds = new DvtRectangle(0, 0, width, height);
  var context = this.getContext();
  
  if (!this._container && animationOnDisplay !== "none") {
       this._animation = DvtBlackBoxAnimationHandler.getInAnimation(context, animationOnDisplay, container,
                                                                  bounds, animationDuration);
                                                                  
      if (!this._animation)
         this._animation = this.CreateAnimationOnDisplay(this.__shapes, animationOnDisplay, animationDuration);
  }
  else if (animationOnDataChange != "none" && data) {
    this._animation = DvtBlackBoxAnimationHandler.getCombinedAnimation(context, animationOnDataChange, 
                                                                      this._container, container, bounds, animationDuration);   
    if (this._animation)
      bBlackBoxUpdate = true;
    else
      this._animation = this.CreateAnimationOnDataChange(oldShapes, this.__shapes, animationOnDisplay, animationDuration);
  }
  
  if(this._animation) {  
    if (!bBlackBoxUpdate)
      this.removeChild(this._container);
    this._animation.play();  
    this._animation.setOnEnd(this._onAnimationEnd, this);
  }
  
  if(bBlackBoxUpdate)
    this._oldContainer = this._container;
  
  this._container = container;
}

/**
  * Creates a DvtPlayable that performs animation upon inital gauge display.
  * @param {Array} objs The array of DvtShapes to animate to.
  * @param {string} animationType The animation type.
  * @param {number} animationDuration The duration of the animation in seconds.
  * @return {DvtPlayable}
  * @protected
  */
DvtGauge.prototype.CreateAnimationOnDisplay = function(objs, animationType, animationDuration) {
  // subclasses may implement
  return null;
}

/**
  * Creates a DvtPlayable that performs animation for a gauge update.
  * @param {Array} oldObjs The array of DvtShapes to animate from.
  * @param {Array} newObjs The array of DvtShapes to animate to.
  * @param {string} animationType The animation type.
  * @param {number} animationDuration The duration of the animation in seconds.
  * @return {DvtPlayable}
  * @protected
  */
DvtGauge.prototype.CreateAnimationOnDataChange = function(oldObjs, newObjs, animationType, animationDuration) {
  // subclasses may implement
  return null;
}

/**
 * Cleans up the old container used by black box updates.
 * @private
 */
DvtGauge.prototype._onAnimationEnd = function() {
  if(this._oldContainer) {
    this.removeChild(this._oldContainer);
    this._oldContainer = null;
  }
  
  // Reset the animation flag and reference
  this._animationStopped = false;
  this._animation = null;
}

/**
 * Returns the resource bundle for this component.
 * @return {DvtGaugeBundle}
 */
DvtGauge.prototype.__getBundle = function() {
  return this.Bundle;
}

/**
 * Returns the data object for the component.
 * @return {object} The object containing data for this component.
 */
DvtGauge.prototype.__getData = function() {
  return this.Data ? this.Data : {};
}

/**
 * Returns the evaluated options object, which contains the user specifications
 * merged with the defaults.
 * @return {object} The options object.
 */
DvtGauge.prototype.__getOptions = function() {
  return this.Options;
}

/**
 * Returns the DvtEventManager for this component.
 * @return {DvtEventManager}
 */
DvtGauge.prototype.__getEventManager = function() {
  return this._eventHandler;
}
/**
 * Resource bundle for DvtGauge.
 * @class
 * @constructor
 * @extends {DvtBundle}
 */
var DvtGaugeBundle = function() {}

DvtObj.createSubclass(DvtGaugeBundle, DvtBundle, "DvtGaugeBundle");

DvtGaugeBundle.EMPTY_TEXT = "No data to display";
/**
 * Default values and utility functions for component versioning.
 * @class
 */
var DvtGaugeDefaults = new Object();

DvtObj.createSubclass(DvtGaugeDefaults, DvtObj, "DvtGaugeDefaults");

/**
 * Defaults for version 1.
 */ 
DvtGaugeDefaults.VERSION_1 = {
  color: "#313842", borderColor: null, visualEffects: "auto", emptyText: null,
  labelDisplay: "off", labelStyle: "font-size: 13px; color: #42415A;",
  animationOnDataChange: "none", animationOnDisplay: "none", animationDuration: 1000,
  
  // Internal layout constants
  __layout: {outerGap: 1, labelGap: 3}
};

/**
 * Returns the default options object for the specified version of the component.
 * @param {object} userOptions The object containing options specifications for this component.
 * @return {object} The default options object for the component.
 */
DvtGaugeDefaults.getDefaults = function(userOptions) {
  // Note: Version checking will eventually get added here
  // Note: Future defaults objects are deltas on top of previous objects
  return DvtJSONUtils.clone(DvtGaugeDefaults.VERSION_1);
};
/**
 * Style related utility functions for gauge components.
 * @class
 */
var DvtGaugeDataUtils = new Object();

DvtObj.createSubclass(DvtGaugeDataUtils, DvtObj, "DvtGaugeDataUtils");

/**
 * Returns true if the specified chart has data.
 * @param {DvtGauge} gauge
 * @return {boolean}
 */
DvtGaugeDataUtils.hasData = function(gauge) {
  var data = gauge.__getData();
  
  // Check that there is a data object with a valid value
  if(!data || isNaN(data.value) || data.value === null)
    return false;
  else
    return true;
}

/**
 * Returns the index of the threshold corresponding to the gauge value.
 * @param {DvtGauge}
 * @return {number} The index of the threshold definition or null if none is available.
 */
DvtGaugeDataUtils.getValueThresholdIndex = function(gauge) {
  var data = gauge.__getData();
  var value = data.value;
  var thresholds = data ? data.thresholds : null;
  
  // Return -1 if no thresholds exist
  if(!thresholds)
    return -1;
  
  // Loop through and find the threshold
  for(var i=0; i<thresholds.length; i++) {
    if(value <= thresholds[i].maxValue)
      return i;
  }

  // None found, but thresholds exist, this means the last threshold
  return thresholds.length-1;
}

/**
 * Returns the specified threshold.
 * @param {DvtGauge}
 * @param {number} index The index of the threshold.
 * @return {object} The threshold definition or null if none is available.
 */
DvtGaugeDataUtils.getThreshold = function(gauge, index) {
  var data = gauge.__getData();
  var thresholds = data ? data.thresholds : null;

  if(index >= 0 && index < thresholds.length)
    return thresholds[index];
  else
    return null;
}
/**
 * Style related utility functions for gauge components.
 * @class
 */
var DvtGaugeStyleUtils = new Object();

DvtObj.createSubclass(DvtGaugeStyleUtils, DvtObj, "DvtGaugeStyleUtils");

/** @private */
DvtGaugeStyleUtils._THRESHOLD_COLOR_RAMP = ["#D62800", "#FFCF21", "#84AE31"];

/**
 * Returns the color, taking into account the thresholds if specified.
 * @param {DvtGauge} gauge
 * @return {string} The color of the gauge.
 */
DvtGaugeStyleUtils.getColor = function(gauge) {
  // Thresholds
  var thresholdIndex = DvtGaugeDataUtils.getValueThresholdIndex(gauge);
  var threshold = DvtGaugeDataUtils.getThreshold(gauge, thresholdIndex);
  if(threshold) {
    // Color is either directly specified or comes from the color ramp
    if(threshold.color)
      return threshold.color;
    else if(thresholdIndex < DvtGaugeStyleUtils._THRESHOLD_COLOR_RAMP.length)
      return DvtGaugeStyleUtils._THRESHOLD_COLOR_RAMP[thresholdIndex];
  }
  
  // Options Object
  var options = gauge.__getOptions();
  return options.color;  
}

/**
 * Returns the border color, taking into account the thresholds if specified.
 * @param {DvtGauge} gauge
 * @return {string} The border color of the gauge.
 */
DvtGaugeStyleUtils.getBorderColor = function(gauge) {
  // Thresholds
  var thresholdIndex = DvtGaugeDataUtils.getValueThresholdIndex(gauge);
  var threshold = DvtGaugeDataUtils.getThreshold(gauge, thresholdIndex);
  if(threshold && threshold.borderColor)
    return threshold.borderColor;
  
  // Options Object
  var options = gauge.__getOptions();
  return options.borderColor;  
}
/**
 * Renderer for DvtGauge.
 * @class
 */
var DvtGaugeRenderer = new Object();

DvtObj.createSubclass(DvtGaugeRenderer, DvtObj, "DvtGaugeRenderer");

/** @private */
DvtGaugeRenderer._EMPTY_TEXT_GAP_WIDTH = 2;
DvtGaugeRenderer._EMPTY_TEXT_GAP_HEIGHT = 1;

/**
 * Renders the empty text for the component. 
 * @param {DvtGauge} gauge The gauge being rendered.
 * @param {DvtContainer} container The container to render into.
 * @param {DvtRectangle} availSpace The available space.
 */
DvtGaugeRenderer.renderEmptyText = function(gauge, container, availSpace) {
  // Get the empty text string
  var options = gauge.__getOptions();
  var emptyTextStr = options.emptyText;
  if(!emptyTextStr) {
    emptyTextStr = gauge.__getBundle().getRBString(DvtGaugeBundle.EMPTY_TEXT, null, 'DvtChartBundle.EMPTY_TEXT');
  }
  // Calculate the alignment point and available space
  var x = availSpace.x + availSpace.w/2;
  var y = availSpace.y + availSpace.h/2;
  var width = availSpace.w - 2*DvtGaugeRenderer._EMPTY_TEXT_GAP_WIDTH;
  var height = availSpace.h - 2*DvtGaugeRenderer._EMPTY_TEXT_GAP_HEIGHT;

  // Create and position the text
  var text = new DvtText(gauge.getContext(), emptyTextStr, x, y);
  text.setCSSStyle(new DvtCSSStyle(options.labelStyle));
  text.alignMiddle();
  text.alignCenter();
  text = text.truncateToSpace(container, width, height);
  container.addChild(text);
};

// Copyright (c) 2008, 2012, Oracle and/or its affiliates. 
// All rights reserved. 

/**
 * LED Gauge component.  This class should never be instantiated directly.  Use the
 * newInstance function instead.
 * @class
 * @constructor
 * @extends {DvtGauge}
 */
var DvtLedGauge = function() {}

// TODO move to ADF

DvtObj.createSubclass(DvtLedGauge, DvtGauge, "DvtLedGauge");

/**
 * Returns a new instance of DvtLedGauge.
 * @param {DvtContext} context The rendering context.
 * @param {string} callback The function that should be called to dispatch component events.
 * @param {object} callbackObj The optional object instance on which the callback function is defined.
 * @param {object} options The object containing options specifications for this component.
 * @return {DvtLedGauge}
 */
DvtLedGauge.newInstance = function(context, callback, callbackObj, options) {
  var gauge = new DvtLedGauge();
  gauge.Init(context, callback, callbackObj, options);
  return gauge;
}

/**
 * @override
 */
DvtLedGauge.prototype.setOptions = function(options) {
  // Combine the user options with the defaults and store
  DvtLedGauge.superclass.setOptions.call(this, DvtLedGaugeDefaults.calcOptions(options));
}

/**
 * @override
 */
DvtLedGauge.prototype.Render = function(container, width, height) 
{  
  DvtLedGaugeRenderer.render(this, container, width, height);
}
/**
 * Default values and utility functions for component versioning.
 * @class
 */
var DvtLedGaugeDefaults = new Object();

DvtObj.createSubclass(DvtLedGaugeDefaults, DvtObj, "DvtLedGaugeDefaults");

/**
 * Defaults for version 1.
 */ 
DvtLedGaugeDefaults.VERSION_1 = {
  type: "circle"
};

/**
 * Combines the user options with the defaults for the specified version.  Returns the
 * combined options object.  This object will contain internal attribute values and
 * should be accessed in internal code only.
 * @param {object} userOptions The object containing options specifications for this component.
 * @return {object} The combined options object.
 */
DvtLedGaugeDefaults.calcOptions = function(userOptions) {
  var defaults = DvtLedGaugeDefaults._getDefaults(userOptions);

  // Use defaults if no overrides specified
  if(!userOptions)
    return defaults;
  else // Merge the options object with the defaults
    return DvtJSONUtils.merge(userOptions, defaults);
}

/**
 * Returns the default options object for the specified version of the component.
 * @param {object} userOptions The object containing options specifications for this component.
 * @return {object} The default options object for the component.
 * @private
 */
DvtLedGaugeDefaults._getDefaults = function(userOptions) {
  // Note: Version checking will eventually get added here
  // Note: Future defaults objects are deltas on top of previous objects
  
  // The base class defaults are combined with the defaults from this class
  var baseDefaults = DvtGaugeDefaults.getDefaults(userOptions);
  var defaults = DvtJSONUtils.clone(DvtLedGaugeDefaults.VERSION_1)
  return DvtJSONUtils.merge(defaults, baseDefaults);
}
/**
 * Renderer for DvtLedGauge.
 * @class
 */
var DvtLedGaugeRenderer = new Object();

DvtObj.createSubclass(DvtLedGaugeRenderer, DvtObj, "DvtLedGaugeRenderer");

/** @private **/
DvtLedGaugeRenderer.__RECTANGLE_CORNER_RADIUS = 1/6;

/**
 * Renders the gauge in the specified area.
 * @param {DvtLedGauge} gauge The gauge being rendered.
 * @param {DvtContainer} container The container to render into.
 * @param {number} width The width of the component.
 * @param {number} height The height of the component.
 */
DvtLedGaugeRenderer.render = function(gauge, container, width, height) {
  if(DvtGaugeDataUtils.hasData(gauge)) {
    // Allocate the outer gap for the component
    var options = gauge.__getOptions();
    var outerGap = options.__layout.outerGap;
    var bounds = new DvtRectangle(outerGap, outerGap, width - 2*outerGap, height - 2*outerGap);
    
    // Render the LED shape first
    DvtLedGaugeRenderer._renderShape(gauge, container, bounds);
    
    // Render the visual effects
    DvtLedGaugeRenderer._renderVisualEffects(gauge, container, bounds);
    
    // Render the label on top of the LED
    DvtLedGaugeRenderer._renderLabel(gauge, container, bounds);
  }
  else // Render the empty text
    DvtGaugeRenderer.renderEmptyText(gauge, container, new DvtRectangle(0, 0, width, height));
}

/**
 * Renders the led shape into the specified area.
 * @param {DvtLedGauge} gauge The gauge being rendered.
 * @param {DvtContainer} container The container to render into.
 * @param {DvtRectangle} bounds The available bounds for rendering.
 */
DvtLedGaugeRenderer._renderShape = function(gauge, container, bounds) {
  var data = gauge.__getData();
  var options = gauge.__getOptions();
  
  // Find the styles
  var type = options.type;
  var color = DvtGaugeStyleUtils.getColor(gauge);
  var borderColor = DvtGaugeStyleUtils.getBorderColor(gauge);
  
  // Render the LED shape
  var shape;
  if(type == "rectangle") {
    shape = new DvtRect(gauge.getContext(), bounds.x, bounds.y, bounds.w, bounds.h);
  }
  else { // circle
    var cx = bounds.x + bounds.w/2;
    var cy = bounds.y + bounds.h/2;
    var radius = Math.min(bounds.w, bounds.h)/2;
    shape = new DvtCircle(gauge.getContext(), cx, cy, radius);
  }
  
  gauge.__shapes.push(shape);
  
  // Apply the style properties
  shape.setFill(new DvtSolidFill(color));
  if(borderColor)
    shape.setStroke(new DvtSolidStroke(borderColor));
    
  // Tooltip Support
  if(data.tooltip)
    gauge.__getEventManager().associate(shape, new DvtSimpleObjPeer(null, data.tooltip, color));
  
  // Add the shape to the container
  container.addChild(shape);
}

/**
 * Renders the visual effects for the shape into the specified area.
 * @param {DvtLedGauge} gauge The gauge being rendered.
 * @param {DvtContainer} container The container to render into.
 * @param {DvtRectangle} bounds The bounds of the shape.
 */
DvtLedGaugeRenderer._renderVisualEffects = function(gauge, container, bounds) {
  var options = gauge.__getOptions();
  var type = options.type;
  
  if(options.visualEffects == "none")
    return;

  // Render the visual effects
  if(type == "rectangle") 
    DvtLedGaugeRenderer._renderOverlayRectangle(gauge, container, bounds);
  else 
    DvtLedGaugeRenderer._renderOverlayCircle(gauge, container, bounds);
}

/**
 * Renders the visual effects for the rectangle LED into the specified area.
 * @param {DvtLedGauge} gauge The gauge being rendered.
 * @param {DvtContainer} container The container to render into.
 * @param {DvtRectangle} bounds The bounds of the shape.
 */
DvtLedGaugeRenderer._renderOverlayRectangle = function(gauge, container, bounds) {
  // Overlay Shape
  var dx = bounds.w * 0.05;
  var dy = bounds.h * 0.05;
  var overlayBounds = new DvtRectangle(bounds.x + dx, bounds.y + dy, bounds.w - 2*dx, bounds.h - 2*dy);
  var overlay = new DvtRect(gauge.getContext(), overlayBounds.x, overlayBounds.y, overlayBounds.w, overlayBounds.h);
  overlay.setMouseEnabled(false);
  container.addChild(overlay);
  
  // Gradient
  var arColors = ["#FFFFFF", "#FFFFFF", "#FFFFFF", "#FFFFFF", "#FFFFFF", "#FFFFFF", "#FFFFFF"];
  var arAlphas = [0.75, 0.5, 0.15, 0, 0.2, 0.4, 0.2];
  var arStops = [0, 0.05, 0.4, 0.6, 0.8, 0.9, 1.0];
  var gradient = new DvtLinearGradientFill(270, arColors, arAlphas, arStops);
  overlay.setFill(gradient);
  gauge.__shapes.push(overlay);
}

/**
 * Renders the visual effects for the circle LED into the specified area.
 * @param {DvtLedGauge} gauge The gauge being rendered.
 * @param {DvtContainer} container The container to render into.
 * @param {DvtRectangle} bounds The bounds of the shape.
 */
DvtLedGaugeRenderer._renderOverlayCircle = function(gauge, container, bounds) {
  // The circle uses two overlays.
  var dx = bounds.w * 0.05;
  var dy = bounds.h * 0.05;
  var gradientBounds = new DvtRectangle(bounds.x + dx, bounds.y + dy, bounds.w - 2*dx, bounds.h - 2*dy);

  //********************* First Overlay: "Overlay" ************************/
 
  // Shape
  var cx = gradientBounds.x + gradientBounds.w/2;
  var cy = gradientBounds.y + gradientBounds.h/2;
  var radius = Math.min(gradientBounds.w, gradientBounds.h)/2;
  var overlay = new DvtCircle(gauge.getContext(), cx, cy, radius);
  overlay.setMouseEnabled(false);
  container.addChild(overlay);
  
  // Gradient
  var arColors = ["#FFFFFF", "#FFFFFF", "#FFFFFF"];
  var arAlphas = [0, 0.25, 0.5];
  var arStops = [0.15, 0.7, 0.95];
  var gradientCx = cx;
  var gradientCy = cy - radius * 0.6; // per UX
  var gradientRadius = radius * 1.5;
  var gradient = new DvtRadialGradientFill(arColors, arAlphas, arStops, gradientCx, gradientCy, gradientRadius,
                                                                                    [gradientBounds.x, gradientBounds.y,
                                                                                     gradientBounds.w, gradientBounds.h]);
  overlay.setFill(gradient);
  
  //********************* Second Overlay: "Highlight" ************************/
  
  // Shape
  var rx = radius * 0.6; // per UX
  var ry = radius * 0.4; // per UX
  cy = cy - 0.3*ry; // per UX
  var highlight = new DvtOval(gauge.getContext(), cx, cy-ry, rx, ry);
  highlight.setMouseEnabled(false);
  container.addChild(highlight);
  
  // Gradient
  var highlightColors = ["#FFFFFF", "#FFFFFF", "#FFFFFF"];
  var highlightAlphas = [0, 0.2, 0.5];
  var highlightStops = [0.6, 0.8, 1.0];
  var highlightCx = cx;
  var highlightCy = cy;
  var highlightRadius = rx;
  var highlightGradient = new DvtRadialGradientFill(highlightColors, highlightAlphas, highlightStops, highlightCx, highlightCy, highlightRadius,
                                                                                    [gradientBounds.x, gradientBounds.y,
                                                                                     gradientBounds.w, gradientBounds.h]);
  highlight.setFill(highlightGradient);
}

/**
 * Renders the label into the specified area.
 * @param {DvtLedGauge} gauge The gauge being rendered.
 * @param {DvtContainer} container The container to render into.
 * @param {DvtRectangle} bounds The available bounds for rendering.
 */
DvtLedGaugeRenderer._renderLabel = function(gauge, container, bounds) {
  var data = gauge.__getData();
  var options = gauge.__getOptions();

  // Create and position the label
  if(options.labelDisplay == "on") {
    var label = new DvtText(gauge.getContext(), data.value, bounds.x + bounds.w/2, bounds.y + bounds.h/2);
    label.setCSSStyle(new DvtCSSStyle(options.labelStyle));
    label.alignCenter();
    label.alignMiddle();
    
    // Truncate if needed, null is returned if the label doesn't fit
    label = label.truncateToSpace(container, bounds.w, bounds.h);
    if(label)
      container.addChild(label);
  }
}
// Copyright (c) 2008, 2012, Oracle and/or its affiliates. 
// All rights reserved. 

/**
 * Status Meter Gauge component.  This class should never be instantiated directly.  Use the
 * newInstance function instead.
 * @class
 * @constructor
 * @extends {DvtGauge}
 */
var DvtStatusMeterGauge = function() {}

// TODO move to ADF

DvtObj.createSubclass(DvtStatusMeterGauge, DvtGauge, "DvtStatusMeterGauge");

/**
 * Returns a new instance of DvtStatusMeterGauge.
 * @param {DvtContext} context The rendering context.
 * @param {string} callback The function that should be called to dispatch component events.
 * @param {object} callbackObj The optional object instance on which the callback function is defined.
 * @param {object} options The object containing options specifications for this component.
 * @return {DvtStatusMeterGauge}
 */
DvtStatusMeterGauge.newInstance = function(context, callback, callbackObj, options) {
  var gauge = new DvtStatusMeterGauge();
  gauge.Init(context, callback, callbackObj, options);
  return gauge;
}

/**
 * @override
 */
DvtStatusMeterGauge.prototype.setOptions = function(options) {
  // Combine the user options with the defaults and store
  DvtStatusMeterGauge.superclass.setOptions.call(this, DvtStatusMeterGaugeDefaults.calcOptions(options));
}

/**
 * @override
 */
DvtStatusMeterGauge.prototype.Render = function(container, width, height) 
{  
  DvtStatusMeterGaugeRenderer.render(this, container, width, height);
}

/**
 * @override
 */
DvtStatusMeterGauge.prototype.CreateAnimationOnDisplay = function(objs, animationType, animationDuration) {
  var animatedObjs = [];
  for (var i=0; i<objs.length; i++) {
    var obj = objs[i];
    var endState = obj.getAnimationParams();
    obj.setAnimationParams([endState[0], endState[0], endState[2], endState[3]]);
    var animation = new DvtCustomAnimation(this.getContext(), obj, animationDuration);
    animation.getAnimator().addProp(DvtAnimator.TYPE_NUMBER_ARRAY, obj, obj.getAnimationParams, obj.setAnimationParams, endState);
    animatedObjs.push(animation);
  }
  return new DvtParallelPlayable(this.getContext(), animatedObjs);
}

/**
 * @override
 */
DvtStatusMeterGauge.prototype.CreateAnimationOnDataChange = function(oldObjs, newObjs, animationType, animationDuration) {
  var animatedObjs = [];
  for (var i=0; i<oldObjs.length; i++) {
    var oldObj = oldObjs[i];
    var newObj = newObjs[i];
    var startState = oldObj.getAnimationParams();
    var endState = newObj.getAnimationParams();
    
    newObj.setAnimationParams(startState);
    var animation = new DvtCustomAnimation(this.getContext(), newObj, animationDuration);
    animation.getAnimator().addProp(DvtAnimator.TYPE_NUMBER_ARRAY, newObj, newObj.getAnimationParams, newObj.setAnimationParams, endState);
    animatedObjs.push(animation);
  }
  return new DvtParallelPlayable(this.getContext(), animatedObjs);
}

/**
 * Default values and utility functions for component versioning.
 * @class
 */
var DvtStatusMeterGaugeDefaults = new Object();

DvtObj.createSubclass(DvtStatusMeterGaugeDefaults, DvtObj, "DvtStatusMeterGaugeDefaults");

/**
 * Defaults for version 1.
 */ 
DvtStatusMeterGaugeDefaults.VERSION_1 = {
  color: "#313842"
};

/**
 * Combines the user options with the defaults for the specified version.  Returns the
 * combined options object.  This object will contain internal attribute values and
 * should be accessed in internal code only.
 * @param {object} userOptions The object containing options specifications for this component.
 * @return {object} The combined options object.
 */
DvtStatusMeterGaugeDefaults.calcOptions = function(userOptions) {
  var defaults = DvtStatusMeterGaugeDefaults._getDefaults(userOptions);

  // Use defaults if no overrides specified
  if(!userOptions)
    return defaults;
  else // Merge the options object with the defaults
    return DvtJSONUtils.merge(userOptions, defaults);
}

/**
 * Returns the default options object for the specified version of the component.
 * @param {object} userOptions The object containing options specifications for this component.
 * @return {object} The default options object for the component.
 * @private
 */
DvtStatusMeterGaugeDefaults._getDefaults = function(userOptions) {
  // Note: Version checking will eventually get added here
  // Note: Future defaults objects are deltas on top of previous objects
  
  // The base class defaults are combined with the defaults from this class
  var baseDefaults = DvtGaugeDefaults.getDefaults(userOptions);
  var defaults = DvtJSONUtils.clone(DvtStatusMeterGaugeDefaults.VERSION_1)
  return DvtJSONUtils.merge(defaults, baseDefaults);
}
/**
 * Renderer for DvtStatusMeterGauge.
 * @class
 */
var DvtStatusMeterGaugeRenderer = new Object();

DvtObj.createSubclass(DvtStatusMeterGaugeRenderer, DvtObj, "DvtStatusMeterGaugeRenderer");

/**
 * Renders the gauge in the specified area.
 * @param {DvtStatusMeterGauge} gauge The gauge being rendered.
 * @param {DvtContainer} container The container to render into.
 * @param {number} width The width of the component.
 * @param {number} height The height of the component.
 */
DvtStatusMeterGaugeRenderer.render = function(gauge, container, width, height) {
  if(DvtGaugeDataUtils.hasData(gauge)) {
    // Allocate the outer gap for the component
    var options = gauge.__getOptions();
    var outerGap = options.__layout.outerGap;
    var bounds = new DvtRectangle(outerGap, outerGap, width - 2*outerGap, height - 2*outerGap);
    
    // Render the label first to allocate space
    DvtStatusMeterGaugeRenderer._renderLabel(gauge, container, bounds);
    
    // Render the bar
    DvtStatusMeterGaugeRenderer._renderShape(gauge, container, bounds);
  }
  else // Render the empty text
    DvtGaugeRenderer.renderEmptyText(gauge, container, new DvtRectangle(0, 0, width, height));
}

/**
 * Renders the led shape into the specified area.
 * @param {DvtLedGauge} gauge The gauge being rendered.
 * @param {DvtContainer} container The container to render into.
 * @param {DvtRectangle} bounds The available bounds for rendering.
 */
DvtStatusMeterGaugeRenderer._renderShape = function(gauge, container, bounds) {
  var data = gauge.__getData();
  
  // Create an axis info to find the coords of values.
  var axisOptions = {layout: {}};
  axisOptions.layout.gapRatio = 0;
  axisOptions.timeAxisType = "disabled";
  axisOptions.position = "bottom";
  axisOptions.minValue = data.minValue;
  axisOptions.maxValue = data.maxValue;
  
  var axisData = {};
  axisData.minDataValue = data.minValue;
  axisData.maxDataValue = data.maxValue;
  
  var axisInfo = DvtAxisInfo.newInstance(axisData, axisOptions, bounds);
  
  // First calculate the baseline coordinate.
  var baseline = 0;
  if(data.maxValue < 0)
    baseline = data.maxValue;
  else if(data.minValue > 0)
    baseline = data.minValue;
  
  var baselineCoord = axisInfo.getCoordAt(baseline);
  // Calculate the endCoord.  Adjust to keep within the axis.
  var endCoord = axisInfo.getCoordAt(data.value);
  endCoord = Math.max(bounds.x, Math.min(bounds.x + bounds.w, endCoord));
  var y1 = bounds.y;
  var y2 = bounds.y + bounds.h;

  // Add the shape to the container
  var bRender = true;
  if(endCoord == baselineCoord)
    bRender = false; // don't draw an empty bar
  
  // Create the shapes    
  var shape = DvtStatusMeterGaugeRenderer._createShape(gauge.getContext(), baselineCoord, endCoord, y1, y2);
  gauge.__shapes.push(shape);
  
  // Apply style properties
  var color = DvtGaugeStyleUtils.getColor(gauge);
  var borderColor = DvtGaugeStyleUtils.getBorderColor(gauge);
  shape.setFill(new DvtSolidFill(color));
  if(borderColor)
    shape.setStroke(new DvtSolidStroke(borderColor));
    
  // Tooltip Support
  if (bRender) {
    if(data.tooltip)
      gauge.__getEventManager().associate(shape, new DvtSimpleObjPeer(null, data.tooltip, color));
    
    container.addChild(shape);
  }
  
  // Render the visual effects
  var overlay = DvtStatusMeterGaugeRenderer._createShape(gauge.getContext(), baselineCoord, endCoord, y1, y2);
  DvtStatusMeterGaugeRenderer._renderVisualEffects(gauge, container, overlay, bRender);
}

/**
 * Creates and returns the shape for the statusmeter.
 * @param {DvtContext} context
 * @param {number} baselineCoord
 * @param {number} endCoord
 * @param {number} y1
 * @param {number} y2
 * @return {DvtShape}
 */
DvtStatusMeterGaugeRenderer._createShape = function(context, baselineCoord, endCoord, y1, y2) {
  return new DvtStatusMeterGaugeIndicator(context, baselineCoord, endCoord, y1, y2);
}

/**
 * Renders the visual effects for the shape into the specified area.
 * @param {DvtLedGauge} gauge The gauge being rendered.
 * @param {DvtContainer} container The container to render into.
 * @param {DvtShape} shape The shape to use for the visual effects.
 * @param {boolean} bRender True if the shape should be rendered at this time.
 */
DvtStatusMeterGaugeRenderer._renderVisualEffects = function(gauge, container, shape, bRender) {
  var options = gauge.__getOptions();
  var type = options.type;
  
  if(options.visualEffects == "none")
    return;

  // Overlay Shape
  shape.setMouseEnabled(false);
  if(bRender)
    container.addChild(shape);
  
  // Gradient
  var arColors = ["#FFFFFF", "#FFFFFF", "#FFFFFF"];
  var arAlphas = [0.5, 0.3125, 0];
  var arStops = [0, 0.3, 1.0];
  var gradient = new DvtLinearGradientFill(270, arColors, arAlphas, arStops);
  shape.setFill(gradient);
  gauge.__shapes.push(shape);
}

/**
 * Renders the label into the specified area.  Updates the bounds after rendering to reserve space
 * for the labels.
 * @param {DvtLedGauge} gauge The gauge being rendered.
 * @param {DvtContainer} container The container to render into.
 * @param {DvtRectangle} bounds The available bounds for rendering.
 */
DvtStatusMeterGaugeRenderer._renderLabel = function(gauge, container, bounds) {
  var data = gauge.__getData();
  var options = gauge.__getOptions();
  var isBIDI = DvtStyleUtils.isLocaleR2L();

  // Allocate space for the label
  if(options.labelDisplay == "on") 
  {
    var labelGap = options.__layout.labelGap;
    var minLabelDims;
    var maxLabelDims;
    var alignCoord; // the alignment point for the label
  
    // Allocate space to the right for positive values
    if(data.maxValue > 0) {
      var maxLabel = new DvtText(gauge.getContext(), data.maxValue);
      maxLabel.setCSSStyle(new DvtCSSStyle(options.labelStyle));
      container.addChild(maxLabel);
      maxLabelDims = maxLabel.getDimensions();
      container.removeChild(maxLabel);
      
      // Align the label
      if(data.value >= 0 || data.minValue >= 0) {
        alignCoord = isBIDI ? bounds.x + maxLabelDims.w: bounds.x + bounds.w;
        labelSpace = maxLabelDims.w;
      }
      
      // Update the allocated space
      if(isBIDI) { // Allocate to the left
        bounds.x += (maxLabelDims.w + labelGap);
        bounds.w -= (maxLabelDims.w + labelGap);
      }
      else // Allocate to the right
        bounds.w -= (maxLabelDims.w + labelGap);
    }
    
    // Allocate space to the left for negative values
    if(data.minValue < 0) {
      var minLabel = new DvtText(gauge.getContext(), data.minValue);
      minLabel.setCSSStyle(new DvtCSSStyle(options.labelStyle));
      container.addChild(minLabel);
      minLabelDims = minLabel.getDimensions();
      container.removeChild(minLabel);
      
      // Align the label
      if(data.value < 0 || data.maxValue <= 0) {
        alignCoord = isBIDI ? bounds.x + bounds.w: bounds.x + minLabelDims.w;
        labelSpace = minLabelDims.w; 
      }
      
      // Update the allocated space
      if(isBIDI)  // Allocate to the right
        bounds.w -= (minLabelDims.w + labelGap);
      else { // Allocate to the left
        bounds.x += (minLabelDims.w + labelGap);
        bounds.w -= (minLabelDims.w + labelGap);
      }
    }
    
    // Create and position the text
    var label = new DvtText(gauge.getContext(), data.value, alignCoord, bounds.y + bounds.h/2);
    label.setCSSStyle(new DvtCSSStyle(options.labelStyle));
    label.alignCenter();
    
    // Align the text to the end
    var agent = DvtAgent.getAgent();
    if (agent.isGecko() || !DvtStyleUtils.isLocaleR2L())
      label.alignEnd();
    else 
      label.alignStart();
    
    // Truncate if needed, null is returned if the label doesn't fit
    label = label.truncateToSpace(container, labelSpace, bounds.h);
    if(label)
      container.addChild(label);
  }
}
// Copyright (c) 2008, 2012, Oracle and/or its affiliates. 
// All rights reserved. 

/**
 * Indicator for DvtStatusMeterGauge.
 * @class
 * @constructor
 * @extends {DvtRect}
 */
var DvtStatusMeterGaugeIndicator = function(context, baselineCoord, endCoord, y1, y2) {
  this.Init(context, baselineCoord, endCoord, y1, y2);
}

DvtObj.createSubclass(DvtStatusMeterGaugeIndicator, DvtRect, "DvtStatusMeterGaugeIndicator");

/** @private **/
DvtStatusMeterGaugeIndicator._CORNER_RADIUS_SIZE = 0.25;  // factor of the height
DvtStatusMeterGaugeIndicator._MIN_CORNER_RADIUS = 2.5;

/**
 * Initializes the component.
 * @param {DvtContext} context The rendering context
 * @param {number} baselineCoord
 * @param {number} endCoord
 * @param {number} y1
 * @param {number} y2
 * @protected
 */
DvtStatusMeterGaugeIndicator.prototype.Init = function(context, baselineCoord, endCoord, y1, y2) {
  DvtStatusMeterGaugeIndicator.superclass.Init.call(this, context);
  
  // Set the coordinates of the shape based on the params
  this.setCoords(baselineCoord, endCoord, y1, y2);
}

/**
 * Specifies the coordinates for the indicator.
 * @param {number} baselineCoord
 * @param {number} endCoord
 * @param {number} y1
 * @param {number} y2
 */
DvtStatusMeterGaugeIndicator.prototype.setCoords = function(baselineCoord, endCoord, y1, y2) {
  // Store these params
  this._baselineCoord = baselineCoord;
  this._endCoord = endCoord;
  this._y1 = y1;
  this._y2 = y2;

  // Convert into rectangle coordinates and set
  var x = Math.min(baselineCoord, endCoord);
  var y = y1;
  var width = Math.abs(baselineCoord - endCoord);
  var height = y2 - y1;
  this.setRect(x, y, width, height);
  
  // Apply rounded corners
  var radius = height * DvtStatusMeterGaugeIndicator._CORNER_RADIUS_SIZE;
  if(radius >= DvtStatusMeterGaugeIndicator._MIN_CORNER_RADIUS)
    this.setCornerRadius(radius, radius);
}

/**
 * Animation support.
 * @return {array}
 */
DvtStatusMeterGaugeIndicator.prototype.getAnimationParams = function() {
  return [this._baselineCoord, this._endCoord, this._y1, this._y2];
}

/**
 * Animation support.
 * @param {array} params
 */
DvtStatusMeterGaugeIndicator.prototype.setAnimationParams = function(params) {
  if(params && params.length == 4) 
    this.setCoords(params[0], params[1], params[2], params[3]);
}
