﻿/// Copyright (c) Microsoft Corporation.  All rights reserved.

//Useful sites for getting updates for Silverlight specific Javascript:
//http://code.msdn.microsoft.com/SLsupportedUA/
//http://code.msdn.microsoft.com/silverlightjs

if (!window.Microsoft)
{
    window.Microsoft = {};
    window.Microsoft.Photosynth = {};
    window.Microsoft.Photosynth.Math = {};
}

/**
* Configuration settings for Photosynth related objects
*/
Microsoft.Photosynth.Config =
{
    //The minimum required runtime to run the Photosynth viewer
    minimumSilverlightVersion: "3.0.40818.0",

    //Max allowed size of image used for install and delay loading
    maxImageDimension: 512,

    //The location of the Photosynth Silverlight XAP file
    xapSource: "http://photosynth.net/silverlight/photosynth2.xap",

    //The location of the photosynth webservice
    psWebService: "http://photosynth.net/",

    //The URL to get more information
    silverlightInstallUrl: "http://www.microsoft.com/silverlight/resources/install.aspx",

    //The URL of the image which shows the "click to view synth content"
    clickToViewImageUrl: "http://photosynth.net/inc/images/view_synth_btn.png",

    //The image used as a background to the text boxes in the install messages
    backgroundTexture: "http://photosynth.net/api/slbackground_black.png",

    //boolean value to determine if padding is needed for the Silverlight install message
    //should only be used on Embed.aspx page
    useLeftPadding: false
};

/**
* Strings used in the viewer
*/
Microsoft.Photosynth.Strings =
{
    ClickToView: "Click to view the synth",
    PleaseRefresh: "When the Silverlight installation is finished<br>reload this page to view the synth.",
    PleaseRestart: "When the Silverlight installation is finished<br>restart the browser to view the synth."
};

//An array of all the current viewer instances on the page
Microsoft.Photosynth.viewers = new Array();

//A unique id assigned to different photosynth object instances
Microsoft.Photosynth._nextId = 0;


//==== Enumerations ===================================================

/**
* Different methods of controlling the viewers navigation
*/
Microsoft.Photosynth.NavigationType =
{
    //The viewer will navigate immediately to the target settings
    Immediate: 0,

    //The viewer will navigate to the target setttings in the most direct route
    Direct: 1,

    //The viewer will move from the current image to the target image through
    // a pre computed path
    DefaultPath: 2
};

/**
* The various display modes of the viewer
*/
Microsoft.Photosynth.DisplayMode =
{
    //The viewer is in 2D mode
    TwoD: 1,

    //The viewer is in 3D mode
    ThreeD: 2
};

/**
* The differing states of the pointcloud
*/
Microsoft.Photosynth.PointCloudMode =
{
    //Only images are displayed, no pointcloud
    ImagesOnly: 1,

    //Images and pointcloud are displayed
    ImagesAndPointCloud: 2,

    //Only the pointcloud is displayed
    PointCloudOnly: 3
};


//==== C# -> Javascript callbacks =====================================

/**
* Called when a viewer instance has loaded.  This is called once it has loaded but before
* any state has been set i.e. camera position, rotation etc
*
* @viewer the <object> tag corresponding to the viewer that loaded
*/
Microsoft.Photosynth.viewerLoadCollectionCompletedCallback = function(viewer)
{
    var viewerInstance = Microsoft.Photosynth._getViewerInstance(viewer.id);
    if (viewerInstance == null)
    {
        return;
    }

    viewerInstance._onLoadCollectionCompleted(viewer);
}


//=====================================================================

/**
* When called opens a new window that contains the Silverlight install information
*/
Microsoft.Photosynth.openSilverlightInfoWindow = function()
{
    window.open(Microsoft.Photosynth.Config.silverlightInstallUrl, "_blank");
}

/**
* Helper method used to generate unique ids for Photosynth Javascript objects
*/
Microsoft.Photosynth._getNextId = function()
{
    Microsoft.Photosynth._nextId++;
    return Microsoft.Photosynth._nextId;
}

/**
* Returns a viewer instance with the specified id, null if no match found
*
* @viewerId the id of the viewer to look for in the DOM
*/
Microsoft.Photosynth._getViewerInstance = function(viewerId)
{
    for (var i in Microsoft.Photosynth.viewers)
    {
        if (Microsoft.Photosynth.viewers[i].id == viewerId)
        {
            return Microsoft.Photosynth.viewers[i];
        }
    }
    return null;
}

/**
* Registers a viewer instance with the global Photosynth class
*
* @viewer the Photosynth.Viewer instance to register
*/
Microsoft.Photosynth.registerViewer = function(viewer)
{
    viewer.id = "photosynth_viewer_" + Microsoft.Photosynth._getNextId();
    Microsoft.Photosynth.viewers.push(viewer);
}

/**
* Called when the user clicks on the delay loading HTML.  Find the desired
* viewer instance and inform it user wants to load the viewer
*
* @viewerId the id of the viewer to look for in the DOM
*/
Microsoft.Photosynth.delayLoadClicked = function(viewerId)
{
    var viewer = Microsoft.Photosynth._getViewerInstance(viewerId);
    if (viewer == null)
    {
        return;
    }
    viewer.onDelayLoadClicked();
}

/**
* Called when the user clicks on the install HTML to install the Silverlight runtime
*
* @viewerId the id of the viewer to look for in the DOM
*/
Microsoft.Photosynth.installClicked = function(viewerId)
{
    //Put all viewers into install clicked state
    for (var i in Microsoft.Photosynth.viewers)
    {
        Microsoft.Photosynth.viewers[i].onInstallClicked();
    }
}

/**
* When called will resize the image to its parent containers dimension
* respecting the aspect ratio of the image
*
* @imageId the id of the image in the DOM
* @ownerId the id of the parent element in the DOM
* @maxDimension the maximum size allowed for either the width or the height
*/
Microsoft.Photosynth.resizeImageToParent = function(viewerId, imageId, ownerId, maxDimension)
{
    var image = document.getElementById(imageId);
    var owner = document.getElementById(ownerId);

    var borderWidth = 5;
    var imageWidth = image.width;
    var imageHeight = image.height;
    var ownerWidth = owner.offsetWidth - borderWidth * 2;
    var ownerHeight = owner.offsetHeight - borderWidth * 2;

    var scaleFactor = 1;
    if (imageWidth > ownerWidth)
    {
        scaleFactor = ownerWidth / imageWidth;
    }
    if ((imageHeight * scaleFactor) > ownerHeight)
    {
        scaleFactor = ownerHeight / imageHeight;
    }
    var maxSideLength = (imageWidth > imageHeight) ? imageWidth * scaleFactor : imageHeight * scaleFactor;
    if (maxSideLength > maxDimension)
    {
        scaleFactor = scaleFactor * (maxDimension / maxSideLength);
    }

    image.width = image.width * scaleFactor;
    image.style.visibility = "visible";

    var imgClick = document.getElementById(viewerId + "_messageDisplay");
    var pageLocation = document.location.href;
    if (imgClick != null)
    {
        var topPadding = "0";
        var leftPadding = "0";

        if (scaleFactor > 0)
        {
            topPadding = (imageHeight * scaleFactor) - 80;
        }

        imgClick.style.paddingTop = topPadding + "px";

        if (Microsoft.Photosynth.Config.useLeftPadding == true)
        {
            if (((ownerWidth / 2) - ownerWidth) > 0)
            {
                leftPadding = ((ownerWidth) / 2) - ownerWidth;
            }
            imgClick.style.paddingLeft = leftPadding + "px";
        }
    }
}

//==== Photosynth.Viewer ==============================================

/*
* Represents an instance of the Photosynth viewer class
*
* @ownerId the id of the HTML element where the viewer will be placed inside
*/
Microsoft.Photosynth.Viewer = function(ownerId)
{
    var self = this;

    /**
    *  Initializes the viewer instance
    */
    this._init = function()
    {
        //register the viewer with the global handler
        self.id = '';
        Microsoft.Photosynth.registerViewer(self);


        self._viewerInstance = null;
        self.viewport3D = null;
        self._ownerId = ownerId;

        //This is the element the viewer will live inside
        self._owner = document.getElementById(self._ownerId);

        //Events exposed for users to attach to
        self.loadCollectionCompleted = null;
        self.displayModeChanged = null;
        self.pointCloudModeChanged = null;
        self.moveToImageCompleted = null;
        self.beforeToggleFullScreen = null;
    };

    /*
    * Called when the viewer loads
    * 
    * @viewerObject the <object> tag associated with the viewer
    */
    this._onLoadCollectionCompleted = function(viewerObject)
    {

        //The DOM object element associated with the viewer, gives us access to the C# Silverlight viewer
        self._viewerInstance = viewerObject;

        //Call back into C# to register the Viewer javascript object with
        //the JS bridge so it knows who it is talking to
        self._viewerInstance.content.api.registerViewer(self);

        //Hook up for events exposed by the viewer
        self.viewport3D = self._viewerInstance.content.api.viewport3D;
        self._viewerInstance.content.api.displayModeChanged = self._onDisplayModeChanged;
        self._viewerInstance.content.api.pointCloudModeChanged = self._onPointCloudModeChanged;
        self._viewerInstance.content.api.moveToImageCompleted = self._onMoveToImageCompleted;
        self._viewerInstance.content.api.beforeToggleFullScreen = self._onBeforeToggleFullScreen;

        //Call user defined event if present
        if (self.loadCollectionCompleted != null)
        {
            self.loadCollectionCompleted(self);
        }
    };

    /**
    * Called when the display mode changes in the viewer.
    * 
    */
    this._onDisplayModeChanged = function(viewer)
    {
        //Call any user defined events if present
        if (self.displayModeChanged != null)
        {
            self.displayModeChanged(viewer);
        }
    };

    /**
    *  Called when the viewers point cloud mode changes
    */
    this._onPointCloudModeChanged = function(viewer)
    {
        if (self.pointCloudModeChanged != null)
        {
            self.pointCloudModeChanged(viewer);
        }
    };

    /**
    * Called when the viewer moves to an image, will not fire on zooming or object/camera rotation
    */
    this._onMoveToImageCompleted = function(viewer)
    {
        if (self.moveToImageCompleted != null)
        {
            self.moveToImageCompleted(viewer);
        }
    };

    /**
    * Called when the viewer is about the enter fullscreen mode
    */
    this._onBeforeToggleFullScreen = function(viewer, args)
    {
        if (self.beforeToggleFullScreen != null)
        {
            self.beforeToggleFullScreen(viewer, args);
        }
    };

    /**
    *  When called will return a string that contains information that allows someone to call
    *  setFromPoseState and recreate the view they were looking at.
    */
    this.getPoseState = function()
    {
        if (self._viewerInstance == null)
        {
            return '';
        }
        return self._viewerInstance.content.api.getPoseState();
    };

    /**
    *  When called with a valid string created from calling getViewerState will set the current
    *  visual state of the viewer to match the time when getViewerState was called.
    *
    *  @poseState a string retrieved from calling getPoseState
    */
    this.setFromPoseState = function(poseState)
    {
        if (self._viewerInstance == null)
        {
            return;
        }
        self._viewerInstance.content.api.setFromPoseState(poseState);
    };

    /**
    * Returns the current pointcloud mode
    */
    this.getPointCloudMode = function()
    {
        if (self._viewerInstance == null)
        {
            return;
        }
        return self._viewerInstance.content.api.pointCloudMode;
    };

    /**
    *  Sets the current point cloud mode
    */
    this.setPointCloudMode = function(mode)
    {
        if (self._viewerInstance == null)
        {
            return;
        }
        self._viewerInstance.content.api.pointCloudMode = mode;
    };

    /**
    *  Sets the interval of the slideshow
    */
    this.setSlideshowInterval = function(intervalInSeconds)
    {
        if (self._viewerInstance == null)
        {
            return;
        }
        self._viewerInstance.content.api.setSlideshowInterval(intervalInSeconds);
    };

    /**
    * Returns true if the viewer is currently in slideshow mode
    */
    this.isSlideshowRunning = function()
    {
        if (self._viewerInstance == null)
        {
            return;
        }
        return self._viewerInstance.content.api.isSlideshowRunning();
    };

    /**
    * When called toggles the slideshow mode from on to off
    *
    * @transitionImmediatelyOnStart if true the slideshow will transition immediately to the next item
    *                               in the slideshow order when the slideshow mode is set to true.
    */
    this.toggleSlideshow = function(transitionImmediatelyOnStart)
    {
        if (self._viewerInstance == null)
        {
            return;
        }
        self._viewerInstance.content.api.toggleSlideshow(transitionImmediatelyOnStart);
    };

    /**
    * When called will return the current display mode of the viewer i.e 2D, 3D
    */
    this.getCurrentDisplayMode = function()
    {
        if (self._viewerInstance == null)
        {
            return -1;
        }
        return self._viewerInstance.content.api.currentDisplayMode;
    };

    /**
    * Returns the id of the currently selected image
    */
    this.getSelectedImageId = function()
    {
        if (self._viewerInstance == null)
        {
            return -1;
        }
        return self._viewerInstance.content.api.selectedImageId;
    }

    /**
    * When called moves the viewer to the specified image
    *
    * @imageId the id of the image to navigate to
    * @navType the type of navigation in Microsoft.Photosynth.NavigationType
    */
    this.moveToImage = function(imageId, navType)
    {
        if (self._viewerInstance == null)
        {
            return;
        }
        self._viewerInstance.content.api.moveToImage(imageId, navType);
    };

    /**
    * Loads the specified synth into the viewer
    *
    * @loadParameters parameters to use during the load of the viewer
    */
    this.loadCollectionAsync = function(loadParameters)
    {

        //Only support a single load, if needed we can look into supporting multiple loads
        if (self._loadParameters != null)
        {
            throw "Load can only be called once on a viewer instance";
        }

        self._loadParameters = loadParameters;

        //If the user wants to delay load the viewer then we should put a placeholder
        //piece of HTML that the user can click on to initiate the viewer to load.
        var viewerHtml;
        if (loadParameters.delayLoad && Silverlight.isInstalled(Microsoft.Photosynth.Config.minimumSilverlightVersion))
        {
            //Show users the delay loading screen
            viewerHtml = self.onCreateDelayLoadHtml();
        }
        else
        {
            //Try to instantiate viewer, if not possible will show install HTML
            viewerHtml = self._createViewerHtml();
        }

        this.setContainerHtml(viewerHtml);
    };

    /**
    * Sets the HTML to display in the viewer container
    *
    * @ html the HTML to display in the viewer container
    */
    this.setContainerHtml = function(html)
    {
        self._owner.innerHTML = html;
    };

    /**
    * Called when the user clicks to install Silverlight
    */
    this.onInstallClicked = function()
    {
        var postInstallMessage = null;
        if (Silverlight.isBrowserRestartRequired)
        {
            postInstallMessage = document.getElementById(self.id + "_restartMessageDisplay");
        }
        else
        {
            postInstallMessage = document.getElementById(self.id + "_refreshMessageDisplay");
        }
        if (postInstallMessage != null)
        {
            postInstallMessage.style.display = "block";
            var installMessage = document.getElementById(self.id + "_installMessageDisplay");
            if (installMessage != null)
            {
                installMessage.style.display = "none";
            }
            var installLink = document.getElementById(self.id + "_installLink");
            if (installLink != null)
            {
                installLink.onclick = "";
                installLink.style.cursor = "cross";
            }
        }

        //Forward user to get the correct version of the Silerlight runtime
        Silverlight.getSilverlight(Microsoft.Photosynth.Config.minimumSilverlightVersion);
    };

    /**
    * Called when the user clicks on the delay load screen indicating they want to view the phtosynth viewer
    */
    this.onDelayLoadClicked = function()
    {
        var viewerHtml = self._createViewerHtml();
        self._owner.innerHTML = viewerHtml;
    };

    /**
    * Creates the HTML to display when the user does not have the correct version of Silverlight installed
    */
    this.onCreateInstallHtml = function()
    {
        var html = self._getHtmlTemplate();
        var overlayHtml = '<div style="width:410px" id="' + self.id + '_installMessageDisplay"><img id="' + this.id + 'imgClick" alt="' + Microsoft.Photosynth.Strings.ClickToView + '" title="' + Microsoft.Photosynth.Strings.ClickToView + '" src="' + Microsoft.Photosynth.Config.clickToViewImageUrl + '" /> \
                            <p style="color:#ffffff;font-size:10px;cursor:default;background-image:url(' + Microsoft.Photosynth.Config.backgroundTexture + '); padding:4px;margin:20px;margin-top: 10px;border-style:none;"> \
                                You\'ll be installing <span style="cursor:pointer;text-decoration:underline;" onclick="Microsoft.Photosynth.openSilverlightInfoWindow(); if (event.stopPropagation) {event.stopPropagation();} else {event.cancelBubble = true;};"><b>Microsoft Silverlight</b></span>. It\'s small and fast. \
                            </p></div> \
                            <div style="width:410px; display:none;" id="' + self.id + '_restartMessageDisplay"><p style="color:#ffffff;font-size:16px;cursor:default;background-image:url(' + Microsoft.Photosynth.Config.backgroundTexture + ');padding:4px;margin:20px;border-style:none;">' + Microsoft.Photosynth.Strings.PleaseRestart + '</p></div> \
                            <div style="width:410px; display:none;" id="' + self.id + '_refreshMessageDisplay"><p style="color:#ffffff;font-size:16px;cursor:default;background-image:url(' + Microsoft.Photosynth.Config.backgroundTexture + ');padding:4px;margin:20px;border-style:none;">' + Microsoft.Photosynth.Strings.PleaseRefresh + '</p></div>';

        return html.replace("##OVERLAY_CONTENT##", overlayHtml).replace("##ONCLICK##", 'onclick="Microsoft.Photosynth.installClicked(' + "'" + self.id + "'" + ');"');
    };

    /**
    * Called to create the HTML to display
    */
    this.onCreateDelayLoadHtml = function()
    {
        var html = self._getHtmlTemplate();
        var overlayHtml = '<div style="width:410px"><img id="' + self.id + 'imgClick" alt="' + Microsoft.Photosynth.Strings.ClickToView + '" title="' + Microsoft.Photosynth.Strings.ClickToView + '" src="' + Microsoft.Photosynth.Config.clickToViewImageUrl + '" /></div>';
        return html.replace("##OVERLAY_CONTENT##", overlayHtml).replace("##ONCLICK##", ' onclick="Microsoft.Photosynth.delayLoadClicked(' + "'" + self.id + "'" + ');"');
    };

    /**
    * Returns the HTML that is used by most of the screen i.e. install, delay load
    * can customize for different scenarios.
    */
    this._getHtmlTemplate = function()
    {
        return '<div id="' + self.id + '_mainDiv" style="cursor:pointer;font-family:Verdana,Arial,Helvetica;z-index:0;min-height:100%;position:relative;width:100%;height:100%;overflow:hidden;margin:0;padding:0;background-color:#000000;" ##ONCLICK## > \
                    <div style="z-index:0;min-height:100%;position:absolute;width:100%;height:100%;margin:0;padding:0;border:0;background-color:#000000;"> \
                        <table width="100%" style="height:100%;margin:0;padding:0;border:0;"> \
                            <tr> \
                                <td width="100%" height="100%" style="margin:0;padding:0;border:0;" align="center"> \
                                    <img onload="Microsoft.Photosynth.resizeImageToParent(' + "'" + self.id + "', '" + self.id + "_holdingImage'" + ", '" + self.id + "_mainDiv'" + ', ' + Microsoft.Photosynth.Config.maxImageDimension + ');" style="visibility:hidden;" id="' + self.id + '_holdingImage" alt="' + Microsoft.Photosynth.Strings.ClickToView + '" title="' + Microsoft.Photosynth.Strings.ClickToView + '" src="' + self._loadParameters.holdingImageUrl + '" /> \
                                </td> \
                            </tr> \
                        </table> \
                    </div> \
                    <div style="z-index:1;min-height:100%;position:absolute;width:100%;height:100%;margin:0;padding:0;border:0;background-color:Transparent;"> \
                        <table border="0" style="height:100%;margin-left:auto;margin-right:auto;padding:0;border:0" align="center"> \
                            <tr> \
                                <td height="100%" style="margin:0;padding:0 auto; border:0;" align="center"> \
                                    <div id="' + self.id + '_messageDisplay"> \
                                        ##OVERLAY_CONTENT## \
                                    </div> \
                                </td> \
                            </tr> \
                        </table> \
                    </div> \
                </div>';
    };

    /**
    * Creates the HTML to display the Photosynth viewer, if Silverlight is not installed will show
    * the install HTML.
    */
    this._createViewerHtml = function()
    {
        //Want HTML returned so set parent to null
        var parent = null;

        var autoPlay = (this._loadParameters.autoPlay) ? 'true' : 'false';
        var viewerHtml = Silverlight.createObject(Microsoft.Photosynth.Config.xapSource,
                                                  parent,
                                                  self.id,
                                                  {
                                                      version: Microsoft.Photosynth.Config.minimumSilverlightVersion,
                                                      alt: self.onCreateInstallHtml(),
                                                      width: "100%",
                                                      height: "100%",
                                                      background: "#000000",
                                                      enableHtmlAccess: "true",
                                                      autoUpgrade: "true",
                                                      allowHtmlPopupWindow: "true"
                                                  },
                                                  {
                                                  //NOTE: These events don't get called for cross domain XAP loading
                                              },
                                                  "psWebService=" + Microsoft.Photosynth.Config.psWebService + ",autoPlay=" + autoPlay + ",cid=" + self._loadParameters.collectionId,
                                                  self);
        return viewerHtml;
    };

    self._init();
}

/**
* Holds the parameters used to load a synth in the viewer
*
* @collectionId The CID of the synth to view
* @holdingImageUrl URL of image to show when delay loading or need Silverlight install
* @delayLoad bool indicates if viewer should delay load or not
* @autoPlay bool indicates if viewer should start the slideshow once it has loaded
*/
Microsoft.Photosynth.Viewer.LoadParameters = function(collectionId, holdingImageUrl, delayLoad, autoPlay)
{
    this.collectionId = collectionId;
    this.holdingImageUrl = holdingImageUrl;
    this.delayLoad = delayLoad;
    this.autoPlay = autoPlay;
}

/**
* Represents a simple quaternion class
*/
Microsoft.Photosynth.Math.Quaternion = function(x, y, z, w)
{
    this._init = function(x, y, z, w)
    {
        if (x)
        {
            this.x = x;
            this.y = y;
            this.z = z;
            this.w = w;
        }
        else
        {
            this.x = 0.0;
            this.y = 0.0;
            this.z = 0.0;
            this.w = 1.0;
        }
    };

    this._init(x, y, z, w);
};

/**
* Represents a simple vector class
*/
Microsoft.Photosynth.Math.Vector3D = function(x, y, z)
{
    this._init = function(x, y, z)
    {
        if (x)
        {
            this.x = x;
            this.y = y;
            this.z = z;
        }
        else
        {
            this.x = 0.0;
            this.y = 0.0;
            this.z = 0.0;
        }
    }

    this._init(x, y, z);
};
