/**
 * This class manages the placement of the <code>Plate</code> over server image data.
 * <br><br>
 * <br>
 * <br><br>
 * <a href="mailto:ehudsons@andrew.cmu.edu">Ellen Hudson-Snyder</a>, 
 * <a href="mailto:evedar@andrew.cmu.edu">Elvin Vedar</a>,
 * <a href="mailto:cbalz@andrew.cmu.edu">Christopher M. Balz</a>.
 * <br><br>CVS Version Info:<br>
 *  $Id: ServerImageSet.js,v 1.36 2005/12/09 05:29:38 ehudsons Exp $  
 * <br><br>
 * @listens-to-event <code>onurlsready</code> from the <code>ImageURLCalculator</code> indicating new image URLs
 *                   are ready.
 * @emits-event <code>onnewimageset</code> to the <code>ImageURLCalculator</code> to request a new set of image
 *              URLs.
 * @object-prop <code>PanoramaProtocol</code> <code>objPanoramaProtocol</code> The protocol object used to
 *                                            communicate information about the server image set to this
 *                                            application.
 * @object-prop <code>Gigapan</code> <code>objController</code> The parent of this object: the Controller.
 * @object-prop <code>string</code> <code>strId</code> The identifer of this object: "serverimageset"
 * @object-prop <code>PlatefulRequest</code> <code>objPlatefulRequest</code> The protocol object used to
 *                                           communicate image data requests to the
 *                                           <code>ImageURLCalculator</code>.
 * @object-prop <code>PlateUpdateResponse</code> <code>objPlateUpdateResponse</code> The protocol object used to
 *                                               communicate image data back to the <code>Plate</code>.
 * @object-prop <code>number</code> <code>intDisplayHeightPx</code>  The height of the <code>plate</code> screen
 *                                  element.
 * @object-prop <code>number</code> <code>intDisplayWidthPx</code>  The width of the <code>plate</code> screen
 *                                  element.
 * @object-prop <code>number</code> <code>intNumImagesHigh</code>  The number of images in a column on the
 *                                  <code>Plate</code>.
 * @object-prop <code>number</code> <code>intNumImagesWide</code>  The number of images in a row on the
 *                                  <code>Plate</code>.
 * @object-prop <code>number</code> <code>intImageHeightPx</code> The height of each image in the server image set.
 * @object-prop <code>number</code> <code>intImageWidthPx</code> The width of each <code>Stack</code> object in the
 *                                  stackList.  
 * @object-prop <code>number</code> <code>intServerImageSetMaxRow</code>  The row number of the last image tile on 
 *                                  the Panorama data.
 * @object-prop <code>number</code> <code>intServerImageSetMaxCol</code>  The column number of the last image tile 
 *                                  on the Panorama data.
 * @object-prop <code>number</code> <code>intLeftDistanceMovedPx</code>    The number of pixels moved horizontally 
 *                                  since the last image load.  This value serves as the link between presentation-
 *                                  specific positioning and the application data of the image set on the server.
 *                                  This is presentation-specific data, unrelated to the image set on the server.
 * @object-prop <code>number</code> <code>intTopDistanceMovedPx</code>    The number of pixels moved vertically
 *                                  since the last image load.  This value serves as the link between presentation-
 *                                  specific positioning and the application data of the image set on the server.
 *                                  This is presentation-specific data, unrelated to the image set on the server.
 * @object-prop <code>number</code> <code>intTopLeftRow</code> The row of the top left tile/image on the plate.
 * @object-prop <code>number</code> <code>intTopLeftCol</code> The column of the top left tile/image on the plate.
 * @object-prop <code>number</code> <code>intNumDigits</code>  The number of digits in the ...row.column... image
 *                                  name notation.
 * @object-prop <code>number</code> <code>intZoomLevel</code> The current zoom level.
 * @object-prop <code>number</code> <code>intZoomFactor</code> The factor relating consecutive zoom levels.
 * @object-prop <code>number</code> <code>intImageSetZoomPointXPx</code> The pixel location of the zoom point
 *                                  horizontal value on the image set.
 * @object-prop <code>number</code> <code>intImageSetZoomPointYPx</code> The pixel location of the zoom point
 *                                  vertical value on the image set.
 * @object-prop <code>number</code> <code>intPlateZoomPointDistanceXPx</code> The horizontal distance in pixels from
 *                                  the edge of the plate to the zoom point in the center. 
 * @object-prop <code>number</code> <code>intPlateZoomPointDistanceYPx</code> The vertical distance in pixels from
 *                                  the edge of the plate to the zoom point in the center. 
 * @object-prop <code>string</code> <code>strSuffix</code> The image file name suffix, initialized from the
 *                                  'PanoramaProtocol' xml file data.
 * @object-prop <code>string</code> <code>strLayerLevel</code> The starting layer path initialized from the
 *                                  'PanoramaProtocol' xml file data.
 * @object-prop <code>string</code> <code>strLayerLevelTitle</code> The title of a layer as described in the
 *                                   'Layer' xml file.
 * @object-prop <code>string</code> <code>strForwardFastLayerLevel</code> The next layer as described in the
 *                                  'Layer' xml file at 'Fast' rate.
 * @object-prop <code>string</code> <code>strForwardMediumLayerLevel</code> The next layer as described in the
 *                                  'Layer' xml file at 'Medium' rate. 
 * @object-prop <code>string</code> <code>strForwardSlowLayerLevel</code> The next layer as described in the
 *                                  'Layer' xml file at 'Slow' rate.                                                                    
 * @object-prop <code>string</code> <code>strBackwardFastLayerLevel</code> The previous layer as described in the
 *                                  'Layer' xml file at 'Fast' rate.
 * @object-prop <code>string</code> <code>strBackwardMediumLayerLevel</code> The previous layer as described in the
 *                                  'Layer' xml file at 'Medium' rate. 
 * @object-prop <code>string</code> <code>strBackwardSlowLayerLevel</code> The previous layer as described in the
 *                                  'Layer' xml file at 'Slow' rate.                            
 * @author Team GigaToasted (Fall-2005-CMU-NASA/Google-Practicum Subteam) 
 * @version 1.0
 * <br><br>
 */ 


/**
 * Create a <code>ServerImageSet</code> object.
 * @param pObjParent <code>Object</code>  The parent of this object.
 */
function ServerImageSet(pObjParent) {    
    // Properties:
    this.objPanoramaProtocol = null;
    this.objController = pObjParent;
    this.strId = "serverimageset";
    // Passed in from Plate when recalculation is needed
    this.intLeftDistanceMovedPx = null;
    this.intTopDistanceMovedPx = null; 
    this.objPlatefulRequest = null;
    this.intZoomLevel = null;
    this.intZoomFactor = null;
    this.intTopLeftRow = null; // The row of the top left panorama tile/image on the plate
    this.intTopLeftCol = null; // The column of the top left panorama tile/image on the plate
    this.intImageHeightPx = null;
    this.intImageWidthPx = null;
    this.intNumDigits = null;
    this.strSuffix = null;
    this.intImageSetZoomPointXPx = null;
    this.intImageSetZoomPointYPx = null;
    this.intPlateZoomPointDistanceXPx = null;
    this.intPlateZoomPointDistanceYPx = null;
    // Calculated plate data from Panorama data:
    this.intDisplayHeightPx = null; // in pixels
    this.intDisplayWidthPx = null;  // in pixels
    this.intNumImagesHigh = null;
    this.intNumImagesWide = null;
    this.intServerImageSetMaxRow = null;
    this.intServerImageSetMaxCol = null;    
    this.objPlateUpdateResponse = null;
    this.strLayerLevel = null;
    this.strLayerLevelTitle = null;
    this.strForwardFastLayerLevel = null;
    this.strForwardMediumLayerLevel = null;
    this.strForwardSlowLayerLevel = null;
    this.strBackwardFastLayerLevel = null;
    this.strBackwardMediumLayerLevel = null;
    this.strBackwardSlowLayerLevel = null;
    // Methods: 
    this.init = ServerImageSet_init;
    this.initLayers = ServerImageSet_initLayers;
    this.setZoomLevel = ServerImageSet_setZoomLevel;
    this.receiveEvent = ServerImageSet_receiveEvent;
    this.setPlatefulRequest = ServerImageSet_setPlatefulRequest;
    this.calcNewRow = ServerImageSet_calcNewRow;
    this.calcNewCol = ServerImageSet_calcNewCol;
    this.calculatePanoramaLimits = ServerImageSet_calculatePanoramaLimits;
    this.setNumImagesHighWide = ServerImageSet_setNumImagesHighWide;
    this.retrieveImageData = ServerImageSet_retrieveImageData;
    this.getReloadData = ServerImageSet_getReloadData;
    this.zoom = ServerImageSet_zoom;
    this.browseLayerForwardSlow = ServerImageSet_browseLayerForwardSlow;
    this.browseLayerForwardMedium = ServerImageSet_browseLayerForwardMedium;
    this.browseLayerForwardFast = ServerImageSet_browseLayerForwardFast;
    this.browseLayerBackwardSlow = ServerImageSet_browseLayerBackwardSlow;
    this.browseLayerBackwardMedium = ServerImageSet_browseLayerBackwardMedium;
    this.browseLayerBackwardFast = ServerImageSet_browseLayerBackwardFast;
    this.requestImages = ServerImageSet_requestImages;
    this.clearPlateUpdateResponse  = ServerImageSet_clearPlateUpdateResponse;
    this.calculateImageSetZoomPoint = ServerImageSet_calculateImageSetZoomPoint;
    this.calculateTopLeftFromZoomPoint = ServerImageSet_calculateTopLeftFromZoomPoint;
    this.calculateNewPlateXY = ServerImageSet_calculateNewPlateXY;
    // Register events:
    self.gloScope.registerEvent("serverimageset", this, "imageurlcalculator", "onurlsready");
}


/**
 * This method initializes attributes in the <code>ServerImageSet</code>.  This function uses the multiplier 
 * <code>intNumImagesPerSide</code> to determine the number of stacks on a <code>plate</code>.  
 * This multiplier should not exceed 15 to 20,
 * depending on the performance of the machine running the application.
 * @param pObjPanoramaProtocol <code>Object</code> Contains information on panorama protocol
 */
function ServerImageSet_init(pObjPanoramaProtocol) {
    var intLevelIndex = 0;

    this.objPanoramaProtocol = pObjPanoramaProtocol; 
    this.intZoomLevel = intLevelIndex;
    this.intZoomFactor = parseInt(pObjPanoramaProtocol.getZoomFactor());  
    this.intImageHeightPx = parseInt(pObjPanoramaProtocol.getTileHeight(this.intZoomLevel));
    this.intImageWidthPx = parseInt(pObjPanoramaProtocol.getTileWidth(this.intZoomLevel));
    this.intTopLeftRow = 0; // The row of the top left tile/image on the plate
                            // we start at 0,0
    this.intTopLeftCol = 0; // The column of the top left tile/image on the plate
                               // We start at 0,0
    this.calculatePanoramaLimits(this.intZoomLevel);
        // Call the size determination method here
    this.setNumImagesHighWide(); 
    this.intDisplayHeightPx = this.intImageHeightPx * this.intNumImagesHigh;
    this.intDisplayWidthPx = this.intImageWidthPx * this.intNumImagesWide;
    this.intNumDigits = parseInt(pObjPanoramaProtocol.getNumDigits(this.intZoomLevel));
    this.strSuffix = pObjPanoramaProtocol.getSuffix(this.intZoomLevel);
    this.strLayerLevel = pObjPanoramaProtocol.getStartLayer();
    /* Check if you need to get image layer data */
    if (this.strLayerLevel != "") {
        this.objController.getXmlDescriptionOfLayer(this.strLayerLevel);
    }
    this.objPlateUpdateResponse = new PlateUpdateResponse(false);
    // These two values are used to let the plate know how big it should make itself.
    this.objPlateUpdateResponse.intNumColsInCompositeImage = this.intNumImagesWide;	
    this.objPlateUpdateResponse.intNumRowsInCompositeImage = this.intNumImagesHigh;
    this.objPlatefulRequest = new PlatefulRequest();
}


/**
 * This method sets the display (plate) size  attributes in the <code>ServerImageSet</code>. The number
 * of images high or wide is determined to be the lesser of the intNumImagesPerSide value from the 
 * PanoramaProtocol, and the Panorama max row or column value.  This means that if a panorama at a particular
 * zoom level is smaller than the standard plate size, we will tailor our size values to the smaller panorama
 * value.
 */
function ServerImageSet_setNumImagesHighWide() {
	var intProposedNumImagesHigh, intProposedNumImagesWide;
	
	intProposedNumImagesHigh = this.intServerImageSetMaxRow;
	intProposedNumImagesWide = this.intServerImageSetMaxCol;
	
     if (intProposedNumImagesHigh <= this.objController.intNumImagesPerSide) {
        this.intNumImagesHigh = this.intServerImageSetMaxRow; 
    } else {
        this.intNumImagesHigh = this.objController.intNumImagesPerSide;
    }

    if (intProposedNumImagesWide <= this.objController.intNumImagesPerSide) {
        this.intNumImagesWide = this.intServerImageSetMaxCol; 
    } else {
        this.intNumImagesWide = this.objController.intNumImagesPerSide;
    }
}



/**
 * This method initializes attributes in the <code>ServerImageSet</code>.  This function uses the multiplier 
 * <code>intNumImagesPerSide</code> to determine the number of stacks on a <code>plate</code>.  
 * This multiplier should not exceed 15 to 20,
 * depending on the performance of the machine running the application.
 * @param pObjPanoramaLayerProtocol <code>Object</code> Contains information on layer protocol
 */
function ServerImageSet_initLayers(pObjPanoramaLayerProtocol) {
    this.strBackwardSlowLayerLevel = pObjPanoramaLayerProtocol.getBackwardSlowPath();
    this.strBackwardMediumLayerLevel = pObjPanoramaLayerProtocol.getBackwardMediumPath();
    this.strBackwardFastLayerLevel = pObjPanoramaLayerProtocol.getBackwardFastPath();
    this.strForwardSlowLayerLevel = pObjPanoramaLayerProtocol.getForwardSlowPath();
    this.strForwardMediumLayerLevel = pObjPanoramaLayerProtocol.getForwardMediumPath();
    this.strForwardFastLayerLevel = pObjPanoramaLayerProtocol.getForwardFastPath();
    this.strLayerLevelTitle = pObjPanoramaLayerProtocol.getLayerTitle();
}


/**
 * This method is called by <code>Plate</code> to perform the initial image load.  It fills the protocol
 * object communicated to ImageURLCalculator, and fires the event requesting a new image URL list.  When this
 * method returns, the PlateUpdateResponse object will be filled and may be accessed by <code>Plate</code> to 
 * obtain the new image data.
 * @return <code>PlatefulUpdateResponse</code> An object representing the response to a protocol request for 
 *                                             an update of the <code>plate</code> object's image data.
 */
function ServerImageSet_requestImages() {

    this.setPlatefulRequest( this.objPlatefulRequest, this.intZoomLevel, this.strLayerLevel );
    self.Environment.handleEvent(this.strId, 'onnewimageset', this.objPlatefulRequest);
    return this.objPlateUpdateResponse;
}


/**
 * This method sets the <code>intZoomLevel</code> attribute in <code>ServerImageSet</code>.  This is called by
 * <code>Plate</code> after the user has changed zoom levels.
 * @param pIntZoomLevel <code>number</code> The current zoom level.
 */
function ServerImageSet_setZoomLevel(pIntZoomLevel) {
    this.intZoomLevel = pIntZoomLevel;    
 }


/**
 * This method receives any events that this object listens to and 
 * routes them to the proper methods. 
 * @param pStrEventSourceElementId <code>string</code> The i.d. of the document object model from which the event
 *                                 sprang.
 * @param pStrEventType <code>string</code> The event type of the object (e.g., 'onclick', etc).
 * @param pObjEvent <code>Object</code> The event object itself.
 */
function ServerImageSet_receiveEvent(pStrEventSourceElementId, pStrEventType, pObjEvent) { 
    switch (pStrEventSourceElementId) {
    case "imageurlcalculator" : 
        switch (pStrEventType) {
        case "onurlsready" : 
            this.retrieveImageData( pObjEvent );
            break;
        }
    }
}


/**
 * This method calculates the limits of the panorama at the parameterized zoom level.
 * @param pIntZoomIndex <code>number</code> The current zoom level.
 */
function ServerImageSet_calculatePanoramaLimits(pIntZoomLevel) {
    this.intServerImageSetMaxRow = 
			Math.ceil(parseInt(this.objPanoramaProtocol.getMaxY(pIntZoomLevel)) / this.intImageHeightPx);
    this.intServerImageSetMaxCol = 
			Math.ceil(parseInt(this.objPanoramaProtocol.getMaxX(pIntZoomLevel)) / this.intImageWidthPx);
}


/**
 * This method sets the <code>PlatefulRequest</code> elements before the object is passed
 * to the <code>ImageURLCalculator</code>.
 * @param pObjPlatefulRequest <code>PlatefulRequest</code> The protocol object communicated 
 *                            to <code>ImageURLCalculator</code>.
 * @param pIntZoomIndex <code>number</code> The current zoom level.
 * @param pStrLayerLevel <code>number</code> The current zoom level.
 */
function ServerImageSet_setPlatefulRequest(pObjPlatefulRequest, pIntZoomIndex, pStrLayerLevel ) {
    var intNumTilesPerDisplay = (this.intNumImagesHigh *this.intNumImagesWide);

    // Fill the PlatefulRequest
    pObjPlatefulRequest.setNumTilesPerPlate(intNumTilesPerDisplay);
    pObjPlatefulRequest.setTopLeftRow(this.intTopLeftRow);
    pObjPlatefulRequest.setTopLeftColumn(this.intTopLeftCol);
    pObjPlatefulRequest.setNumTilesHigh(this.intNumImagesHigh);
    pObjPlatefulRequest.setNumTilesWide(this.intNumImagesWide);
    pObjPlatefulRequest.setNumDigits(this.intNumDigits);
    pObjPlatefulRequest.setLevel(pIntZoomIndex);
    pObjPlatefulRequest.setSuffix(this.strSuffix);
    pObjPlatefulRequest.setStartLayer(pStrLayerLevel);
}

/**
 * This method calculates the  <code>intTopLeftRow</code> value which represents the 
 * row value of the server image tile which is in the top left position on the <code>Plate</code>.
 * This function takes the y (top) distance moved and performs the calculations necessary to determine
 * the new tile position.  A tile is Panorama-speak for a <code>Stack</code>.  The (row, column) value 
 * indicates tile placement in the server-side Panorama data and used to get the URLs for a plate load.
 * If a new plate load is not possible (we may be at the end of the data), this is indicated in protocol
 *  object communicated back to the <code>Plate</code>.
 * @param pIntTopDistanceMovedPx <code>number</code> The directed horizontal distance the <code>Plate</code> since
 *                                its last reload.
 * @param pObjPlateUpdateResponse <code>PlateUpdateResponse</code> The protocol object used to communicate with the
 *                                <code>Plate</code>.
 * @return pObjPlateUpdateResponse <code>PlateUpdateResponse</code> The protocol object passed back to
 *                                 <code>Plate</code>.
 */
function ServerImageSet_calcNewRow( pIntTopDistanceMovedPx, pObjPlateUpdateResponse ) {
    var intNumTilesMovedRows, fltTilesMovedLeft, 
    intTooFar, booMoveIt, intOffsetTop, intProposedTopLeftRow;

    // This adjustment value is used when we are coming close to the end of the plate
    // and can only do a 'partial' load.  This value tells us how far to 'back up'
    // to start this plate load.
	pObjPlateUpdateResponse.intAdjustmentPx = 0;
    // Make sure new top and left values are in range    
	// Calculate Row values

    fltTilesMovedTop = pIntTopDistanceMovedPx / this.intImageHeightPx;
    intOffsetTop = pIntTopDistanceMovedPx % this.intImageHeightPx;
    if ( intOffsetTop > 0 ) {
		intOffsetTop = this.intImageHeightPx - intOffsetTop;
    } else if ( intOffsetTop < 0 ) {
		intOffsetTop = (-1)*(this.intImageHeightPx + intOffsetTop);
    } else {
		intOffsetTop = 0;
    }
    pObjPlateUpdateResponse.intYOffsetPx = intOffsetTop;
    intNumTilesMovedRows = (fltTilesMovedTop < 0) ? Math.floor(fltTilesMovedTop) :
        Math.ceil(fltTilesMovedTop);
	// Set the move boolean to false outside the if, then only set to true inside
	
	booMoveIt = false;
    if ( ((this.intTopLeftRow <= 0) && (pIntTopDistanceMovedPx >= 0)) ||  
		((this.intTopLeftRow  + this.intNumImagesHigh) >= this.intServerImageSetMaxRow ) && (pIntTopDistanceMovedPx < 0) ){
        // Do not move
        ;

    } else {
		booMoveIt = true;
		intProposedTopLeftRow = this.intTopLeftRow - intNumTilesMovedRows;
		// If the bottom of the plate is past the end of the panorama with the new top left values...
        if( (intProposedTopLeftRow + this.intNumImagesHigh) > this.intServerImageSetMaxRow ) {
			// find out how much too far the bottom of the plate would move and adjust back by that amount
			intTooFar = (intProposedTopLeftRow + this.intNumImagesHigh) - this.intServerImageSetMaxRow;
			this.intTopLeftRow = intProposedTopLeftRow - intTooFar;
			pObjPlateUpdateResponse.intAdjustmentPx = (-1)*intTooFar * this.intImageHeightPx;
        } else {
			this.intTopLeftRow -= intNumTilesMovedRows;
        }
        // If the top of the plate is past the end of the panorama data with the new top left values...
        if( intProposedTopLeftRow < 0){
			intTooFar = intProposedTopLeftRow;
			this.intTopLeftRow = 0;
			pObjPlateUpdateResponse.intAdjustmentPx = (-1)*intTooFar * this.intImageHeightPx;
        }
    }
    pObjPlateUpdateResponse.booHasNewImages = booMoveIt;
    return pObjPlateUpdateResponse;
}


/**
 * This method calculates the  <code>intTopLeftCol</code> value which represent the 
 * column values of the server image tile which is in the top left position on the <code>Plate</code>.
 * This function takes the x (left) distance moved and performs the calculations necessary to determine
 * the new tile position.  A tile is Panorama-speak for a <code>Stack</code>.  The (row, column) value 
 * indicates tile placement in the server-side Panorama data and used to get the URLs for a plate load.
 * If a new plate load is not possible (we may be at the end of the data), this is indicated in protocol
 *  object communicated back to the <code>Plate</code>.
 * @param pIntLeftDistanceMovedPx <code>number</code> The directed horizontal distance the <code>Plate</code> since
 *                                its last reload.
 * @param pObjPlateUpdateResponse <code>PlateUpdateResponse</code> The protocol object used to communicate with the
 *                                <code>Plate</code>.
 * @return pObjPlateUpdateResponse <code>PlateUpdateResponse</code> The protocol object passed back to
 *                                 <code>Plate</code>.
 */    
function ServerImageSet_calcNewCol(pIntLeftDistanceMovedPx, pObjPlateUpdateResponse ) { 
    var  intNumTilesMovedCols, fltTilesMovedLeft, 
    intTooFar, booMoveIt, intOffsetLeft, intProposedTopLeftCol;   
    // Calculate Column values
    
    // This adjustment value is used when we are coming close to the end of the plate
    // and can only do a 'partial' load.  This value tells us how far to 'back up'
    // to start this plate load.
    pObjPlateUpdateResponse.intAdjustmentPx = 0;
    // Calc the raw movement value in number of tiles.
    fltTilesMovedLeft = pIntLeftDistanceMovedPx / this.intImageWidthPx;
    // Calculate the partial tile value we'll need to adjust our final presentation movement
    intOffsetLeft = (pIntLeftDistanceMovedPx % this.intImageWidthPx);
    if ( intOffsetLeft > 0 ) {
		intOffsetLeft = this.intImageWidthPx - intOffsetLeft;
    } else if ( intOffsetLeft < 0 ) {
		intOffsetLeft = (-1)*(this.intImageWidthPx + intOffsetLeft);
    } else {
		intOffsetLeft = 0;
    }
    pObjPlateUpdateResponse.intXOffsetPx = intOffsetLeft;
    // Determine the 'outside' number of columns to move
    intNumTilesMovedCols = (fltTilesMovedLeft < 0) ? Math.floor(fltTilesMovedLeft) :        
        Math.ceil(fltTilesMovedLeft);

	booMoveIt = false;
    if ( ((this.intTopLeftCol <= 0) && (pIntLeftDistanceMovedPx >= 0)) ||
		((this.intTopLeftCol  + this.intNumImagesWide) >= this.intServerImageSetMaxCol ) && (pIntLeftDistanceMovedPx < 0)) {
        // Do not move
        ;
    } else {
		intProposedTopLeftCol = this.intTopLeftCol - intNumTilesMovedCols;
		booMoveIt = true;
        // If the right side of the plate is past the end of the panorama with the new top Left values....
		if( (intProposedTopLeftCol + this.intNumImagesWide) > this.intServerImageSetMaxCol ) {
			// find out how much too far the right side of the plate would move and adjust back by that amount
			intTooFar = (intProposedTopLeftCol + this.intNumImagesWide) - this.intServerImageSetMaxCol;
			this.intTopLeftCol = intProposedTopLeftCol - intTooFar;
			pObjPlateUpdateResponse.intAdjustmentPx = (-1)*intTooFar * this.intImageWidthPx;
        } else {
			// Otherwise, just move
			this.intTopLeftCol -= intNumTilesMovedCols;
        }
        // If the left side of the plate is past the end of the panorama data with the new top left values...
        if( intProposedTopLeftCol < 0){
			intTooFar = intProposedTopLeftCol;
			this.intTopLeftCol = 0;
			pObjPlateUpdateResponse.intAdjustmentPx = (-1)*intTooFar * this.intImageWidthPx;
        }
    }
    pObjPlateUpdateResponse.booHasNewImages = booMoveIt;
    return pObjPlateUpdateResponse;
}


/**
 * This method is called from the <code>Plate</code> movement methods when a reload is necessary.  
 * This function handles the request from the <code>Plate</code> for new image urls.  If there are new images
 * available to load, this function will return a protocol object to <code>Plate</code> containing the new URL list.
 * @param pObjPlateUpdateRequest <code>PlateUpdateRequest</code> The protocol object sent from <code>Plate</code>
 *                               requesting an image update.
 * @return <code>PlateUpdateResponse</code> The protocol object returned to <code>Plate</code>.
 */
function ServerImageSet_getReloadData( pObjPlateUpdateRequest ) {
    var intLeftDistanceMovedPx = pObjPlateUpdateRequest.intLeftDistanceMovedPx, 
        intTopDistanceMovedPx = pObjPlateUpdateRequest.intTopDistanceMovedPx;
        
    // Clear the old data from the objPlateUpdateResponse
    this.clearPlateUpdateResponse();
    if( intLeftDistanceMovedPx ) {
		this.objPlateUpdateResponse = this.calcNewCol( intLeftDistanceMovedPx, this.objPlateUpdateResponse); 
    }
    if( intTopDistanceMovedPx ) {
		this.objPlateUpdateResponse = this.calcNewRow( intTopDistanceMovedPx, this.objPlateUpdateResponse); 
    }
    if( this.objPlateUpdateResponse.booHasNewImages ) {
		//if we move, then we ask for new URLs
		this.setPlatefulRequest( this.objPlatefulRequest, this.intZoomLevel, this.strLayerLevel );
		// Fire the event
		self.Environment.handleEvent(this.strId, 'onnewimageset', this.objPlatefulRequest);
		
		// Since we KNOW that the event handling mechanism is not truly asynchronous,
		// we know the objPlateUpdateResponse has been filled by the time we get to this point.
    }
    return this.objPlateUpdateResponse;
}


/**
 * This function is called to retrieve the data from the <code>PlatefulResponse</code>
 * returned by the <code>ImageUrlCalculator</code>.  This data is put into a <code>PlateUpdateResponse</code>
 * object to be returned to the <code>Plate</code>.
 * @param pObjPlatefulResponse <code>Object</code>The object returned from the <code>ImageURLCalculator</code>.
 */
function ServerImageSet_retrieveImageData( pObjPlatefulResponse ) {    
    this.objPlateUpdateResponse.booHasNewImages = pObjPlatefulResponse.booHasNewImages;
    this.objPlateUpdateResponse.objNewImagesList = pObjPlatefulResponse.objUrlList;
}


/**
 * This function sets the zoom level to the new value passed in from the <code>Plate</code>, then
 * calculates the limits of the server data at the new zoom level, and requests the new image urls.
 * @param pObjPlateZoomRequest <code>object</code> The zoom request object.
 */
function ServerImageSet_zoom( pObjPlateZoomRequest ) {  
	this.clearPlateUpdateResponse(); 
    this.intZoomLevel = pObjPlateZoomRequest.intZoomLevel;
    this.calculatePanoramaLimits(this.intZoomLevel);
    this.setNumImagesHighWide();
    this.objPlateUpdateResponse.intNumColsInCompositeImage = this.intNumImagesWide;	
    this.objPlateUpdateResponse.intNumRowsInCompositeImage = this.intNumImagesHigh;
    this.calculateImageSetZoomPoint( pObjPlateZoomRequest );
    this.calculateTopLeftFromZoomPoint( pObjPlateZoomRequest );
    this.calculateNewPlateXY( pObjPlateZoomRequest );
    this.setPlatefulRequest( this.objPlatefulRequest, this.intZoomLevel, this.strLayerLevel );
    self.Environment.handleEvent(this.strId, 'onnewimageset', this.objPlatefulRequest);
    
    return this.objPlateUpdateResponse;
}


/**
 * This function browses the layer level to the next layer forward 
 */
function ServerImageSet_browseLayerForwardSlow() {
    if (this.strForwardSlowLayerLevel != null) {
        this.strLayerLevel = this.strForwardSlowLayerLevel;
        this.objController.getXmlDescriptionOfLayer(this.strLayerLevel);
        this.setPlatefulRequest( this.objPlatefulRequest, this.intZoomLevel, this.strLayerLevel );
        self.Environment.handleEvent(this.strId, 'onnewimageset', this.objPlatefulRequest);
    }
}


/**
 * This function browses the layer level to the medium layer forward 
 */
function ServerImageSet_browseLayerForwardMedium() {
    if (this.strForwardMediumLayerLevel != null) {
        this.strLayerLevel = this.strForwardMediumLayerLevel;
        this.objController.getXmlDescriptionOfLayer(this.strLayerLevel);
        this.setPlatefulRequest( this.objPlatefulRequest, this.intZoomLevel, this.strLayerLevel );
        self.Environment.handleEvent(this.strId, 'onnewimageset', this.objPlatefulRequest);
    }
}


/**
 * This function browses the layer level to the fast layer forward 
 */
function ServerImageSet_browseLayerForwardFast() {
    if (this.strForwardFastLayerLevel != null) {
        this.strLayerLevel = this.strForwardFastLayerLevel;
        this.objController.getXmlDescriptionOfLayer(this.strLayerLevel);
        this.setPlatefulRequest( this.objPlatefulRequest, this.intZoomLevel, this.strLayerLevel );
        self.Environment.handleEvent(this.strId, 'onnewimageset', this.objPlatefulRequest);
    }
}


/**
 * This function browses the layer level to the next layer backward 
 */
function ServerImageSet_browseLayerBackwardSlow() {
    if (this.strBackwardSlowLayerLevel != null) {
        this.strLayerLevel = this.strBackwardSlowLayerLevel;
        this.objController.getXmlDescriptionOfLayer(this.strLayerLevel);
        this.setPlatefulRequest( this.objPlatefulRequest, this.intZoomLevel, this.strLayerLevel );
        self.Environment.handleEvent(this.strId, 'onnewimageset', this.objPlatefulRequest);
    }
}


/**
 * This function browses the layer level to the medium layer backward 
 */
function ServerImageSet_browseLayerBackwardMedium() {
    if (this.strBackwardMediumLayerLevel != null) {
        this.strLayerLevel = this.strBackwardMediumLayerLevel;
        this.objController.getXmlDescriptionOfLayer(this.strLayerLevel);
        this.setPlatefulRequest( this.objPlatefulRequest, this.intZoomLevel, this.strLayerLevel );
        self.Environment.handleEvent(this.strId, 'onnewimageset', this.objPlatefulRequest);
    }
}


/**
 * This function browses the layer level to the fast layer backward 
 */
function ServerImageSet_browseLayerBackwardFast() {
    if (this.strBackwardFastLayerLevel != null) {
        this.strLayerLevel = this.strBackwardFastLayerLevel;
        this.objController.getXmlDescriptionOfLayer(this.strLayerLevel);
        this.setPlatefulRequest( this.objPlatefulRequest, this.intZoomLevel, this.strLayerLevel );
        self.Environment.handleEvent(this.strId, 'onnewimageset', this.objPlatefulRequest);
    }
}


/**
 * This function clears the <code>objPlateUpdateResponse</code> object. 
 */
function ServerImageSet_clearPlateUpdateResponse() {
    this.objPlateUpdateResponse.booHasNewImages  = false;    
    this.objPlateUpdateResponse.objNewImagesList = null;
    this.objPlateUpdateResponse.intXOffsetPx     = 0;
    this.objPlateUpdateResponse.intYOffsetPx     = 0;
    this.objPlateUpdateResponse.intAdjustmentPx  = 0;  
}

/**
 * This function uses the values for the center of the console (the point on which we are zooming)
 * and calculates the new x and y values for the zoom point at the new zoom level.  These values indicate how far
 * from the edge of the Image Set the zoom point is at the new zoom level.
 * @param pObjPlateZoomRequest <code>object</code> The zoom request object.
 */
function ServerImageSet_calculateImageSetZoomPoint( pObjPlateZoomRequest ) { 	
	if ( pObjPlateZoomRequest.strZoomDirection == "in")	{
		this.intImageSetZoomPointXPx = 
			(pObjPlateZoomRequest.intCenterXValuePx + (this.intImageWidthPx *this.intTopLeftCol )) * this.intZoomFactor;
		this.intImageSetZoomPointYPx = 
			(pObjPlateZoomRequest.intCenterYValuePx + (this.intImageHeightPx*this.intTopLeftRow )) * this.intZoomFactor;
	} else if ( pObjPlateZoomRequest.strZoomDirection == "out") {
		this.intImageSetZoomPointXPx = 
			(pObjPlateZoomRequest.intCenterXValuePx + (this.intImageWidthPx *this.intTopLeftCol )) / this.intZoomFactor;
		this.intImageSetZoomPointYPx = 
			(pObjPlateZoomRequest.intCenterYValuePx + (this.intImageHeightPx*this.intTopLeftRow )) / this.intZoomFactor;
	}
	//if( (this.intImageSetZoomPointXPx < 0)||(this.intImageSetZoomPointXPx > (this.intServerImageSetMaxCol*this.intImageWidthPx)) ) {
	//	alert("ServerImageSet_calculateImageSetZoomPoint: Zoom point X is outside panorama limits!!"); 
	//}
	
}


/**
 * This function calculates the new top left tile to be loaded on the plate.
 * The plate load is approximately centered on the zoom point.
 * @param pObjPlateZoomRequest <code>object</code> The zoom request object.
 */
function ServerImageSet_calculateTopLeftFromZoomPoint( pObjPlateZoomRequest ) { 

	var intZoomPointTilesY, intZoomPointTilesX, 
	intProposedTop, intProposedLeft, intTooFar;	

	// If the plate and panorama widths are the same then our top left tile position is still 0,0
	// otherwise, the zoom point should be approximately centered on the plate
	
	if (this.intNumImagesHigh < this.objController.intNumImagesPerSide) {
		// this will happen when the image set is smaller than the configured plate size
		intProposedTop     = 0;
	} else {
		intZoomPointTilesY  = Math.floor(this.intImageSetZoomPointYPx / this.intImageHeightPx);
		intProposedTop      = intZoomPointTilesY - Math.floor(this.intNumImagesHigh / 2);
		if( intProposedTop < 0 ) {
			intProposedTop = 0;
		}
	}
	if (this.intNumImagesWide < this.objController.intNumImagesPerSide) {
		// this will happen when the image set is smaller than the configured plate size
		intProposedLeft    = 0;
		
	} else {
		intZoomPointTilesX  = Math.floor(this.intImageSetZoomPointXPx / this.intImageWidthPx);
		intProposedLeft     = intZoomPointTilesX - Math.floor(this.intNumImagesWide / 2);
		if( intProposedLeft < 0 ) {
			intProposedLeft = 0;
		}

		// Adjust the Proposed Top tile value if the proposed value will run the plate off the end 
		// of the image set.
		if( (intProposedTop + this.intNumImagesHigh) > this.intServerImageSetMaxRow ) {
			// find out how much too far the bottom of the plate would move and adjust back by that amount
			intTooFar = this.intServerImageSetMaxRow - (intProposedTop + this.intNumImagesHigh);
			intProposedTop = intProposedTop + intTooFar;
        }
        
        // If the right side of the plate is past the end of the panorama with the new top Left values....
		if( (intProposedLeft + this.intNumImagesWide) > this.intServerImageSetMaxCol ) {
			// find out how much too far the right side of the plate would move and adjust back by that amount
			intTooFar = this.intServerImageSetMaxCol - (intProposedLeft + this.intNumImagesWide);
			intProposedLeft = intProposedLeft + intTooFar;
        } 
		
	}
	this.intTopLeftCol = intProposedLeft;
	this.intTopLeftRow = intProposedTop;
}

/**
 * This function calculates the new distance from the edge of the plate to the zoom point
 * in the center.  These are NOT the final x,y presentation values.
 * Ideally this value should be passed back to the plate through a protocol object.
 * @param pObjPlateZoomRequest <code>object</code> The zoom request object.
 */
function ServerImageSet_calculateNewPlateXY( pObjPlateZoomRequest ) { 
	this.intPlateZoomPointDistanceXPx = (this.intImageSetZoomPointXPx - (this.intImageWidthPx *this.intTopLeftCol ));
	this.intPlateZoomPointDistanceYPx = (this.intImageSetZoomPointYPx - (this.intImageHeightPx*this.intTopLeftRow ));
}
