/** 
 * This is the Axes superclass ('axes' as in the plural of 'axis').
 */
PrtAxes.prototype = new abstract_Widget();
function PrtAxes() {
  // Properties:
  //   Constants:
  this.AXES_CONTAINER_DIV_ID = "axesContainer";
  this.AXES_CLASS_ID         = "axesPieces";
  this.HATCH_LABEL_DIV_ID    = "hatchLabels";
  this.X_AXIS_DIV_ID         = "Xaxis";
  this.Y_AXIS_DIV_ID         = "Yaxis";
  //   Variables:
  this.strId = this.AXES_CONTAINER_DIV_ID;
  this.intBorderAdj = GloScope.CrossPlatformCode.getBorderAdjustment();
  this.intHatchMarkLength = 10; // units: pixels.
  this.intHatchPixelInterval = 50;  // units: pixels.
  this.intGraphHeight = null;
  this.intGraphWidth = null;
  this.intLeft = 0; // units: pixels.
  this.intTop  = 0; // units: pixels.
  // Methods:
  this.getHtml = Axes_getHtml;
  this.getChangeAxesHtml = Axes_getChangeAxesHtml;
  this.getData = function () { 
    return "This widget currently holds no data.";
  };
  this.priSetHshAxesCoordinates = Axes_priSetHshAxesCoordinates;
  this.getContainedHtml = Axes_getContainedHtml;
  //   Private methods:
  this.priGetObjects    = Axes_priGetObjects; // Implement object inheritance.
  this.priGetAxes       = Axes_priGetAxes; 
  this.priGetHatchMarksAndLabels = Axes_priGetHatchMarksAndLabels;
}   


/**
 * This method bestows unique copies of objects upon extending classes.
 */
function Axes_priGetObjects() {
  this.hshVisibleXYranges = new Object();
  this.hshVisibleXYranges[Grapher.MINX] = -10;
  this.hshVisibleXYranges[Grapher.MAXX] =  10;
  this.hshVisibleXYranges[Grapher.MINY] = -10;
  this.hshVisibleXYranges[Grapher.MAXY] =  10;  
  this.hshAxesCoordinates = new Object();
  this.hshAxesCoordinates["leftY"] = null;
  this.hshAxesCoordinates["topX"]  = null;
}


// Static values:
Axes.prototype = new PrtAxes();
/** 
 * This is the class constructor for the Axes class. 
 * @param intOffsetLeft  The left-hand offset of the div that is the container for the axes.
 * @param intGraphWidth  The width of the graph.
 * @param intGraphHeight The height of the graph.
 * @param arrXYranges    Contains the maximum and minimum ranges for the X and Y axes 
 *                       (for the hatch-mark labels).
 * @param hshAxesCoordinates OPTIONAL   The y-position of the X axis, and the x-position of the Y axis,
 *                                      relative to the div that contains them.  If left null, the Axes
 *                                      class will position its axes at the center of the graph space.
 */
function Axes(intOffsetLeft, intOffsetTop, intGraphWidth, intGraphHeight, hshXYranges) {
  this.abstract_Widget(); // Complete inheritance from the superclass.
  this.priGetObjects();   // Get unique objects from prototype.
  var strMessage = null;
  if (arguments.length > 4 && (hshXYranges != null) ) {
    this.hshVisibleXYranges = hshXYranges;
  }
  this.intGraphHeight  = intGraphHeight;
  this.intGraphWidth   = intGraphWidth;
  this.priSetHshAxesCoordinates();
  this.intLeft        += intOffsetLeft;
  this.intTop         += intOffsetTop;
}



/**
 * This method returns the markup for rendering the axes and their container.
 * @return strHtml  A string containing the above markup.
 */
function Axes_getHtml() {
  var intXtop  = this.hshAxesCoordinates["topX" ]; 
  var intYleft = this.hshAxesCoordinates["leftY"];
  var strHtml  =          
       "\n <div id='" + this.AXES_CONTAINER_DIV_ID + "'  style='top: "
       +     (this.intTop ) + "px;  left: " 
       +     (this.intLeft) + "px;  border-width: "+ContainerDiv.BORDER_WIDTH+"px; "
       + " ' >   "
       +     this.getContainedHtml(intXtop, intYleft)
       + "\n </div>";
  return strHtml;
}


/**
 * This method returns the markup for rendering the axes inside of their
 * pre-existing container. It is used for dynamically changing the axes.
 * @param hshVisibleXYranges  The minimum and maximum ranges to display for X and Y.
 * @return string  The HTML markup for what is described above.
 */
function Axes_getChangeAxesHtml(hshVisibleXYranges) {
  this.hshVisibleXYranges = hshVisibleXYranges;
  if (this.priSetHshAxesCoordinates() == false) { 
    return false;
  } else {
    return (this.getContainedHtml(this.hshAxesCoordinates["topX" ], this.hshAxesCoordinates["leftY"]));
  }
}


/**
 * This method returns the HTML markup needed to render the contents of the 
 * naked container that holds everything visible about the graph axes.
 * @return string  The HTML markup described above.
 */
function Axes_getContainedHtml(intXtop, intYleft) {
  return (  "\n\n  " + this.priGetAxes(intXtop, intYleft) 
          + "\n\n  " + this.priGetHatchMarksAndLabels("x",  intXtop, intYleft)
          + "\n\n  " + this.priGetHatchMarksAndLabels("y", intYleft, intXtop ));
}


/** 
 * This method returns the HTML markup for a pair of XY axis bars.
 * @param intXtop   The varying position attribute of the "x" axis.
 * @param intYleft  The varying position attribute of the "y" axis.
 * @return strHtml  The string containing the mentioned HTML markup.
 */
function Axes_priGetAxes(intXtop, intYleft) {
   var intHeightX = 1; 
   var intHeightY = this.intGraphHeight - (ContainerDiv.BORDER_WIDTH*2) - (this.intBorderAdj/2) - 5; // We take off 5 to show centering.
   var intWidthX  = this.intGraphHeight - (ContainerDiv.BORDER_WIDTH*2) - (this.intBorderAdj/2) - 5; // "                               "
   var intWidthY  = 1;
   var strXappropriateDivHW = GloScope.CrossPlatformCode.getFancyDivHtml(intHeightX, intWidthX);
   var strYappropriateDivHW = GloScope.CrossPlatformCode.getFancyDivHtml(intHeightY, intWidthY);
   var strHtml = 
      "\n<div id='" + this.X_AXIS_DIV_ID + "'"
    +       " class='" + this.AXES_CLASS_ID + "' "
    +         strXappropriateDivHW  
    +        "top: "+intXtop+"px; "
    +       " left: "+(ContainerDiv.BORDER_WIDTH+1)+"px; '        > "
    +     this.getFillerMarkup(intHeightX, intWidthX)
    + "\n</div> "
    + "\n<div id='" + this.Y_AXIS_DIV_ID + "'"
    +       " class='" + this.AXES_CLASS_ID + "' "
    +         strYappropriateDivHW  
    +        "top: "+(ContainerDiv.BORDER_WIDTH+1)+"px; "
    + "       left: " + intYleft + "px; '       >"
    +     this.getFillerMarkup(intHeightY, intWidthY)
    + "\n</div>";
   //alert(strHtml);
  return strHtml;
}         


/**
 * This method provides the hatch marks for each axis.  They are drawn relative to the div that holds
 * the axis bars; they are children of that div as are the axis bars.
 * @param  strOrientation      Either 'x' or 'y' -- specifies which axis we're doing hatch marks for.
 * @param  intVaryingPosition  This is the position of the axis for which hatchmarks are being drawn. 
 * @param  intPosOfOtherAxis   No label for the current hatchmark is drawn when the current hatchmark
 *                             is on the other axis (so that the label does not have the line of the 
 *                             other axis running through it).
 * @return strHtml
 */
function Axes_priGetHatchMarksAndLabels(strOrientation, intVaryingPosition, intPosOfOtherAxis) { 
  var strHtml = "";
  var MAX_LABEL_LENGTH = 8;  // Characters.
  var COURIER_FONT_WIDTH = 8; // Pixels: There are about 8 px per Courier font-family character.
  var LABEL_HEIGHT = 18; // Pixels.
  var LABEL_HATCH_SPACER = 5; // Pixels.
  var arrIntAndDecimal = new Array(2);
  var intTop; 
  var intLabelTop;
  var intLabelLeft;
  var intLeft;
  var intHeight;
  var intWidth;
  var intNumHatches;
  var intRangeLowEnd;
  var intRangeHighEnd;
  var fltIntervalFactor;
  var fltHatchLabel;
  var intFollowAxis; // This variable is used to match the hatchmark with the axis when the hatchmark is in its secondary position.
  // Switches to control label drawing:
  var booSecondaryHatchDirection = false;
  var booHatchMarkOnOtherAxis = false;

  // First, get the base settings:
  if (strOrientation == "x") {
    intRangeLowEnd = this.hshVisibleXYranges[Grapher.MINX];
    intNumHatches = Math.floor(( (this.intGraphWidth) - (ContainerDiv.BORDER_WIDTH*2))/this.intHatchPixelInterval);
    fltIntervalFactor = (this.hshVisibleXYranges[Grapher.MAXX] - intRangeLowEnd) / (intNumHatches+1);
    intTop = intVaryingPosition + 1; // 1 pixel to move past the x-axis bar itself.
    if (intTop > (this.intGraphHeight - this.intHatchMarkLength - LABEL_HEIGHT - this.intBorderAdj) ) {
      booSecondaryHatchDirection = true;
      intFollowAxis = (this.intGraphHeight - intVaryingPosition - this.intBorderAdj); // Fine-tuning to fit hatchmark against the axis.
      intTop = this.intGraphHeight - this.intHatchMarkLength - this.intBorderAdj - intFollowAxis;
    }
    intLeft = -(Math.round(this.intBorderAdj/2)); // Hatch marks for the x-axis start here.
    intHeight = this.intHatchMarkLength;
    intWidth  = 1;
    if (intLeft == intPosOfOtherAxis) {
      booHatchMarkOnOtherAxis = true;
    }
  } else if (strOrientation == "y") {
    intRangeLowEnd = this.hshVisibleXYranges [Grapher.MINY];
    intRangeHighEnd = this.hshVisibleXYranges[Grapher.MAXY];
    intNumHatches = Math.floor(( this.intGraphHeight - (ContainerDiv.BORDER_WIDTH*2))/this.intHatchPixelInterval);
    fltIntervalFactor = (this.hshVisibleXYranges[Grapher.MAXY] - intRangeLowEnd) / (intNumHatches+1);
    intTop = -(Math.round(this.intBorderAdj/2));
    intLeft = intVaryingPosition - this.intHatchMarkLength;
    if (intLeft < ( (COURIER_FONT_WIDTH * MAX_LABEL_LENGTH) + LABEL_HATCH_SPACER 
                    + ContainerDiv.BORDER_WIDTH + (GloScope.CrossPlatformCode.getBorderAdjustment()/2)  )) {
      booSecondaryHatchDirection = true;
      intFollowAxis = intVaryingPosition; // Fine-tuning to fit hatchmark against the axis.
      intLeft = intFollowAxis;
    }
    intHeight = 1;
    intWidth  = this.intHatchMarkLength;
  }
 
  // Now, draw each hash mark:
  for (var intI=0; intI < intNumHatches; intI++) {
    /*-
     * First, get correct moveable hatch position (to draw them one after another along the axis), 
     * and compute label position.
     */
    if (strOrientation == "y") {
      fltHatchLabel = intRangeHighEnd - (fltIntervalFactor * (intI+1));
      intTop += this.intHatchPixelInterval;
      // Avoid drawing label on top of other axis:
      if ( (intTop > (intPosOfOtherAxis-LABEL_HATCH_SPACER)) && (intTop < (intPosOfOtherAxis+LABEL_HATCH_SPACER)) ) { 
        booHatchMarkOnOtherAxis = true;
      } else {
        booHatchMarkOnOtherAxis = false;
      }
    } else if (strOrientation == "x") { 
      fltHatchLabel = intRangeLowEnd + (fltIntervalFactor * (intI+1));
      intLeft += this.intHatchPixelInterval;
      // Avoid drawing label on top of other axis:
      if ( (intLeft > (intPosOfOtherAxis-LABEL_HATCH_SPACER)) && (intLeft < (intPosOfOtherAxis+LABEL_HATCH_SPACER)) ) { 
        booHatchMarkOnOtherAxis = true;
      } else {
        booHatchMarkOnOtherAxis = false;
      }
    }   


    // Compose label markup, if any (first we compute the appropriate number for the label):
    strSciNotation = "";
    if ( Math.abs(fltHatchLabel) >= 10000 ) {
      // Add 4 for the 4 zeroes in 10000, subtract 1 because the '1' doesn't figure in the E:
      strSciNotation = "+E" + ( ((new String(fltHatchLabel/100000)).split(".")[0].length) - 1 + 4);
      fltHatchLabel = fltHatchLabel/10000;
    }
    /*-
     * Now reduce if needed so that there are MAX_LABEL_LENGTH characters 
     * (digits plus decimal mark and any scientific notation):
     */
    strHatchLabel = new String(fltHatchLabel);
    arrIntAndDecimal = strHatchLabel.split(".");
    // alert("0: "+arrIntAndDecimal[0] + ", 1: "+arrIntAndDecimal[1])
    // Initialize arrIntAndDecimal (to avoid 'undefined' errors):
    if (typeof arrIntAndDecimal[0] == "undefined" || arrIntAndDecimal[0] == null) {
      arrIntAndDecimal[0] = "";
    }
    if (typeof arrIntAndDecimal[1] == "undefined" || arrIntAndDecimal[1] == null) {
      arrIntAndDecimal[1] = "";
    }
    intRoomForDecimals = MAX_LABEL_LENGTH - strSciNotation.length - arrIntAndDecimal[0].length - 1; // 1 for the decimal point.
    if (intRoomForDecimals <= 0) {
      arrIntAndDecimal[1] = "";     
    } 
    arrIntAndDecimal[1] = (arrIntAndDecimal[1]).slice(0, intRoomForDecimals);
    rxpNoTrailingDecimalZeroes = new RegExp("[0]+[^0-9]*$");
    arrIntAndDecimal[1] = arrIntAndDecimal[1].replace(rxpNoTrailingDecimalZeroes, "");    
    intLabelDecimal = Math.round(new Number(arrIntAndDecimal[1]));
    intLabelInt = new Number(arrIntAndDecimal[0]);
    if (arrIntAndDecimal[0].slice(0,1) == "-" && intLabelInt == 0) {
      strInsertSign = "-";
    } else {
      strInsertSign = "";
    }
    strHatchLabel = strInsertSign + (new String(intLabelInt)) + "." + intLabelDecimal + strSciNotation;
    // Now that we have the label string, set the absolute position of the label according to its length:
    if (strOrientation == "y") {
      intLabelTop = -8;
      if (!booSecondaryHatchDirection) {
        intLabelLeft = -(strHatchLabel.length * COURIER_FONT_WIDTH) - intWidth;
      } else {
        intLabelLeft = this.intHatchMarkLength + LABEL_HATCH_SPACER; 
      }
    } else if (strOrientation == "x") { 
      intLabelLeft = -( (strHatchLabel.length * COURIER_FONT_WIDTH) / 2);
      if (!booSecondaryHatchDirection) {    
        intLabelTop = 14;
      } else {
        intLabelTop = -14 - intHeight;
      }
    }
    // Now make the final HTML for the label:
    if (intI%2 == 0 || booHatchMarkOnOtherAxis) {
      strLabelHtml = "";
    } else { // Need to specify the width to keep IE6 from putting negative x-axis labels on two lines (go figure): 
      strLabelHtml =  "<div style='position: absolute; width: "
        + (strHatchLabel.length * COURIER_FONT_WIDTH) + "px; top: "+intLabelTop+"px;   "
        + " left: "+intLabelLeft+"px;  background-color: transparent; "
        + "font-family: courier;  font-size: 10pt; '>"+strHatchLabel+"</div>";
    } 


    /*-
     * Compose the composite HTML for this hash mark and its label (note that
     * the table code is the only cross-platform 
     * solution; an empty gif does work on Mozilla 0.9.4 but not on IE5.0.
     */
    strHtml += 
      "\n <div id='hatchMark" + (strOrientation+intI) + "' "   
      +  "              class='" + this.AXES_CLASS_ID + "' "
      +                 GloScope.CrossPlatformCode.getFancyDivHtml(intHeight,intWidth)  
      +                "top: "+intTop+"px;  left: "+intLeft+ "px; ' "
      +  "           > "
      + GloScope.CrossPlatformCode.getHatchAndLabelHtml(strLabelHtml, this.getFillerMarkup, intHeight, intWidth)
      +  "\n </div> ";
  }
  return strHtml;
}   


/** 
 * The method calculates the x,y coordinate positions for the x and y axes,
 * based on the range data passed as parameters to it (there are user input
 * fields that can set these values via the the Grapher class and the input_form class),
 * and sets the "topX" and "leftY" properties of the
 * object property 'this.hshAxesCoordinates' to those coordinate position values. 
 */
function Axes_priSetHshAxesCoordinates() {
  var intXmin = this.hshVisibleXYranges[Grapher.MINX]; // The minimum range to display for the X axis.
  var intXmax = this.hshVisibleXYranges[Grapher.MAXX]; // The maximum range to display for the X axis.
  var intYmin = this.hshVisibleXYranges[Grapher.MINY]; // The minimum range to display for the Y axis.
  var intYmax = this.hshVisibleXYranges[Grapher.MAXY]; // The maximum range to display for the Y axis.
  var intBorderAdj;

  // alert("Xmin: "+intXmin+" Xmax: "+intXmax+" Ymin: "+intYmin+" Ymax: "+intYmax);
  /*+
   * This inner function makes sure that the input from the axis range fields is 
   * correct. 
   * @param intXmin  The minimum range to display for the X axis.
   * @param intXmax  The minimum range to display for the X axis.
   * @param intYmin  The minimum range to display for the Y axis.
   * @param intYmax  The minimum range to display for the Y axis.
   * @return boolean  Whether the input data is valid.
   */
  var validateAxesRangeInput = function (intXmin, intXmax, intYmin, intYmax) {
    if ( (intXmin > intXmax) || (intYmin > intYmax) ) {
      alert("Sorry -- all minimum values must be less than their corresponding maximum values.  Please try again.");
      return false; 
    } 
    return true;
  } //-validateAxesRangeInput


  if (!validateAxesRangeInput(intXmin, intXmax, intYmin, intYmax)) {
    return false;
  }
  intBorderAdj = GloScope.CrossPlatformCode.getBorderAdjustment()
  // Compute horizontal position of the 'Y' axis:
  if  (
       ( (intXmin <= 0) && (intXmax >= 0) )
      ) 
    {
    // The signs of intYmin and intYmax are different or one of them is zero, so we must show 0 on the Y axis:
     this.hshAxesCoordinates["leftY"] = Math.round(
                                              (this.intGraphWidth-intBorderAdj) 
                                              * ( (Math.abs(intXmin) / (Math.abs(intXmin - intXmax))) )
                                              );
    // Now adjust to make sure we're inside the bounds of the graph, and tweak for good appearance:
     if (this.hshAxesCoordinates["leftY"] >= (this.intGraphWidth - intBorderAdj)) {
       // Subtract 2 px to move past the border.  
       this.hshAxesCoordinates["leftY"] = this.intGraphWidth - intBorderAdj - 2; 
     } else if (this.hshAxesCoordinates["leftY"] <= (intBorderAdj/2)) {
       this.hshAxesCoordinates["leftY"] = 2; // Add 2 px to move past the edge.
     }  
  } else if ( (intXmin > 0) && (intXmax > 0) ) {  // The signs are not different:
    this.hshAxesCoordinates["leftY"] =  2;  // You get the idea.
  } else if ( (intXmin < 0) && (intXmax < 0) ) {
    this.hshAxesCoordinates["leftY"] = this.intGraphWidth - (intBorderAdj) - 2;
  } 

  // Compute vertical position of the 'X' axis:
  if  (
       ( (intYmin <= 0) && (intYmax >= 0) )
      )
  { 
    // The signs of intYmin and intYmax are different or one of them is zero, so we must show 0 on the Y axis:
    this.hshAxesCoordinates["topX" ] = (this.intGraphWidth-intBorderAdj) - 
      Math.round( 
                 (this.intGraphHeight-intBorderAdj)
                 * ( (Math.abs(intYmin) / (Math.abs(intYmin - intYmax))) )
                 );
    // Now adjust to make sure we're inside the bounds of the graph:
     if (this.hshAxesCoordinates["topX"] >= (this.intGraphHeight - intBorderAdj)) {
       this.hshAxesCoordinates["topX"] = this.intGraphHeight - intBorderAdj - 2;
     } else if (this.hshAxesCoordinates["topX"] <= (intBorderAdj/2)) {
       this.hshAxesCoordinates["topX"] = 2;
     }  
  } else if ( (intYmin > 0) && (intYmax > 0) ) {  // The signs are not different:
    this.hshAxesCoordinates["topX"] = this.intGraphHeight - intBorderAdj - 2;
  } else if ( (intYmin < 0) && (intYmax < 0) ) {
    this.hshAxesCoordinates["topX"] = 2;
  } 

  /*
  alert(this.hshAxesCoordinates["topX" ]);
  alert(this.hshAxesCoordinates["leftY"]);
  alert("Math.abs(intXmin - intXmax) = "+Math.abs(intXmin - intXmax));
  alert("intXmin - intXmax = "+(intXmin - intXmax));
  alert("intXmin"+intXmin);
  alert("intXmax"+intXmax);
  */
}





