﻿// -----------------------------------------------------------------------
//  Copyright (C) Microsoft Corporation. All rights reserved.
// -----------------------------------------------------------------------
// SilverlightControl.js
Type.registerNamespace('Sys.UI.Silverlight');

Sys.UI.Silverlight.Control = function(domElement) {
    /// <summary>A control that attaches to a Silverlight host.</summary>
    /// <param name="domElement" domElement="true">The Silverlight host container element.</param>
    // The dom element passed in is a container SPAN around the actual OBJECT or EMBED element.
    // But we want the control to attach to the actual OBJECT/EMBED, not the span, so find it with _findObject.
    // The container is passed here because in IE when the plugin is not installed, the OBJECT/EMBED element is not
    // found with getElementById in spite of it being in the dom, resulting in an ajax error that Control requires an
    // element. By passing in the container SPAN we are sure to have an element to start with. If no silverlight
    // object is found within the SPAN, attached to the SPAN instead.
    Sys.UI.Silverlight.Control.initializeBase(this, [this._findObject(domElement) || domElement]);
    this._scaleMode = Sys.UI.Silverlight.ScaleMode.none;
}
Sys.UI.Silverlight.Control.prototype = {
    _source: null,
    _splashScreenSource: null,
    _loaded: false,
    _boundEvents: null,
    _setOnLoad: false,
    _setOnFullScreenChange: false,
    _setOnResize: false,
    _setOnError: false,
    _lockSource: false,

    add_pluginError: function(handler) {
        this.get_events().addHandler("pluginError", handler);
    },
    remove_pluginError: function(handler) {
        this.get_events().removeHandler("pluginError", handler);
    },

    add_pluginSourceDownloadComplete: function(handler) {
        this.get_events().addHandler("pluginSourceDownloadComplete", handler);
    },
    remove_pluginSourceDownloadComplete: function(handler) {
        this.get_events().removeHandler("pluginSourceDownloadComplete", handler);
    },

    add_pluginSourceDownloadProgressChanged: function(handler) {
        this.get_events().addHandler("pluginSourceDownloadProgressChanged", handler);
    },
    remove_pluginSourceDownloadProgressChanged: function(handler) {
        this.get_events().removeHandler("pluginSourceDownloadProgressChanged", handler);
    },

    add_pluginFullScreenChanged: function(handler) {
        this.get_events().addHandler("pluginFullScreenChanged", handler);
    },
    remove_pluginFullScreenChanged: function(handler) {
        this.get_events().removeHandler("pluginFullScreenChanged", handler);
    },

    add_pluginLoaded: function(handler) {
        this.get_events().addHandler("pluginLoaded", handler);
    },
    remove_pluginLoaded: function(handler) {
        this.get_events().removeHandler("pluginLoaded", handler);
    },

    add_pluginResized: function(handler) {
        this.get_events().addHandler("pluginResized", handler);
    },
    remove_pluginResized: function(handler) {
        this.get_events().removeHandler("pluginResized", handler);
    },

    get_scaleMode: function() {
        /// <value type="Sys.UI.Silverlight.ScaleMode">
        ///     Specifies how the control scales to the size of the object tag.
        /// </value>
        return this._scaleMode;
    },
    set_scaleMode: function(value) {
        if (value !== this.get_scaleMode()) {
            this._scaleMode = value;
            if (this._loaded) {
                this._ensureTransform();
            }
        }
    },

    get_source: function() {
        /// <value type="String">The source to be loaded initially.</value>
        return this._source || "";
    },
    set_source: function(value) {
        if (this._lockSource) {
            throw Error.invalidOperation(Sys.UI.Silverlight.ControlRes.cannotChangeSource);
        }
        this._source = value;
        if (value && this._setOnLoad) {
            this._lockSource = true;
            this.get_element().Source = value;
        }
    },

    get_splashScreenSource: function() {
        return this._splashScreenSource || "";
    },
    set_splashScreenSource: function(value) {
        if (this._lockSource) {
            throw Error.invalidOperation(Sys.UI.Silverlight.ControlRes.cannotChangeSplash);
        }
        this._splashScreenSource = value;
        if (this._setOnLoad) {
            this.get_element().SplashScreenSource = value;
        }
    },

    addEventListener: function(element, eventName, handler) {
        /// <summary>
        ///     Listens to an event on a Silverlight element, and removes the handler automatically
        ///     when the component is disposed.
        /// </summary>
        /// <param name="element">The element that exposes the event.</param>
        /// <param name="eventName" type="String">
        ///   The name of the event. Must be a valid event name for the type of element provided.
        /// </param>
        /// <param name="handler" type="Function">The event handler to add.</param>
        /// <returns type="Number">The Silverlight event listener token.</returns>
        var token = element.addEventListener(eventName, handler);
        var events = this._getEventsForElement(element, true);
        events[events.length] = { eventName: eventName, token: token };
        return token;
    },

    dispose: function() {
        if (this._loaded) {
            this.pluginDispose();
            this._loaded = false;
        }

        var host = this.get_element();
        if (host) {
            if (this._setOnLoad) {
                host.OnLoad = null;
                host.OnSourceDownloadProgressChanged = null;
                host.OnSourceDownloadComplete = null;
            }
            if (this._setOnError) {
                host.OnError = null;
            }
            if (this._setOnFullScreenChange) {
                host.content.OnFullScreenChange = null;
            }
            if (this._setOnResize) {
                host.content.OnResize = null;
            }
        }

        var boundEvents = this._boundEvents;
        if (boundEvents) {
            for (var i = 0, l = boundEvents.length; i < l; i++) {
                var e = boundEvents[i];
                var element = e.element;
                var events = e.events;
                for (var j = 0, m = events.length; j < m; j++) {
                    var event = events[j];
                    element.removeEventListener(event.eventName, event.token);
                }
            }
            this._boundEvents = null;
        }

        this._doCheck = null;
        Sys.UI.Silverlight.Control.callBaseMethod(this, "dispose");
    },

    _ensureTransform: function() {
        // ensures that the plugin has the correct ScaleTransform ScaleX/Y applied
        // based on the ActualWidth and ActualHeight of the plugin and the current ScaleMode.
        var err, root;
        try {
            // SL3 doesnt allow access to content when loaded cross domain.
            root = this.get_element().content.root;
        }
        catch (err) {
        }
        if (root) {
            // compute the scale x/y that would be required to make the canvas fit the plugin
            var scale = Sys.UI.Silverlight.Control._computeScale(root, this.get_scaleMode());
            Sys.UI.Silverlight.Control._applyMatrix(root, scale.horizontal, scale.vertical, 0, 0);
        }
    },

    _findObject: function(element) {
        if (this._isSilverlight(element)) return element;
        var i, l;
        var object;
        var objects = element.getElementsByTagName("object");
        for (i = 0, l = objects.length; i < l; i++) {
            object = objects[i];
            if (this._isSilverlight(object)) return object;
        }

        objects = element.getElementsByTagName("embed");
        for (i = 0, l = objects.length; i < l; i++) {
            object = objects[i];
            if (this._isSilverlight(object)) return object;
        }
        // the element doesn't contain a silverlight targeted object
        return null;
    },

    _getEventsForElement: function(element, create) {
        var e;
        var boundEvents = this._boundEvents;
        if (!boundEvents) {
            this._boundEvents = boundEvents = [];
        }
        for (var i = 0, l = boundEvents.length; i < l; i++) {
            e = boundEvents[i];
            if (e.element === element) {
                return e.events;
            }
        }
        if (!create) return null;
        e = { element: element, events: [] };
        boundEvents[boundEvents.length] = e;
        return e.events;
    },

    initialize: function() {
        /// <summary>Initializes the control.</summary>
        Sys.UI.Silverlight.Control.callBaseMethod(this, "initialize");

        var host = this.get_element();

        if (this._isSilverlight(host)) {
            if (!host.OnError) {
                host.OnError = Function.createDelegate(this, this._pluginError);
                this._setOnError = true;
            }

            if (host.Source && this.get_source()) {
                // note: could compare for equality but they are URLs and may be different but equal.
                // It's an error to specify a source when the object already has one, even if they are the same source.
                throw Error.invalidOperation(Sys.UI.Silverlight.ControlRes.sourceAlreadySet);
            }
            else if (host.SplashScreenSource && this.get_splashScreenSource()) {
                // It's an error to specify a splash source when the object already has one, even if they are the same splash source.
                throw Error.invalidOperation(Sys.UI.Silverlight.ControlRes.splashAlreadySet);
            }
            if (host.IsLoaded) {
                // source is already loaded, jump straight to silverlight loaded event
                this._lockSource = true;
                this._pluginLoaded();
            }
            else {
                // existing source is still loading or there is no source.
                // either way, we want to be notified when a document loads.
                host.OnLoad = Function.createDelegate(this, this._pluginLoaded);
                host.OnSourceDownloadProgressChanged = Function.createDelegate(this, this._pluginSourceProgress);
                host.OnSourceDownloadComplete = Function.createDelegate(this, this._pluginSourceComplete);
                this._setOnLoad = true;

                if (!host.Source) {
                    // no source loading, assign the control's source.

                    if (!host.SplashScreenSource) {
                        // no splash set, assign the control's splash source.
                        var splash = this.get_splashScreenSource();
                        if (splash) {
                            host.SplashScreenSource = splash;
                        }
                    }

                    var source = this.get_source();
                    if (source) {
                        this._lockSource = true;
                        host.Source = source;
                        if ((Sys.Browser.agent === Sys.Browser.Firefox) && (Sys.Browser.version >= 3) && (Sys.Browser.version < 3.1)) {
                            if (!this._doCheck) {
                                // DevDiv 212604: Work around a firefox 3.0.x bug while in an update panel.
                                // the <object> is reloaded after it is first parsed, causing the changes
                                // we made to it in initialize() to be lost.
                                // see bug: https://bugzilla.mozilla.org/show_bug.cgi?457141
                                this._ensureSet();
                            }
                            else {
                                this._doCheck = null;
                            }
                        }
                    }
                    // source not locked if none set and none loading.
                    // developer may set_source later, and the loaded event will fire
                }
                else {
                    // since a source is already loading you cannot change the source later
                    // also there is no point in setting the splash source since SL would already
                    // be using the default splash screen.
                    this._lockSource = true;
                }
            }
        }
    },
    _ensureSet: function() {
        // checks the Source of the host periodically until it either loads or fails
        // if Source is ever unset, it resets it by re-initializing.
        this._doCheck = Function.createDelegate(this, this._checkSet);
        window.setTimeout(this._doCheck, 50);
    },
    _checkSet: function() {
        if (!this._doCheck) return;
        if (!this.get_element().Source) {
            this.initialize();
            return;
        }
        window.setTimeout(this._doCheck, 200);
    },
    _isSilverlight: function(element) {
        if (!element) return false;

        // must be an <object> or <embed> tag
        var tagName = element.tagName.toLowerCase();
        if (tagName === "object" || tagName === "embed") {
            // must have a silverlight mime type
            var type = (element.type ? element.type.toLowerCase() : "");
            if ((type.indexOf("application/x-silverlight") === 0) ||
                (type.indexOf("application/silverlight") === 0)) {
                // it is a silverlight targeted object/embed tag, but silverlight may not be installed
                // If not, element.settings is nothing.

                // Before accessing 'settings', access innerHTML. This is necessary in Safari to ensure
                // the plugin has been instantiated.
                element.innerHTML;

                return !!(element.settings);
            }
        }
        return false;
    },

    _onFullScreen: function() {
        // may have been disposed by now. SL calls us even though we unhook.
        if (!this.get_element()) return;
        this.onPluginFullScreenChanged(Sys.EventArgs.Empty);
        this._raiseEvent("pluginFullScreenChanged");
    },

    onPluginError: function(errorEventArgs) {
        /// <summary>Called when a Silverlight error occurs.</summary>
        /// <param name="errorEventArgs" type="Sys.UI.Silverlight.ErrorEventArgs">
        ///     Contains the error that occurred.
        /// </param>
    },

    onPluginFullScreenChanged: function(args) {
        /// <summary>Called when full screen mode is toggled.</summary>
        /// <param name="args" type="Sys.EventArgs">Empty.</param>
    },

    onPluginLoaded: function(args) {
        /// <summary>Called when Silverlight has been loaded the specified source.</summary>
        /// <param name="args" type="Sys.EventArgs">Empty</param>
    },

    onPluginResized: function(args) {
        /// <summary>Called when Silverlight has been resized.</summary>
        /// <param name="args" type="Sys.EventArgs">Empty</param>
        if (this.get_scaleMode() !== Sys.UI.Silverlight.ScaleMode.none) {
            this._ensureTransform();
        }
    },

    onPluginSourceDownloadComplete: function(args) {
        /// <summary>Called when the source download has completed.</summary>
        /// <param name="args" type="Sys.EventArgs">Empty.</param>
    },

    onPluginSourceDownloadProgressChanged: function(args) {
        /// <summary>Called when the source download progress has changed.</summary>
        /// <param name="args" type="Sys.UI.Silverlight.DownloadProgressEventArgs">
        ///     Contains information about the download progress.
        /// </param>
    },

    _onResize: function() {
        // may have been disposed by now. SL calls us even though we unhook.
        if (!this.get_element()) return;
        this.onPluginResized(Sys.EventArgs.Empty);
        this._raiseEvent("pluginResized");
    },

    pluginDispose: function() {
        /// <summary>Called on disposal when Silverlight has been previously loaded.</summary>
    },

    _pluginError: function(slSender, e) {
        // may have been disposed by now. SL calls us even though we unhook.
        if (!this.get_element()) return;
        var args = new Sys.UI.Silverlight.ErrorEventArgs(e);
        this.onPluginError(args);
        this._doCheck = null;

        if (!args.get_cancel()) {
            // In debug mode, when error goes unhandled, throw exception.
            // What fields you can access depends on the type of the error.
            // Accessing an inappropriate field will cause an error.
            if (!this._raiseEvent("pluginError", args)) {
                var errorType = e.errorType,
                    errorCode = e.errorCode,
                    errorMessage = e.errorMessage,
                    id = this.get_id(),
                    lineNumber = "", charPosition = "", source = "", methodName = "",
                    errorFormat = Sys.UI.Silverlight.ControlRes.otherError;

                // not all the elements are available for all error types (you get an exception if you try)
                if (errorType === "ParserError") {
                    errorFormat = Sys.UI.Silverlight.ControlRes.parserError;
                    lineNumber = e.lineNumber;
                    charPosition = e.charPosition;
                    source = e.xamlFile;
                }
                else if (((errorType === "ImageError") || (errorType === "MediaError")) &&
                        (errorMessage === "AG_E_NOT_FOUND")) {
                    errorFormat = Sys.UI.Silverlight.ControlRes.mediaError_NotFound;
                    errorMessage = slSender.Source;
                }
                else if (errorType === "RuntimeError") {
                    if (e.lineNumber) {
                        errorFormat = Sys.UI.Silverlight.ControlRes.runtimeErrorWithPosition;
                        lineNumber = e.lineNumber;
                        charPosition = e.charPosition;
                        methodName = e.methodName;
                    }
                    else {
                        errorFormat = Sys.UI.Silverlight.ControlRes.runtimeErrorWithoutPosition;
                        methodName = e.methodName;
                    }
                }
                throw Error.invalidOperation(String.format(errorFormat,
                        id,
                        errorType,
                        errorCode,
                        errorMessage,
                        lineNumber,
                        charPosition,
                        methodName,
                        source));
            }
        }
    },

    _pluginLoaded: function() {
        // may have been disposed by now. SL calls us even though we unhook.
        var element = this.get_element();
        if (element) {
            // Accessing properties on 'content' fails when dom element is hidden
            // delay assigning FullScreenChange and Resize handlers until pluginLoaded. They can't fire
            // until after load anyway.

            // SL3 doesnt allow accessing fields of content if it is loaded cross domain, although it does
            // raise the loaded event. Do not assume accessing .content will work, although that does
            // work in SL3.
            var er, content, existingValue;
            try {
                content = element.content;
            }
            catch (er) {
            }
            if (content) {
                // do not overwrite an existing handlers
                try {
                    if (!content.OnFullScreenChange) {
                        content.OnFullScreenChange = Function.createDelegate(this, this._onFullScreen);
                        this._setOnFullScreenChange = true;
                    }
                    if (!content.OnResize) {
                        content.OnResize = Function.createDelegate(this, this._onResize);
                        this._setOnResize = true;
                    }
                }
                catch (er) {
                }
            }
            this._doCheck = null;
            this._raisepluginLoaded();
            // resize must be manually raised so the plugin is scaled according to the scale mode
            // even if it has already been loaded when the component is created.
            this._onResize();
        }
    },

    _pluginSourceProgress: function(sender, args) {
        var progressArgs = new Sys.UI.Silverlight.DownloadProgressEventArgs(args.progress);
        this.onPluginSourceDownloadProgressChanged(progressArgs);
        this._raiseEvent("pluginSourceDownloadProgressChanged", progressArgs);
    },

    _pluginSourceComplete: function() {
        this.onPluginSourceDownloadComplete(Sys.EventArgs.Empty);
        this._raiseEvent("pluginSourceDownloadComplete");
        // workaround to splash screen issue where the animation continues to run
        // after source loading, and invalidates the tree causing CPU utilization even while idle.
        var er;
        try {
            var sb = this.get_element().content.FindName("LoadingAnimation2");
            if (sb) {
                sb.Stop();
            }
        }
        catch (er) {
        }
    },

    _raiseEvent: function(name, args) {
        // in debug mode returns whether the event was handled.
        // pluginError uses this to know whether the error was handled and throws if not (in debug mode)
        var handler = this.get_events().getHandler(name);
        if (handler) {
            handler(this, args || Sys.EventArgs.Empty);
            return true;
        }
        return false;
    },

    _raisepluginLoaded: function() {
        this._loaded = true;
        this.onPluginLoaded(Sys.EventArgs.Empty);
        this._raiseEvent("pluginLoaded");
    },

    removeEventListener: function(element, eventName, token) {
        /// <summary>
        ///     Removes an event listener from a Silverlight element.
        /// </summary>
        /// <param name="element">The element that exposes the event.</param>
        /// <param name="eventName" type="String">
        ///   The name of the event. Must be a valid event name for the type of element provided.
        /// </param>
        /// <param name="token" type="Number">
        ///   The token returned by addEventListener.
        /// </param>
        element.removeEventListener(eventName, token);
        var events = this._getEventsForElement(element, false);
        if (!events) return;

        for (var i = 0, l = events.length; i < l; i++) {
            var e = events[i];
            if ((e.token === token) && (e.eventName === eventName)) {
                Array.removeAt(events, i);
                return;
            }
        }
    }
}

Sys.UI.Silverlight.Control._computeScale = function(element, scaleMode) {
    // Calculates the scale of a Silverlight element in relation to the Silverlight host.
    // used by scaleMode !== none
    if (scaleMode === Sys.UI.Silverlight.ScaleMode.none) {
        // none always applies no scale (1 times authored size)
        return { horizontal: 1, vertical: 1 };
    }
    
    var width = element.width, height = element.height,
        host = element.getHost(),
        scale = { horizontal: width ? (host.content.ActualWidth / width) : 0,
             vertical: height ? (host.content.ActualHeight / height) : 0 };
    if (scaleMode === Sys.UI.Silverlight.ScaleMode.zoom) {
        scale.horizontal = scale.vertical = Math.min(scale.horizontal, scale.vertical);
    }
    return scale;    
}

Sys.UI.Silverlight.Control._applyMatrix = function(e, scaleX, scaleY, offsetX, offsetY) {
    var transform = e.RenderTransform;
    if (!transform) {
        e.RenderTransform = transform =
            e.getHost().content.createFromXaml('<MatrixTransform Matrix="1,0 0,1 0,0"/>');
    }
    else if (transform.toString() !== "MatrixTransform") {
        throw Error.invalidOperation(Sys.UI.Silverlight.ControlRes.scaleModeRequiresMatrixTransform);
    }
    var original = { horizontal: transform.Matrix.M11, vertical: transform.Matrix.M22 };
    transform.Matrix.M11 = scaleX;
    transform.Matrix.M22 = scaleY;
    transform.Matrix.OffsetX = offsetX;
    transform.Matrix.OffsetY = offsetY;
    return original;
}

Sys.UI.Silverlight.Control.createObject = function(parentId, html) {
    /// <summary>Creates an instance of Silverlight within the given parent element.</summary>
    /// <param name="parentId" type="String">The id of the parent element Silverlight should be loaded in.</param>
    /// <param name="html" type="String">The HTML representing the Silverlight plugin.</param>
    // NOTE: This is used to work around click-to-activate in IE, which works because this script is
    // external to the page using it.
    var parent = document.getElementById(parentId);
    if (!parent) {
        throw Error.invalidOperation(String.format(Sys.UI.Silverlight.ControlRes.parentNotFound, parentId));
    }
    parent.innerHTML = html;
}

Sys.UI.Silverlight.Control.registerClass("Sys.UI.Silverlight.Control", Sys.UI.Control);


Sys.UI.Silverlight.ErrorEventArgs = function(error) {
    /// <param name="error">The Silverlight error object</param>
    this._error = error;
    Sys.UI.Silverlight.ErrorEventArgs.initializeBase(this);
}
Sys.UI.Silverlight.ErrorEventArgs.prototype = {
    get_error: function() {
        /// <value>The Silverlight error object</value>
        return this._error;
    }
}
Sys.UI.Silverlight.ErrorEventArgs.registerClass("Sys.UI.Silverlight.ErrorEventArgs", Sys.CancelEventArgs);

Sys.UI.Silverlight.DownloadProgressEventArgs = function(progress) {
    /// <param name="progress" type="Number">Progress as a number between 0 and 1.</param>
    this._progress = progress;
    Sys.UI.Silverlight.DownloadProgressEventArgs.initializeBase(this);
}
Sys.UI.Silverlight.DownloadProgressEventArgs.prototype = {
    get_progress: function() {
        /// <value type="Number">Progress as a number between 0 and 1.</value>
        return this._progress;
    }
}
Sys.UI.Silverlight.DownloadProgressEventArgs.registerClass("Sys.UI.Silverlight.DownloadProgressEventArgs", Sys.EventArgs);

Sys.UI.Silverlight.ScaleMode = function() {
    /// <summary>Specifies how a Silverlight Control scales to the size of the object tag.</summary>
    /// <field name="none" type="Number" integer="true" static="true"/>
    /// <field name="zoom" type="Number" integer="true" static="true"/>
    /// <field name="stretch" type="Number" integer="true" static="true"/>
    throw Error.notImplemented();
}
Sys.UI.Silverlight.ScaleMode.prototype = {
    none: 0,
    zoom: 1,
    stretch: 2
}
Sys.UI.Silverlight.ScaleMode.registerEnum('Sys.UI.Silverlight.ScaleMode');

Type.registerNamespace('Sys.UI.Silverlight');Sys.UI.Silverlight.ControlRes={"scaleModeRequiresMatrixTransform":"When ScaleMode is set to zoom or stretch, only a MatrixTransform is permitted on the root Canvas.","cannotChangeSource":"The source cannot be changed after initialization.","cannotChangeSplash":"The splashScreenSource cannot be changed after initialization.","runtimeErrorWithoutPosition":"Runtime error {2} in control \u0027{0}\u0027, method {6}: {3}","mediaError_NotFound":"Media \"{3}\" in control \"{0}\" could not be found.","runtimeErrorWithPosition":"Runtime error {2} in control \u0027{0}\u0027, method {6} (line {4}, col {5}): {3}","sourceAlreadySet":"The source cannot be set because the Silverlight host already has a source.","otherError":"{1} error #{2} in control \u0027{0}\u0027: {3}","splashAlreadySet":"The splashScreenSource cannot be set because the Silverlight host already has a splashScreenSource.","parentNotFound":"An element with id \"{0}\" could not be found.","parserError":"Invalid XAML for control \u0027{0}\u0027. [{7}] (line {4}, col {5}): {3}"};
if(typeof(Sys)!=='undefined')Sys.Application.notifyScriptLoaded();