var NewSiteApplication = function() { return function() { this.initialize.apply(this, arguments) }; } ();

NewSiteApplication.prototype = {

    /**
    * Constructor called by default on object instantiation.
    * 
    * @param {object} options Object to be copied to internal options reference
    * 
    */
    initialize: function() {
        this.comps = []; //Components
        this.obs = {}; // Observers
        this.addedElements = {};
        this.componentJSON = null;
        this.configJSON = null;
        this.queryString = null;
        this.onLoadCallbacks = [];

        this._readyEventFired = false;
        this._componentLoadComplete = false;
        this._videoPlayerComponentReady = false; // This is the essential piece to have ready
        this._playerContainerName = null; // Wrapper for SWF-On-SWF
        this._responseJSON = null;
        this._partnerGuid = null;
        this._defaultClipId = null;

        this._prefix = "NS_";
        this._flashEmbedPrefix = "NS_FLASH_";
        this._PIDLength = 32;
        this._scriptTagId = "NS_GUID_JS";
        this._guidURLParam = "guid";

        this._jsonServiceTimeoutID = null;
        this._jsonServiceTimeoutLength = 10000;

        this._guidJSurl = "http://config.hulu.com/js/";
        this._urlJSON = "http://player.hulu.com/player/vs?callback=NewSite.loadComponents";

        this.DEBUG = false;
        this.LOGGER = null;

        this.VERSION = "$Id: hulu_global.js 3.00 $";

        //this.getTimer().startTimer('start');

        // Fix flash Out of mem error in IE Flash plugin 9,0,16
        if (!window.opera && document.all) {
            this.unloadSet = false;
            if (!this.unloadSet) {
                window.attachEvent("onbeforeunload", this._prepUnload);
                this.unloadSet = true;
            }
        }
    },

    /**
    * Cleans flash methods out of memory
    * 
    * @private
    */
    _prepUnload: function() {
        __flash_unloadHandler = function() { };
        __flash_savedUnloadHandler = function() { };
        var nullSWFFuncs = function() {
            var objArr = document.getElementsByTagName("OBJECT");
            for (var i = objArr.length - 1; i >= 0; i--) {
                objArr[i].style.display = 'none';
                for (var j in objArr[i]) {
                    if (typeof objArr[i][j] == 'function') {
                        objArr[i][j] = function() { };
                    }
                }
            }
        }
        window.attachEvent("onunload", nullSWFFuncs);
    },

    /**
    * Returns formatted component service URL including GUID
    * 
    * @return {string} Component service url
    */
    getComponentServiceUrl: function() {
        var url = this._urlJSON;
        var sep = this.getUrlSeparator(url);
        try {
            // Add the guid ID to the request url if it is not present
            if (url.toLowerCase().indexOf(this._guidURLParam) == -1) {
                url += sep + this._guidURLParam + "=" + this.getPartnerGuid();
            }
        }
        catch (e) { this.reportError(e); }
        return url;
    },

    /**
    * Method used to activate Application. Responsible for initiating JSON service request.
    */
    run: function() {
        var thisObj = this;

        if (!window.opera && document.all)
            window.attachEvent('onunload', this.cleanupSwfs);

        this._jsonServiceTimeoutID = window.setTimeout(function() { thisObj.fireJSONError(thisObj); }, thisObj._jsonServiceTimeoutLength)
        this.loadExternalJS(this.getComponentServiceUrl());
    },

    /*
    * Method used to clean up page
    */
    cleanupSwfs: function() {
        var objects = document.getElementsByTagName("OBJECT");
        for (var i = objects.length - 1; i >= 0; i--) {
            objects[i].style.display = 'none';
            for (var x in objects[i]) {
                if (typeof objects[i][x] == 'function') {
                    objects[i][x] = function() { };
                }
            }
            objects[i].parentNode.removeChild(objects[i]);
        }
    },

    /**
    * Used to test object instantiation
    * 
    * @return {boolean} Returns true or undefined if object hasn't been instantiated
    */
    ping: function() {
        return true;
    },

    /**
    * Fires a JSON error event after no JSON object is returned in the specified
    * timeout period which is stored in this._jsonServiceTimeoutLength
    * 
    * @param {object} t Reference to "NewSite" global object.
    */
    fireJSONError: function(t) {
        var errorObj = { code: "NS_JS_001",
            type: "JSON",
            displayMessage: "Page components failed to load.",
            description: "Page component JSON service timed out."
        }
        t.updateObservers("newsiteError", errorObj);
    },

    /**
    * Loads an external Javascript file by appending a script tag to the head element
    * 
    * @param {string} url
    * @return {string} Id of script tag
    */
    loadExternalJS: function(url) {

        //this.getTimer().startTimer('loadExternalJS');

        try {
            // Send request for JSON object from service
            if (!url) {
                throw ('url parameter is required');
            }

            var randomId = Math.random() + '';
            randomId.replace(/\./g, '');

            var urlSeperator = this.getUrlSeparator(url);

            var scriptObj = document.createElement("script");
            scriptObj.setAttribute("type", "text/javascript");
            scriptObj.setAttribute("src", url);
            scriptObj.setAttribute("id", randomId);

            var head = document.getElementsByTagName("head");
            if (!head) {
                throw ("No head tag found.");
            }
            var headTag = head.item(0);

            // Add it to the <head>; this will immediately run it
            headTag.appendChild(scriptObj);

            return randomId;

        }
        catch (e) { this.reportError(e, "NewSite.loadExternalJS"); }
    },

    /**
    * Allows external Flash components to notify the app that they are loaded
    * and ready to receive commands. Calls the fireReady method after each call.
    * 
    * @param {string} componentName DOM id of Flash component
    */
    registerReady: function(componentName) {
        this.traceMsg("registerReady: " + componentName);

        if (this.addedElements[componentName]) {
            this.addedElements[componentName].isReady = true;
        }

        // If the incoming component is the video player,
        // loop through all the "ready" components and send
        // their tracking info. 
        if (componentName == 'videoPlayerComponent') {
            this.traceMsg("incoming: " + componentName);
            this._videoPlayerComponentReady = true;
            for (var i in this.addedElements) {
                var comp = this.addedElements[i];
                if (comp && comp.isReady && !comp.isTracked) {
                    this.videoPlayerComponent.trackComponent([comp.trackingInfo]);
                    comp.isTracked = true;
                }
            }
        }
        this.fireReady(componentName);
    },

    /**
    * Gets a hash object containing the components init object (from JSON)
    * 
    * @return {object}
    */
    getComponents: function() {
        return this.componentJSON;
    },

    /**
    * Checks the status of a component
    * 
    * @param {string} componentName Component Name
    * @return {bool} True if component has reported as ready
    */
    isComponentReady: function(componentName) {
        var comp = this.addedElements[componentName];
        if (!comp || comp.isReady === true) {
            this.traceMsg("isComponentReady: " + componentName + " true");
            return true;
        }
        this.traceMsg("isComponentReady: " + componentName + " false");
        return false;
    },

    /**
    * Receives the JSON from the external service and 
    * instantiates each JSON object into a DOM component.
    * 
    * @param {object} response JSON object
    */
    loadComponents: function(responseJSON) {

        //this.getTimer().startTimer('loadComponents');

        clearTimeout(this._jsonServiceTimeoutID);

        var thisObj = this;

        try {
            if (responseJSON.error) {
                window.setTimeout(function() {
                    thisObj.fireJSONError(thisObj);
                    thisObj.reportError("Service error: " + responseJSON.error.errorMessage);
                }, 500)

            }
            if (responseJSON.components.videoPlayerComponent.url.indexOf("widget_player.swf") != -1) {
                responseJSON.components.videoPlayerComponent.url += "noCache" + Math.round(Math.random() * 10000);
            }
            else if (responseJSON.components.videoPlayerComponent.url.indexOf("fancast16x9_player.swf") != -1 ||
                responseJSON.components.videoPlayerComponent.url.indexOf("fancast4x3_player.swf") != -1) {
                responseJSON.components.videoPlayerComponent.url += "&inSwf=true";
            }
            else if (responseJSON.components.videoPlayerComponent.url.indexOf("ificandream_player.swf") != -1) {
                responseJSON.components.videoPlayerComponent.url += "&inSwf=true&partner=IfICanDream";
            }


            // check if there is custom width and height setting in the src string, if so, use these setting to overwrite the current width and height setting
            try {
                var jsParams = this._parseQueryString(this.EL(this._scriptTagId).src.split('?')[1]);

                if (jsParams.partner != undefined) {
                    responseJSON.components.videoPlayerComponent.optionalAttributes.distributionPartner = jsParams.partner;
                    responseJSON.components.videoPlayerComponent.url += "&partner=" + jsParams.partner;
                }

                if (jsParams.showMetaDataBar != undefined) {
                    responseJSON.components.videoPlayerComponent.optionalAttributes.distributionPartner = jsParams.partner;
                    responseJSON.components.videoPlayerComponent.url += "&showMetaDataBar=true";
                }

                if (jsParams.width != undefined) {
                    responseJSON.components.videoPlayerComponent.width = jsParams.width;
                }

                if (jsParams.height != undefined) {
                    responseJSON.components.videoPlayerComponent.height = jsParams.height;
                }

                if (jsParams.wmode != undefined && (jsParams.wmode == "transparent" || jsParams.wmode == "opaque" || jsParams.wmode == "window")) {
                    responseJSON.components.videoPlayerComponent.wmode = jsParams.wmode;
                }

                if (jsParams.autoplay != undefined && (jsParams.autoplay == "true" || jsParams.autoplay == "false")) {
                    responseJSON.components.videoPlayerComponent.optionalAttributes.autoplay = jsParams.autoplay;
                }

                if (jsParams.allowRecommendation != undefined && (jsParams.allowRecommendation == "true" || jsParams.allowRecommendation == "false")) {
                    responseJSON.components.videoPlayerComponent.optionalAttributes.allowRecommendation = jsParams.allowRecommendation;
                }

                if (jsParams.allowPlaylist != undefined && (jsParams.allowPlaylist == "true" || jsParams.allowPlaylist == "false")) {
                    responseJSON.components.videoPlayerComponent.optionalAttributes.allowPlaylist = jsParams.allowPlaylist
                }

            }
            catch (e) { this.reportError(e, "JS query params"); }

            try {
                var r = "&referrer=" + escape(top.location);
                responseJSON.components.videoPlayerComponent.url += r;
            }
            catch (e) {
                responseJSON.components.videoPlayerComponent.url += "&referrer=" + escape(document.referrer);
            }

            this._responseJSON = responseJSON;

            var components = this.componentJSON = this._responseJSON.components;
            var config = this.configJSON = this._responseJSON.config;

            for (var componentName in components) {

                var componentDetails = components[componentName];
                var component = this.initializeComponent(componentName, componentDetails);

                // Check for a DIV in the DOM
                if (this.EL(componentName)) {
                    component = this.addComponentToDom(component);
                }

                this.extendComponent(component);

                // Store a reference to the component name for listing
                this.comps.push(componentName);
            }
            this.traceMsg("ComponentLoadComplete");
            this._componentLoadComplete = true;
            this.fireReady();

        }
        catch (e) { this.reportError(e, "loadComponents"); }
    },

    /**
    * Creates component reference in JS Object
    * 
    * @param {string} id DOM id
    * @param {object} options Meta information about the component (height, width, etc) to be persisted with the component reference
    */
    initializeComponent: function(componentName, options) {
        return this[componentName] = {
            instance: null,
            compName: componentName,

            // Save a reference to the current object
            parent: this,

            // Initialize a storage for the component options and store them
            options: options
        };
    },

    /**
    * Register a component with the framework
    * 
    * @param {object} data Hash of component params
    */
    addComponentToDom: function(component) {
        var componentName = component.compName;

        this.traceMsg("addComponentToDom: " + componentName);

        var options = component.options;

        if (this.EL(componentName)) {
            var instanceId = this._flashEmbedPrefix + componentName;

            // The *container* DIV is in the DOM
            // Check to ensure the *component* hasn't already been added to the DOM
            if (!this.EL(instanceId)) {

                // Insert component into the DOM
                this.addedElements[componentName] = {};
                this.addedElements[componentName].isReady = false;
                this.addedElements[componentName].isTracked = false;

                this.EL(componentName).innerHTML = this.getFlashTag(instanceId, options);

                this.addedElements[componentName].trackingInfo = this.getTrackingInfo(componentName, options);

                // Add a reference to the object to the global object
                component.instance = this.registerFlashObject(instanceId);
            }
        }

        return component;
    },

    /**
    * Extends a component objects by adding methods after initializing
    * 
    * @param {object} comp Component object
    */
    extendComponent: function(component) {

        var componentName = component.compName;

        // First add any common component methods
        this.extend(component, this["commonComponentMethods"]);

        // Add methods specific to this component
        if (this[componentName + "Methods"]) {
            this.extend(component, this[componentName + "Methods"]);
        }
    },

    /**
    * Gets a JS reference to a DOM flash object depending on browser type.
    * 
    * @param {string} id Flash embed/object tag DOM id
    * @return {object} JS DOM reference
    */
    registerFlashObject: function(id) {
        var component = null;
        if (navigator.appName.indexOf("Microsoft") != -1) {
            component = window[id];
        }
        else {
            component = document[id];
        }

        return component ? component : false;
    },

    /*
    *****
    *
    *	Events
    *
    *****
    */

    /**
    * Adds listener to the JS object event queue. Ignores duplicate entries.
    *
    * @param {string} eventType Event type name 
    * @param {mixed} callbackObj Object or string. String will look for Id of object in the DOM.
    *                                              Resolves to object to observe events.
    * @param {string} callbackFunc Function name to be called. Defaults to eventType
    *
    * @return {string} eventType Event type name or false if listener already is registered.
    */
    addListener: function(eventType, callbackObj, callbackFunc) {
        this.traceMsg("addListener: " + eventType + "\n<br>callbackObj " + callbackObj + "\n<br>callbackFunc " + callbackFunc);
        callbackObj = this._stringToObject(callbackObj);

        if (!callbackFunc) {
            callbackFunc = eventType;
        }
        if (!this.obs[eventType]) {
            this.obs[eventType] = [];
        }
        var obsType = this.obs[eventType]
        var len = obsType.length;
        for (var i = 0; i < len; i++) {
            try {
                if (obsType[i].obj && obsType[i].obj == callbackObj && obsType[i].func == callbackFunc)
                    throw "Addition of duplicate listener ignored.";
            }
            catch (e) { this.reportError(e); };
        }
        var cb;
        if (callbackObj && callbackObj.id) {
            cb = callbackObj.id;
        }
        else {
            cb = callbackObj;
        }
        this.traceMsg("addListener: " + eventType + "\n<br>callbackObj " + cb + "\n<br>callbackFunc " + callbackFunc);

        this.obs[eventType].push({ obj: callbackObj, func: callbackFunc });

        return eventType;
    },

    /**
    * Adds a Flash listener to the queue. To be called from Flash to register itself as 
    * as a listener. Doesn't allow duplicate entries.
    * 
    * @param {string} eventType Event type name 
    * @param {string} callbackObjId DOM Id of Flash Object to observe events
    * @param {string} callbackFunc Function name to be called. Defaults to eventType
    * 
    * @return Original object
    */
    addFlashListener: function(eventType, callbackObj, callbackFunc) {
        callbackObj = this.registerFlashObject(callbackObj);
        this.addListener(eventType, callbackObj, callbackFunc);
    },

    /**
    * Removes a listener from the queue.
    *
    * @param {string} eventType Event type name 
    * @param {string} callbackObjId DOM Id of Flash Object to observe events
    * @param {string} callbackFunc Function name to be called. Defaults to eventType
    * 
    * @return Removed object or false
    */
    removeListener: function(eventType, callbackObj, callbackFunc) {

        callbackObj = this._stringToObject(callbackObj);

        if (!callbackFunc) {
            callbackFunc = eventType;
        }

        this.traceMsg("removeListener:\ncallbackObj " + callbackObj + "\ncallbackFunc " + callbackFunc);

        if (!this.obs[eventType]) {
            return false;
        }

        var obsType = this.obs[eventType]
        var len = obsType.length;
        for (var i = 0; i < len; i++) {
            try {
                if (obsType[i].obj
                                 && obsType[i].obj == callbackObj
                                 && obsType[i].func == callbackFunc) {

                    return obsType.splice(i, 1);
                }
            }
            catch (e) { this.reportError(e); };
        }
        return false;
    },

    /**
    * Iterates through the observer queue and fires a method of the same name as the eventType on each object.
    *
    * @param {string} eventType Name of event. Translates to the method name in each object.
    * @param {mixed} data Data to pass to the event method call.
    * @return void
    */
    updateObservers: function(eventType, data) {

        this.traceMsg('updateObservers : eventType ' + eventType + " " + data);

        var obsType = this.obs[eventType]
        if (!obsType) { return; }

        var len = obsType.length;
        for (var i = 0; i < len; i++) {
            try {
                var obj = this._stringToObject(obsType[i].obj);
                var func = obsType[i].func;

                if (obj && func) {
                    if (typeof obj != 'object') {
                        throw "Object missing: " + obj;
                    }
                    if (func == undefined || func == null) {
                        func = eventType;
                    }
                    if (typeof obj[func] == "function") {
                        obj[func](data);
                    }
                    else {
                        obj.toComponent(func, data);
                    }
                }

            }
            catch (e) { this.reportError(e, "updateObservers(" + eventType + ")"); };
        }
    },

    /**
    * Finds an object in the global namespace named the same as the passed string.
    * 
    * @param {mixed} str String or object. String is used to look up 
    *                     an identically named object in the global namespace. 
    *                     Object is returned unaltered.
    * @return {object}
    * @private
    */
    _stringToObject: function(str) {
        var obj;
        if (typeof str == "string") {

            var arr = str.split('.');

            var count = 0;
            while (arr.length > 0) {
                var c = arr.shift();
                if (!count++) {
                    obj = window[c];
                }
                else {
                    obj = obj[c];
                }
            }
            if (obj) { return obj; }
        }
        return str;
    },

    /**
    * Sets the name of the application SWF that acts as a wrapper to the video player component.
    * Once this method is called the .videoPlayerComponent object points to the new container.
    * 
    * @param {string} id DOM id of swf
    * @return {string} The id or null if the id is not in the DOM
    */
    setPlayerContainer: function(id) {
        this.traceMsg('setPlayerContainer : ' + id);

        if (this.EL(id)) {
            this._playerContainerName = id;
            return id;
        }
        return null;
    },

    /**
    * Returns the DOM id of the video wrapper SWF 
    * 
    * @return {string} SWF DOM id or null if wrapper is not set
    */
    getPlayerContainerName: function() {
        return this._playerContainerName;
    },

    /**
    * Sets the default or initial clip id to be played first by videoPlayerComponent
    * 
    * @param {string} id PID or id of default clip
    */
    setDefaultClipId: function(id) {
        this._defaultClipId = id;
    },

    /**
    * Returns the default or initial clip id to be played first by videoPlayerComponent
    * 
    * @return {string}
    */
    getDefaultClipId: function() {
        return this._defaultClipId;
    },

    /**
    * Returns a video id or pid from a url style string.
    * Pid must follow the case-insensitive string "pid=" and not contain the '&' character.
    * If no pid is found param is returned unchanged.
    * 
    * @param {string} id Url style string containing id
    * @throws "Invalid Video PID" error
    * @return {string} 
    */
    _parseVideoId: function(id) {
        try {
            id = id + ''; // convert to string
            id = id.replace(/.*pid=([^&]+).*/i, "$1"); // check for URL style (pid=xyz)
            if (id.length != this._PIDLength) {
                throw "Invalid Video PID: " + id;
            }
        }
        catch (e) { this.reportError(e, "") };

        return id;
    },

    /**
    * 
    */
    getNPMLObject: function() {
        return this._responseJSON;
    },

    /*
    *****
    *
    *	Util methods
    *
    *****
    */

    /**
    * Shortcut to document.getElementById
    * 
    * @param {string} el Passing a string will return a reference to the DOM object with the passed id.
    */
    EL: function(el) {
        if (typeof el == 'string') {
            el = document.getElementById(el);
        }
        return el;
    },

    /**
    * Shallow copies all methods and properties from sourceObj to destination Obj
    * 
    * @param {object} destinationObj Object to receive copies of methods/properties
    * @param {object} sourceObj Object to copy methods/properties from
    */
    extend: function(destinationObj, sourceObj) {
        for (var property in sourceObj) {
            destinationObj[property] = sourceObj[property];
        }
        return destinationObj;
    },

    /**
    * Returns flag set to true if the "ready" event has already been fired.
    * Used to check by a component if the event has been missed.
    * 
    * @return {boolean}
    */
    readyEventFired: function() {
        return this._readyEventFired;
    },

    /**
    * @return {boolean} 
    */
    fireReady: function(componentName) {
        this.traceMsg("fireReady: " + componentName);
        if (!this.readyEventFired()) {
            if (this._componentLoadComplete && this.allComponentsReady()) {
                this.traceMsg("componentLoadComplete: " + componentName);
                this.updateObservers("newsiteReady", {});
                this._readyEventFired = true;
                this.onComponentsLoaded();
            }
        }
    },

    allComponentsReady: function() {
        this.traceMsg("allComponentsReady?");
        for (var i in this.addedElements) {
            if (this.isComponentReady(i) != true) {
                this.traceMsg(i + " is Not ready");
                return false;
            }
        }
        this.traceMsg("allComponentsReady!! " + this._videoPlayerComponentReady);
        return this._videoPlayerComponentReady;
    },

    /**
    * @private
    */
    onComponentsLoaded: function() {

        //this.getTimer().startTimer('onComponentsLoaded');

        while (this.onLoadCallbacks.length > 0) {
            var callback = this.onLoadCallbacks.pop();
            try {
                if (callback) callback();
            }
            catch (e) { this.reportError(e); }
        }

        //if ( this.DEBUG )
        //this.getTimer().reportTimer();

    },


    /**
    * Queues up functions to be called upon JS object load
    * 
    * @param {function} Function to be called
    */
    onLoad: function(func) {
        this.onLoadCallbacks.push(func);
    },


    /**
    * Returns a hash object of key value pairs
    * 
    * @return {object} Hash of key value pairs
    */
    getQueryString: function() {
        if (!this.queryString) {
            this.queryString = this._parseQueryString();
        }
        return this.queryString;
    },


    /**
    * Parses the query string and persists it in the object
    * 
    * @param {string}  querystring Query string to parse. Defaults to use window.location.search
    * @private
    * 
    * @return {object} Hash of key values
    */
    _parseQueryString: function(querystring) {
        var qsObj = {};

        var qs = querystring ? querystring : window.location.search;
        if (qs.charAt(0) == '?') { // Remove question mark
            qs = qs.slice(1, qs.length);
        }
        if (qs.length < 1) {
            return qsObj;
        }

        // Replace &amp; with simple &
        qs = qs.replace(/\&amp\;/gi, '&');

        qs = qs.split('&');
        for (var i = 0; i < qs.length; i++) {
            var pairs = qs[i].split("=");
            qsObj[pairs[0]] = pairs[1];
        }
        return qsObj;
    },


    /**
    * Retreives guid param from script tag src url. 
    * 
    * @return {string} Partner Guid
    */
    getPartnerGuid: function() {
        if (!this._partnerGuid) {
            try {
                var guidscript = this.EL(this._scriptTagId).src;
                var guidqsObj = this._parseQueryString(guidscript.split('?')[1]);
                this._partnerGuid = guidqsObj.guid;
            }
            catch (e) { this.reportError(e); }
        }
        return this._partnerGuid;
    },

    /**
    * 
    * @param {string} url URL
    * @return {string} Either '?' or '&' depending on the existence of a query string
    */
    getUrlSeparator: function(url) {
        return url.indexOf('?') == -1 ? '?' : '&';
    },


    /**
    * Returns a hash object of the browser environment.
    * The following properties are included:
    * <code>{  queryString: [object],
    *          referrer: [string],
    *          url: [string],
    *          userAgent: [string],
    *          availHeight: [number],
    *          availWidth: [number],
    *          colorDepth: [number],
    *          height: [number],
    *          width: [number]
    *      }</code> 
    * 
    * @return {object} Hash of key value pairs
    */
    getEnvironment: function() {
        var qs = this.getQueryString();
        var ref = document.referrer;
        var url = document.URL;
        var ua = navigator.userAgent;
        var env = {
            location: ref,
            queryString: qs,
            referrer: ref,
            url: url,
            userAgent: ua
        }
        var sc = ["availHeight", "availWidth", "colorDepth", "height", "width"];
        for (var i = 0; i < sc.length; i++) {
            var e = sc[i];
            env[e] = screen[e];
        }
        return env;
    },

    getTrackingInfo: function(componentName, options) {
        try {
            var obj = {};
            obj.componentName = componentName;
            obj.url = document.URL; //options.url;
            obj.pixelArea = options.width * options.height;
            obj.version = 1;
        }
        catch (e) { this.reportError(e); }
        return obj;
    },

    loadGuidJS: function() {
        /*  //Long version
        var guidscript = NewSite.EL(this._scriptTagId).src;
        var guidqsObj = NewSite._parseQueryString( guidscript.split('?')[1] );
        NewSite.loadExternalJS( this._guidJSurl+guidqsObj.p+".js" );
        */
        // Concise version!
        this.loadExternalJS(this._guidJSurl + this._parseQueryString(this.EL(this._scriptTagId).src.split('?')[1]).guid + ".js");
    },

    /**
    * Generates a browser appropriate HTML tag for embedding a Flash SWF
    *
    * @param {string} id Tag id
    * @param {number} width Tag width
    * @param {number} height Tag height
    * @param {string} url Tag src url
    *
    * @return {string} HTML object tag string
    */
    getFlashTag: function(id, options) {
        var fb = this.getFlashBuilder();
        fb.setId(id);
        fb.setAttributes(options);
        var isIE = (navigator.appName.indexOf("Microsoft") != -1);
        var tag = isIE ?
                    fb.getObjectTag() :
                    fb.getEmbedTag();
        fb.cleanup();
        delete fb;
        return tag;
    },

    /**
    * Returns a reference to the internal Flash embed library
    * 
    * @return {object}
    */
    getFlashBuilder: function() {
        var fb = {
            errorHandler: null,
            id: '',
            attributes: [],
            allowedAttributes: { width: { req: true, def: null },
                height: { req: true, def: null },
                url: { req: true, def: null },
                wmode: { req: false, def: 'transparent' },
                bgcolor: { req: false, def: '#FFFFFF' },
                allowScriptAccess: { req: false, def: 'always' },
                allowFullScreen: { req: false, def: 'true' },
                menu: { req: false, def: 'true' }
            },
            setId: function(id) { this.id = id },
            getId: function() { return this.id },
            setErrorHandler: function(func) { this.errorHandler = func; },
            setAttributes: function(a) { //Expects Object
                for (var k in a) { this.setAttribute(k, a[k]); }
            },
            setAttribute: function(name, value) {
                this.attributes[name] = value;
            },
            getAttribute: function(name) {
                return this.attributes[name] || this.getDefaultValue(name) || false;
            },
            getAllowedAttributes: function() { return this.allowedAttributes },
            isRequired: function(name) {
                var att = this.allowedAttributes[name];
                return att && att.req ? true : false;
            },
            getDefaultValue: function(name) {
                var att = this.allowedAttributes[name];
                return att && att.def || null;
            },
            getEmbedTag: function() {
                try {
                    var id = this.getId();
                    var tag = '<embed quality="high" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" ';
                    tag += 'name="' + id + '" id="' + id + '" ';
                    var aa = this.getAllowedAttributes();
                    for (var att in aa) {
                        var attVal = this.getAttribute(att);
                        if (this.isRequired(att) && !this.getAttribute(att)) {
                            throw "Missing required attribute for " + id + ": " + att;
                        }
                        if (att.toLowerCase() == 'url') { att = 'src'; }
                        tag += att + '="' + attVal + '" ';
                    }
                    tag += '></embed>';
                }
                catch (e) { this.handleError(e); return ''; }
                return tag;
            },
            getObjectTag: function() {
                try {
                    var aa = this.getAllowedAttributes();
                    var tag = '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" ';
                    tag += 'name="' + this.getId() + '" id="' + this.getId() + '" ';
                    tag += 'width="' + this.getAttribute('width') + '" height="' + this.getAttribute('height') + '"><param name="quality" value="high" />';
                    for (var att in aa) {
                        var attVal = this.getAttribute(att);
                        if (this.isRequired(att) && !this.getAttribute(att)) {
                            throw "Missing required attribute: " + att;
                        }
                        if (att.toLowerCase() != 'width' && att.toLowerCase() != 'height') {
                            if (att.toLowerCase() == 'url') att = 'movie';
                            tag += '<param name="' + att + '" value="' + attVal + '" />';
                        }
                    }
                    tag += '</object>';
                }
                catch (e) { this.handleError(e); return ''; }
                return tag;
            },
            handleError: function(e) { if (this.errorHandler != null) { this.errorHandler(e); } },
            cleanup: function() { this.errorHandler = null; for (var x in this) { this[x] = null; } }
        }; // End of fb

        fb.setErrorHandler(this.reportError);

        return fb;
    },

    /**
    * Hides all components
    */
    hideAllComponents: function() {
        try {
            var len = this.comps.length;
            for (var i = 0; i < len; i++) {
                this[this.comps[i]].hide();
            }
        }
        catch (e) { }
    },

    /**
    * Shows all components
    */
    showAllComponents: function() {
        try {
            var len = this.comps.length;
            for (var i = 0; i < len; i++) {
                this[this.comps[i]].show();
            }
        }
        catch (e) { }
    },

    pingImgUrl: function(url) {
        try {
            var randomId = Math.random() + '';
            randomId.replace(/\./g, '');

            var img = document.createElement('IMG');
            img.width = 1;
            img.height = 1;
            img.src = url;
            img.id = randomId;
            img.onload = img.onerror = function() { var o = this.parentNode.removeChild(this); delete o; }
            document.body.appendChild(img);
        }
        catch (e) { };
    },


    /**
    * Handle caught errors
    * 
    * @param {object} e Error object
    * @param {string} loc Text indicator as to error location in code
    */
    reportError: function(e, loc) {
        try {
            var thisRef = NewSite, msg;
            if (!thisRef.DEBUG || !thisRef.LOGGER) { return false; }

            if (navigator.appName.indexOf("Microsoft") != -1) {
                msg = e.message + "\n" + e.name + "\n" + e.number + "\n" + e.description + "\n";
            }
            else {
                msg = e;
            }
            if (loc) {
                msg = loc + "\n\n" + msg;
            }
            var d = new Date().getMilliseconds();
            var p = document.createElement('P');
            p.style.backgroundColor = "red";
            p.style.color = "white";
            p.innerHTML = d + ": Error caught: \n\n" + msg;
            thisRef.LOGGER.insertBefore(p, thisRef.LOGGER.firstChild);
        }
        catch (e) { }
    },

    /**
    * Traces a message to the log window
    * 
    * @param {string} msg Log message
    * @param {string} loc Location of message
    */
    traceMsg: function(msg, loc) {
        try {
            if (!this.DEBUG || !this.LOGGER) { return false; }

            if (loc) { msg = loc + "\n\n" + msg; }

            var d = new Date().getMilliseconds();
            var p = document.createElement('P');
            p.innerHTML = d + ": Trace: \n\n" + msg;
            this.LOGGER.insertBefore(p, this.LOGGER.firstChild)
        }
        catch (e) { }
    },

    getTimer: function() {
        /*
        if ( ! this._timer ){
        var timer = {
        errorHandler: null,
        tags: {},
        lastTag: null,
        totalStart: null, 
        totalStop: null,
        setErrorHandler: function( func ){ this.errorHandler = func; },
        getTime: function(){ return new Date().getTime(); },
        startTimer: function( tag ){
        this.stopTimer( this.lastTag );
        try{
        if ( tag == this.lastTag ) tag += tag; //Double the name in case the last call was to the same tag
        this.lastTag = tag;
        this.tags[tag] = { started : this.getTime() };
        }catch(e){this.handleError(e)}
        },
        stopTimer: function( tag ){
        try{
        if ( ! tag )
        tag = this.lastTag;
                            
        var lt = this.tags[tag]
        if ( lt && ! lt.stopped ){
        lt.stopped = this.getTime();
        }
        }catch(e){this.handleError(e)}
        },
        reportTimer: function(){
        this.stopTimer( this.lastTag );
        this.totalStop = this.getTime();
        this.totalTime = this.totalStop - this.totalStart;
        var p = document.createElement('P');
        p.innerHTML = this.totalTime + "ms";
        var d = document.getElementById('NS_Timer');
        if ( ! d ) return;
        for ( var i in this.tags ){
        var p = document.createElement('P');
        p.innerHTML = i + ": " + ( this.tags[i].stopped - this.tags[i].started ) + "ms";
        d.appendChild(p);
        }
        var b = document.getElementsByTagName('BODY');
        b[0].insertBefore( p,b[0].firstChild)
        },
        handleError: function(e){ if( this.errorHandler != null )this.errorHandler(e); }
        }
        timer.setErrorHandler( this.reportError );
        timer.totalStart = timer.getTime();
        this._timer = timer;
        }
        return this._timer;
        */
    }
}

/**
* Methods common to all components. 
* Can be overridden in each method object below.
*/
NewSiteApplication.prototype.commonComponentMethods = {
    show: function() {
        var instance = this.getInstance();
        try {
            instance.width = this.options.width;
            instance.height = this.options.height;
        }
        catch (e) { }
        this.toComponent("show");
    },
    hide: function() {
        var instance = this.getInstance();
        try {
            instance.width = 1;
            instance.height = 1;
        }
        catch (e) { }
        this.toComponent("hide");
    },
    getFlashMethod: function(method) {
        if (!this.parent) { return method; }
        return this.parent._prefix + this.compName + "_" + method;
    },
    info: {},
    getInfo: function() {
        return this.info;
    },
    setInfo: function(info) {
        this.info = info;
    },
    getInstance: function() {
        return this.instance;
    },
    toComponent: function(func, param) {

        try {
            var parent = this.parent
            var instance = this.getInstance();
            var flashFunc = this.getFlashMethod(func);

            // If a component doesn't have an instance set
            // then it is possibly in another SWF. Use the player container.
            //if ( ! instance )
            // instance = parent.videoPlayerComponent.getInstance();

            if (typeof instance[flashFunc] == "function") {
                instance[flashFunc](param);
                parent.traceMsg("toComponent(" + flashFunc + ")");
            }
            else if (typeof instance[func] == "function") {
                instance[func](param);
                parent.traceMsg("toComponent(" + func + ")");
            }
            else if (typeof instance.toFlash == "function") {
                instance.toFlash(func, param);
                parent.traceMsg("toComponent.toFlash(" + func + ")");
            }
            else {
                throw "Function not found";
            }

        }
        catch (e) { parent.reportError(e, "NewSiteApplication.toComponent<br>" + func + "<br>" + flashFunc + "<br>" + instance); }
    }
}

/**
* Methods specific to the video player component 
*/
NewSiteApplication.prototype.videoPlayerComponentMethods = {
    properties: {},
    openMenu: function() {
        this.toComponent('openMenu');
    },
    closeMenu: function() {
        this.toComponent('closeMenu');
    },
    setConfiguration: function(options) {
        this.getInstance().setConfiguration(options);
    },
    setSmoothing: function(ifSmoothInNormal, ifSmoothInFS) {
        this.getInstance().setSmoothing(ifSmoothInNormal, ifSmoothInFS);
    },
    setSize: function(w, h) {
        this.getInstance().width = w;
        this.getInstance().height = h;
        try {
            this.getInstance().setStageSize(w, h);
        } catch (e) {
        }
        this.getInstance().setSize(w, h);
    },
    getEmbedCode: function() {
        return this.getInstance().getEmbedCode();
    },
    getCurrentTime: function() {
        return this.getInstance().getCurrentTime();
    },
    enableFullscreen: function(flag) {
        this.toComponent('enableFullscreen', flag);
    },
    cueVideoById: function(id) {
        this.getInstance().cueVideoById(id);
    },
    playVideo: function(id, pos) {
        //id = this.parent._parseVideoId( id );
        this.toComponent('playVideo', id);
    },
    pauseVideo: function() {
        this.toComponent('pauseVideo');
    },
    stopVideo: function() {
        this.toComponent('stopVideo');
    },
    resumeVideo: function() {
        this.toComponent('resumeVideo');
    },
    next: function() {
        this.toComponent('nextVideo');
    },
    previous: function() {
        this.toComponent('previousVideo');
    },
    seek: function(position) {
        this.toComponent('seekVideo', position);
    },
    getProperty: function(propertyName) {
        return this.properties[propertyName];
    },
    getProperties: function() {
        return this.properties;
    },
    setProperties: function(propObj) {
        for (var i in propObj) {
            this.properties[i] = propObj[i];
        }
    },
    mute: function() {
        this.toComponent('muteVideo');
    },
    setVolume: function(volumeNumber) {
        this.toComponent('setPlayerVolume', volumeNumber);
    },
    trackComponent: function(domComponents) {
        this.toComponent('trackComponent', domComponents);
    },
    trackBeacon: function(beaconParam) {
        this.toComponent('trackBeacon', beaconParam);
    },
    submitToUrl: function(url) {
        this.toComponent('submitToUrl', url);
    },
    closeConnection: function() {
        this.toComponent('closeConnection');
    },
    relatedVideos : function(){
    	return this.getInstance().relatedVideos();
    },
    trackBannerClickThru: function(state){
         this.getInstance().trackBannerClickThru(state);
    },
    trackBannerLoaded: function(state){
         this.getInstance().trackBannerLoaded(state);
    },
    trackBannerError: function(state){
         this.getInstance().trackBannerError(state);
    },
    getInstance: function() {
        if (!this.instance) {
            this.instance = this.parent.registerFlashObject(this.parent.getPlayerContainerName());
        }
        return this.instance;
    }
}

/**
* Methods specific to the link component 
*/
NewSiteApplication.prototype.linkComponentMethods = {
    newLink: function(linkData) {
        this.setInfo(linkData);
        this.toComponent('newLink', linkData);
    }
}

/**
* Methods specific to the logo component 
*/
NewSiteApplication.prototype.logoComponentMethods = {}

/**
* Methods specific to the ad component 
*/
NewSiteApplication.prototype.adComponentMethods = {
    overlayAdBegin: function(adData) {
        this.setInfo(adData);
        this.toComponent('overlayAdBegin', adData);
    },
    videoAdBegin: function(adData) {
        this.setInfo(adData);
        this.toComponent('videoAdBegin', adData);
    },
    videoAdEnd: function() {
        this.toComponent('videoAdEnd');
    },
    getBannerState: function() {
        return this.getInstance().getBannerState();
    }
}

/**
* Methods specific to the metadata component 
*/
NewSiteApplication.prototype.metadataComponentMethods = {
    videoMetadata: function(metadata) {
        this.setInfo(metadata);
        this.toComponent('videoMetadata', metadata);
    }
}

/**
* Methods specific to the related video component 
*/
NewSiteApplication.prototype.relatedVideosComponentMethods = {
    relatedVideos: function(relatedVideoData) {
        this.setInfo(relatedVideoData);
        this.toComponent('relatedVideos', relatedVideoData);
    }
}

// * **************** *
// *  End App object  *
// * **************** *

// * ******************************************** 
// * Instantiate NewSite Singleton and initialize
// * ********************************************
if (!NewSite) {
    var NewSite = new NewSiteApplication;
    NewSite.run();
    //NewSite.loadGuidJS();
}
