//****************************************************************************
// Copyright (C) thePlatform for Media, Inc. All Rights Reserved.
//****************************************************************************

////// UTIL FUNCTIONS ////////////

// dynamically resize an element, for example, the category list
function tpResize(divID, height, width)
{
    var element = document.getElementById(divID);
    element.style.height = height + "px";
    element.style.width = width + "px";
}

// helper function for getting the "top" coordinate of an object
function tpGetTop(obj)
{
    result = 0;
    while (obj)
    {
        result += obj.offsetTop;
        obj = obj.offsetParent;
    }
    return result;
}

// helper function for getting the "left" coordinate of an object
function tpGetLeft(obj)
{
    result = 0;
    while(obj)
    {
        result += obj.offsetLeft;
        obj = obj.offsetParent;
    }
    return result;
}
tpThisMovie = function(movieName)
{
    var oDoc
    if (window.frame)
    {
        oDoc = frame.contentWindow.document || frame.contentDocument.document ;
    }
    else
    {
        oDoc = document
    }
    return oDoc.getElementById(movieName);
}
function tpDebug(str)
{
    if (document.getElementById("debugDiv"))
    {
        document.getElementById("debugDiv").innerHTML += str + "<br>";
    }
}

// open a new pop-up window
function tpOpenNewWindow(URLtoOpen, windowName, windowFeatures)
{
    var newWindow=window.open(URLtoOpen, windowName, windowFeatures); 
}

// handle tracking URLs
var tpTrackingImage = new Image();
function tpCallTrackingUrl(url)
{
    url = unescape(url);
    tpTrackingImage.src = url;
    for (i = 0; ((!tpTrackingImage.complete) && (i < 100000)); i++)
    {
    }
}

///// INIT tpController //////////
function tpGetUseJS() { return "true" }
function tpGetCommManagerID() { return tpCommID; }
tpLogLevel = "warn";
function tpSetLogLevel(level){ tpLogLevel = level };
function tpGetLogLevel() { return tpLogLevel }
function tpGetProperties()
{
    //each pdk controller will call this to get the default properties
    var props = new Object();
    props.commManagerId = tpGetCommManagerID();
    props.useJS = tpGetUseJS();
    props.registeredComponents = tpGetRegisteredIDs();
    props.logLevel = tpGetLogLevel();
    return props;
}

var tpRegisteredIDArr;
function tpRegisterID(swfName)
{
    if (!tpRegisteredIDArr) tpRegisteredIDArr = [];
    for (var i = 0; i < tpRegisteredIDArr.length; i++)
    {
        if (tpRegisteredIDArr[i] == swfName) return;
    }
    tpRegisteredIDArr.push(swfName);
}
function tpGetRegisteredIDs()
{
    return tpRegisteredIDArr;
}

// handle references to the communication manager
var tpController;
var tpCommID;
var tpBridgeID;
var tpExternalController;

//the kicks off the creation of the tpController, must be called before any pdk components are set on the page
function tpSetCommManagerID(commID, embed, commManagerUrl)
{
    // create a unique token for each set of player controls
    if (commID && embed)
    {
        //get rid of any existing commManager
        var divEl = window.document.getElementById("commManagerDiv");
        if (divEl)
        {
            divEl.parentNode.removeChild(divEl);
            divEl = null;
        }
        //create a commManager as the first element in the body
        divEl = window.document.createElement('div');
        divEl.id = "commManagerDiv";
        divEl.style.position = "absolute";
        divEl.style.top = "0";
        divEl.style.left = "0";
        var bodyTag = window.document.getElementsByTagName('body')[0];
        bodyTag.insertBefore(divEl, bodyTag.firstChild);
        var url = commManagerUrl ? commManagerUrl : "swf/commManager.swf"
        var so = new SWFObject(url, commID, "1", "1", "9.0.0.0");
        so.addParam("allowScriptAccess", "always");
        so.addParam("wmode", "transparent");
        so.write("commManagerDiv");
        
    }
    tpController = new tpControllerClass();
    tpCommID = commID;
    tpBridgeID = commID ? commID : "unknown";
    //external controller
    //tpCleanupExternal();
}

///// TPCONTROLLER CLASS /////////////

// implementation of the controller proxy in javascript
function tpControllerClass()
{
    //vars loaded at bottom
    /////// Send Messages to the rest of the app //////////////
    ///////////////////////////////////////////////////////////
    
    var me = this;
    
    if (window.addEventListener)
    {
        this.pageLoadListener = function(e) 
        {
            window.removeEventListener('load', me.pageLoadListener, true); 
            me.callFunction("htmlPageLoaded", [tpGetRegisteredIDs()])
        }; 
        window.addEventListener('load', this.pageLoadListener, true);
    }
    else if (window.attachEvent)
    {
        this.pageLoadListener = function() 
        {
            window.detachEvent('onload', me.pageLoadListener); 
            me.callFunction("htmlPageLoaded", [tpGetRegisteredIDs()])
        };
        window.attachEvent("onload", this.pageLoadListener);
    }
    else
    {
        //oops, this won't work;
        alert("this won't work");
    }
    
    //all communication to the communication manager happens here
    this.sendMessage = function(destination, message, skipBus)
    {
        //tpDebug("putting message on queue: " + message.name + " skipBus?" + skipBus + " canMessage?" + this.canMessage + " isLoading?" + this.isLoading);
        var sendObj = new Object();
        sendObj.message = message;
        sendObj.destination = destination;
        if (this.isLoading && !skipBus)
        {
            //these are low priority messages that should be sent only after OnPlayerLoaded is fired
            this.messageQueue.push(sendObj);
        }
        else if (!this.canMessage)
        {
            //these are high priority messages (like addEventListener or registerFunction) that usually need to be sent before OnPlayerLoaded is fired
            //but we still have to wait until after the communication manager has loaded or they'll just disappear
            this.priorityQueue.push(sendObj);
        }
        else
        {
            this.doSendMessage(sendObj);
        }
    }
    
    this.doSendMessage = function(sendObj)//private function
    {
        if (this.isShutDown) return;
        var obj = tpThisMovie(sendObj.destination);
        
        //tpDebug("sending: " + sendObj.message.name + " dest:" + sendObj.destination);
        // Flash ExternalInterface will convert any "" or " " string to null.  However,
        // in the PDK, null and "" mean different things.  So, if there are blank strings,
        // convert to a signal value, and then unconvert on the way out.
        /*for (var i=0; i<sendObj.parameters.length; i++)
        {
            var param = sendObj.parameters[i];
            if (typeof param == "string" && (param.length == 0 || param == " "))
            {
                sendObj.parameters[i] = this.blankString;
            }
        }*/
        //tpDebug("do send message: " + sendObj.message.name);
        obj.executeMessage(sendObj.message);    
    }

    this.checkMessageQueue = function()//private function
    {
        var len = this.messageQueue.length
        while (this.messageQueue.length > 0)
        {
            this.doSendMessage(this.messageQueue.shift());
        }
    }
    
    this.checkPriorityQueue = function()
    {
        while (this.priorityQueue.length > 0)
        {
            var sendObj = this.priorityQueue.shift();
            if (sendObj.destination == "unknown") sendObj.destination = tpBridgeID;
            this.doSendMessage(sendObj);
        }
    }
        
    this.wrapMessage = function(messageName, payload)
    {
        var comm = {globalDataType:this.getDataTypeName("CommInfo"), id:"javascript"}
        var message = {globalDataType:this.getDataTypeName("MessageInfo"), name:messageName, payload:payload, comm:comm};
        return message;
    }
    this.getDataTypeName = function(shortType)
    {
        switch(shortType)
        {
            case "AdPattern":return "com.theplatform.pdk.data::AdPattern";
            case "Banner":return "com.theplatform.pdk.data::Banner";
            case "BaseClip":return "com.theplatform.pdk.data::BaseClip";
            case "CallInfo":return "com.theplatform.pdk.communication::CallInfo";
            case "CategoryInfo":return "com.theplatform.pdk.data::CategoryInfo";
            case "Clip":return "com.theplatform.pdk.data::Clip";
            case "CommInfo":return "com.theplatform.pdk.communication::CommInfo";
            case "CustomData":return "com.theplatform.pdk.data::CustomData";
            case "CustomValue":return "com.theplatform.pdk.data::CustomValue";
            case "DispatchInfo":return "com.theplatform.pdk.communication::DispatchInfo";
            case "FunctionInfo":return "com.theplatform.pdk.communication::FunctionInfo";
            case "HandlerInfo":return "com.theplatform.pdk.communication::HandlerInfo";
            case "HyperLink":return "com.theplatform.pdk.data::HyperLink";
            case "MediaClick":return "com.theplatform.pdk.data::MediaClick";
            case "MediaFile":return "com.theplatform.pdk.data::MediaFile";
            case "MessageInfo":return "com.theplatform.pdk.communication::MessageInfo";
            case "MetricInfo":return "com.theplatform.pdk.data::MetricInfo";
            case "Overlay":return "com.theplatform.pdk.data::Overlay";
            case "PdkEvent":return "com.theplatform.pdk.events::PdkEvent";
            case "ProviderInfo":return "com.theplatform.pdk.data::ProviderInfo";
            case "Range":return "com.theplatform.pdk.data::Range";
            case "Rating":return "com.theplatform.pdk.data::Rating";
            case "Release":return "com.theplatform.pdk.data::Release";
            case "ReleaseInfo":return "com.theplatform.pdk.data::ReleaseInfo";
            case "ScopeInfo":return "com.theplatform.pdk.communication::ScopeInfo";
            case "Sort":return "com.theplatform.pdk.data::Sort";
            case "Subtitles":return "com.theplatform.pdk.data::Subtitles";
            case "TrackingUrl":return "com.theplatform.pdk.data::TrackingUrl";
            case "BandwidthPreferences":return "com.theplatform.pdk.data::BandwidthPreferences";
            case "Annotation":return "com.theplatform.pdk.data::Annotation";
        }
    }
    this.createScope = function(scope)
    {
        if (scope == undefined) return this.defaultScope;
        else
        {
            scope.push("javascript");
            //tpDebug("creating scopes: " + scope.toString());
            return {globalDataType:this.getDataTypeName("ScopeInfo"),controlId:"javascript",isGlobal:true,isAny:false,isEmpty:false,scopeIds:scope};
        }
    }
    
    //////// handle communication to the rest of the app ///////
    ////////////////////////////////////////////////////////////
    
    //register a function
    this.registerFunction = function(funcName, callback, scopes)
    {
        var scopeObj = this.createScope(scopes);
        var informComm = false;
        if (this.functions[funcName] == undefined)
        {
            this.functions[funcName] = new Object();
            informComm = true;
        }
        for (var i = 0; i < scopeObj.scopeIds.length; i++)
        {
            var s = scopeObj.scopeIds[i];
            if (s == "*") return;//can't register a scope of any
            this.functions[funcName][s] = callback;
        }
        if (informComm)
        {
            //send the registered function to the commManager
            var func = {globalDataType:this.getDataTypeName("FunctionInfo"),name:funcName,scope:scopeObj};
            var message = this.wrapMessage("registerFunction", func);
            this.sendMessage(tpBridgeID,message,true);
        }
    }
    
    this.unregisterFunction = function(funcName, scope)
    {
        var scopeObj = this.createScope(scopes);
        if (this.functions[funcName] != undefined)
        {
            var funcs = this.functions[funcName];
            for (var i = 0; i < scopeObj.scopeIds.length; i++)
            {
                var s = scopeObj.scopeIds[i];
                if (s == "*")
                {
                    delete funcs;//delete them all
                    break;
                }
                if (funcs[s] != undefined) delete funcs[s];//delete each scope
            }
            var funcsLeft = false;
            if (funcs != undefined)//prune the object
            {
                for (var sc in funcs)
                {
                    funcsLeft = true;
                    break;
                }
                if (!funcsLeft) delete this.functions[funcName];
            }
        }
        if (!funcsLeft)
        {
            var func = {globalDataType:this.getDataTypeName("FunctionInfo"),name:funcName,scope:scopeObj};
            var message = this.wrapMessage("unregisterFunction", func);
            this.sendMessage(tpBridgeID,message,true);
        }
    }
    
    this.addEventListener = function(eventName, callback, scope)
    {
        //tpDebug("adding event listener: " + eventName + " callback:" + callback);
        var scopeObj = this.createScope(scope);
        var handler = {globalDataType:this.getDataTypeName("HandlerInfo"),name:eventName,handler:callback,scope:scopeObj}
        var informComm = false;
        if (this.events[eventName] == undefined)
        {
            this.events[eventName] = new Array();
            informComm = true;
        }
        var evts = this.events[eventName];
        var repeat = false;
        for (var i = 0; i < evts.length; i++)//repeats?
        {
            if (evts[i].handler == callback)
            {
                evts[i] = handler;//replace the scopes
                repeat = true;
                break;
            }
        }
        if (!repeat) evts.push(handler);
        tpDebug("how many listeners? " + evts.length);
        if (informComm)
        {
            //tpDebug("informing comm of event listener");
            var message = this.wrapMessage("addEventListener", handler);
            this.sendMessage(tpBridgeID,message,true);
        }
    }
    this.removeEventListener = function(eventName, callback, scope)
    {
        if (this.events[eventName] != undefined)
        {
            var scopeObj = this.createScope(scope);
            var handler = {globalDataType:this.getDataTypeName("HandlerInfo"),name:eventName,handler:callback,scope:scopeObj}
        
            var eventArray = this.events[eventName];
            for (var i = 0; i < eventArray.length; i++)
            {
                var h = eventArray[i];
                if (h.handler == handler.handler)
                {
                    eventArray = eventArray.splice(i, 1);
                    break;
                }
            }
            if (eventArray.length == 0)
            {
                //no callbacks left, zap the variable
                delete this.events[eventName];
                var message = this.wrapMessage("removeEventListener", handler);
                this.sendMessage(tpBridgeID, message, true)
            }
        }
    }
    
    this.dispatchEvent = function(eventName, value, scope)
    {
        var scopeObj = this.createScope(scope);
        var evt = {globalDataType:this.getDataTypeName("PdkEvent"),type:eventName, data:value};
        var dispatch = {globalDataType:this.getDataTypeName("DispatchInfo"),evt:evt,scope:scopeObj};
        //check local events
        this.doDispatchEvent(dispatch);
        
        var message = this.wrapMessage("dispatchEvent", dispatch);
        this.sendMessage(tpBridgeID, message, true);
    }
        
    this.callFunction = function(funcName, args, scope)
    {
        var scopeObj = this.createScope(scope);
        var call = {globalDataType:this.getDataTypeName("CallInfo"),name:funcName,args:args,scope:scopeObj};
        this.doCallFunction(call);
        
        var message = this.wrapMessage("callFunction", call);
        this.sendMessage(tpBridgeID, message, true);
    }
    
    this.doDispatchEvent = function(dispatch)
    {
        //tpDebug("do dispatching event: " + dispatch.evt.type);
        if (this.events[dispatch.evt.type] != undefined)
        {
            var handlers = this.events[dispatch.evt.type]
            for (var i = 0; i < handlers.length; i++)
            {
                var handler = handlers[i];
                //tpDebug("checking handler? " + handler.handler)
                if (dispatch.scope.isAny)
                {
                    eval(handler.handler)(dispatch.evt);
                    continue;
                }
                for (var j = 0; j < handler.scope.scopeIds.length; j++)
                {
                    var s = handler.scope.scopeIds[j];
                    var fired = false;
                    if (s == "*")
                    {
                        eval(handler.handler)(dispatch.evt);
                        break;
                    }
                    for (var k = 0; k < dispatch.scope.scopeIds.length; k++)
                    {
                        tpDebug("running through scopes handler: " + s + " dispatch: " + dispatch.scope.scopeIds[k]);
                        if (s == dispatch.scope.scopeIds[k])
                        {
                            fired = true;
                            eval(handler.handler)(dispatch.evt);
                            break;
                        }
                    }
                    if (fired) break;//go to next handler
                }
            }
        }
    }
    
    this.doCallFunction = function(call)
    {
        if (this.functions[call.name] != undefined)
        {
            //check local functions
            var funcsToCall = new Object();
            for (var i = 0; i < call.scope.scopeIds.length; i++)
            {
                var s = call.scope.scopeIds[i];
                if (this.functions[call.name][s] != undefined)
                {
                    funcsToCall[this.functions[call.name][s]] = "f";//we need lookup
                }
            }
            for (var f in funcsToCall)
            {
                eval(f)(call.args);
            }
        }
    }
    
    
    
    
    //////// RECEIVE CALLS FROM AS //////////////
    ////////////////////////////////////////////
    
    //all communication from the communication manager happens here
    this.receiveMessage = function(destination, message)
    {
        if (destination == "javascript")
        {
            //tpDebug("receiving message: " + message.name)
            var messStr = message.name;
            switch(messStr)
            {
                
                case "commReady":
                    tpBridgeID = tpCommID;
                    this.canMessage = true;
                    this.checkPriorityQueue();
                    break;
                case "bridgeReady":
                    tpBridgeID = message.comm.id;
                    this.canMessage = true;
                    this.checkPriorityQueue();
                    break;
                case "dispatchEvent":
                    var dispatch = message.payload;
                    this.receiveEvent(dispatch);
                    break;
                case "callFunction":
                    var call = message.payload;
                    this.doCallFunction(call);
                    break;
                default:
                    break;
            }
        }
        else
        {
            //transfer the message to its final destination
            this.sendMessage(destination, message, true);
        }
    }
    this.receiveEvent = function(dispatch)
    {
        //tpDebug("RECIEVED:" + dispatch.evt.type)

        if (dispatch.evt.type == "OnPlayerLoaded")
        {
            this.isLoading = false;
            this.checkMessageQueue();
        }
        this.doDispatchEvent(dispatch);
    }
    
    //create a list of direct calls
    
    // PLAYER
    
    this.setRelease = function(release, replaceDefault, scope)
    {
        release = this.modRelease(release);
        this.callFunction("setRelease", [release, replaceDefault], scope);
    }
    this.loadRelease = function(release, replaceDefault, scope)
    {
        release = this.modRelease(release);
        this.callFunction("loadRelease", [release, replaceDefault], scope);
    }
    this.loadReleaseURL = function(releaseURL, replaceDefault, scope)
    {
        this.callFunction("loadReleaseURL", [releaseURL, replaceDefault], scope);
    }
    this.setReleaseURL = function(url, replaceDefault, scope)
    {
        this.callFunction("setReleaseURL", [url, replaceDefault], scope);
    }
    this.loadSmil = function(smil, replaceDefault, scope)
    {
        if (replaceDefault == undefined) replaceDefault = true;
        this.callFunction("loadSmil", [smil, replaceDefault], scope);
    }
    this.setSmil = function(smil, scope)
    {
        this.callFunction("setSmil", [smil], scope);
    }
    this.resetPlayer = function(scope)
    {
        this.callFunction("resetPlayer", [], scope);
    }
    this.setPlayerMessage = function(message, showDuration, scope)
    {
        if (showDuration == undefined) showDuration = 5000;
        this.callFunction("setPlayerMessage", [message, showDuration], scope);
    }
    this.clearPlayerMessage = function(scope)
    {
        this.callFunction("clearPlayerMessage", [], scope);
    }
    this.setCurrentReleaseList = function(id, scope)
    {
        this.callFunction("setCurrentReleaseList", [id], scope);
    }
    this.seekToPosition = function(position, scope)
    {
        this.callFunction("seekToPosition", [position], scope);
    }
    this.seekToPercentage = function(percent, scope)
    {
        this.callFunction("seekToPercentage", [percent], scope);
    }
    this.nextClip = function(scope)
    {
        this.callFunction("nextClip", [], scope);
    }
    this.previousClip = function(scope)
    {
        this.callFunction("previousClip", [], scope);
    }
    this.mute = function(muted, scope)
    {
        this.callFunction("mute", [muted], scope);
    }
    this.pause = function(paused, scope)
    {
        this.callFunction("pause", [paused], scope);
    }
    this.setPreviewImageUrl = function(url, scope)
    {
        this.callFunction("setPreviewImageUrl", [url], scope);
    }
    this.showFullScreen = function(isFullScreen, scope)
    {
        if (isFullScreen)
        {
            alert("Switching to full screen from Javascript is not supported by the Flash run-time. Flash only allows you to go to full screen mode via a click in the player itself.");
        }
        else
        {
            this.callFunction("showFullScreen", [isFullScreen], scope);
        }
    }
    this.showEmailForm = function(visible, scope)
    {
        this.callFunction("showEmailForm", [visible], scope);
    }
    this.showLinkForm = function(visible, scope)
    {
        this.callFunction("showLinkForm", [visible], scope);
    }
    this.trace = function(str, className, level)
    {
        this.callFunction("trace", [str, className, level], null); 
    }
    this.useDefaultPlayOverlay = function(useDefault, scope)
    {
        this.callFunction("useDefaultPlayOverlay", [useDefault], scope);
    }
    this.getUseDefaultPlayOverlay = function(scope)
    {
        this.callFunction("getUseDefaultPlayOverlay", [], scope);
    }
    this.useDefaultLinkForm = function(useDefault, scope)
    {
        this.callFunction("useDefaultLinkForm", [useDefault], scope);
    }
    this.useDefaultEmailForm = function(useDefault, scope)
    {
        this.callFunction("useDefaultEmailForm", [useDefault], scope);
    }
    this.getSubtitleLanguage = function(requestor, scope)
    {
        this.callFunction("getSubtitleLanguage", [requestor], scope);
    }
    this.clickPlayButton = function(scope)
    {
        this.callFunction("clickPlayButton", [], scope);
    }
    this.disablePlayerControls = function(disable, exceptions, scope)
    {
        this.callFunction("disablePlayerControls", [disable, exceptions], scope);
    }    
    this.hidePlayerRegions = function(hide, exceptions, scope)
    {
        this.callFunction("hidePlayerRegions", [hide, exceptions], scope);
    }
    this.setSubtitleLanguage = function(language, scope)
    {
        this.callFunction("setSubtitleLanguage", [language], scope);
    }    
    this.getPlayerVariables = function(names, scope)
    {
        this.callFunction("getPlayerVariables", [names], scope);    
    }
    this.setVolume = function(volume, scope)
    {
        this.callFunction("setVolume", [volume], scope);
    }
    this.clearAdCookie = function(scope)
    {
        this.callFunction("clearAdCookie", [], scope);
    }
    this.setBandwidthPreferences = function(bandwidthPreferences, scope)
    {
        if (bandwidthPreferences) bandwidthPreferences.globalDataType = this.getDataTypeName("BandwidthPreferences");
        this.callFunction("setBandwidthPreferences", [bandwidthPreferences], scope);
    }
    this.getBandwidthPreferences = function(scope)
    {
        this.callFunction("getBandwidthPreferences", [], scope);
    }                                               
    this.setVideoScalingMethod = function(method, scope)
    {
        this.callFunction("setVideoScalingMethod", [method], scope);
    }
    this.setExpandVideo = function(expand, scope)
    {
        this.callFunction("setExpandVideo", [expand], scope);
    }
    this.showPlayerCard = function(deckId, cardId, otherCardAction, parentCardId, animation, scope)
    {
        this.callFunction("showPlayerCard", [deckId, cardId, otherCardAction, parentCardId, animation], scope)
    }
    this.hidePlayerCard = function(deckId, animation, scope)
    {
        this.callFunction("hidePlayerCard", [deckId, animation], scope)
    }
    this.setVariable = function(widgetId, name, value, componentId)
    {
        //this does nothing except call the setProperty function to obviate
        //any syntactical errors. Our doc writers thought there might be
        //confusion because we use the term 'flashvars' a lot. 

        this.setProperty(widgetId, name, value, componentId);
    }
    this.setProperty = function(widgetId, name, value, componentId)
    {
        //alert("setProperty: " + componentId + ", " + name + ", " + value);
        this.callFunction("setProperty", [widgetId, name, value, componentId], null)
    }
    this.getNextClip = function(scope) 
    {        
        this.callFunction("getNextClip", [], scope);
    }

    this.addAnnotation = function(annotation)
    {
        if (annotation)
            annotation.globalDataType = this.getDataTypeName("Annotation");
        this.callFunction("addAnnotation",[annotation])
    }

    this.removeAnnotation = function(annotation)
    {
        if (annotation)
            annotation.globalDataType = this.getDataTypeName("Annotation");
        this.callFunction("removeAnnotation",[annotation])
    }

    this.clearAnnotations = function()
    {
        this.callFunction("clearAnnotations",[])
    }

    this.getAnnotations = function()
    {
        return this.callFunction("getAnnotations",[]);
    }
    
    // RELEASE MODEL
    
    this.refreshReleaseModel = function(category, search, sort, range, params, secondaryParams, scope, mediaIds, feedUrl)
    {
        if (sort) sort.globalDataType = this.getDataTypeName("Sort");
        if (range) range.globalDataType = this.getDataTypeName("Range");
        this.callFunction("refreshReleaseModel", [category, search, sort, range, params, secondaryParams, mediaIds, feedUrl], scope);
    }
    
    this.previewRefreshReleaseModel = function(category, search, sort, range, params, secondaryParams, scope, mediaIds, feedUrl)
    {
        if (sort) sort.globalDataType = this.getDataTypeName("Sort");
        if (range) range.globalDataType = this.getDataTypeName("Range");
        this.callFunction("previewRefreshReleaseModel", [category, search, sort, range, params, secondaryParams, mediaIds, feedUrl], scope);
    }
    this.previewNextRefreshReleaseModel = function(scope)
    {
        this.callFunction("previewNextRefreshReleaseModel", [], scope);
    }

    // CATEGORY MODEL
    
    this.refreshCategoryModel = function(params, scope, feedUrl)
    {
        this.callFunction("refreshCategoryModel", [params, feedUrl], scope);
    }
    
    // NAVIGATION
    
    this.nextRange = function(scope)
    {
        this.callFunction("nextRange", [], scope);
    }
    this.previousRange = function(scope)
    {
        this.callFunction("previousRange", [], scope);
    }
    this.getCurrentRange = function(scope)
    {
        this.callFunction("getCurrentRange", [], scope);
    }
    
    
    // CLIP INFO
    
    this.setClipInfo = function(clip, isDefault, scope)
    {
        clip = this.modClip(clip);
        this.callFunction("setClipInfo", [clip, isDefault], scope);
    }
    
    // CATEGORY LIST

    this.clearCategorySelection = function(scope) 
    {        
        this.callFunction("clearCategorySelection", [], scope);
    }

    // RELEASE LIST
    
    this.suspendPlayAll = function(suspend, scope) 
    {        
        this.callFunction("suspendPlayAll", [suspend], scope);
    }
    this.playNext = function(wrapAround, naturalEnd, scope) 
    {        
        this.callFunction("playNext", [wrapAround, naturalEnd], scope);
    }
    this.playPrevious = function(wrapAround, scope) 
    {        
        this.callFunction("playPrevious", [wrapAround], scope);
    }
    this.getNextRelease = function(wrapAround, naturalEnd, scope) 
    {        
        this.callFunction("getNextRelease", [wrapAround, naturalEnd], scope);
    }
    
    // GENERAL
    this.modRelease = function(release)
    {
        if (release)
        {
            release.globalDataType = this.getDataTypeName("Release");
            if (release.categories) release.categories = this.modCategories(release.categories);
            if (release.thumbnails)
            {
                for (var i = 0; i < release.thumbnails.length; i++)
                {
                    release.thumbnails[i].globalDataType = this.getDataTypeName("MediaFile");
                    if (release.thumbnails[i].customValues) release.thumbnails[i].customValues = this.modCustomValues(release.thumbnails[i].customValues);
                    
                }            
            }
            if (release.customValues) release.customValues = this.modCustomValues(release.customValues);
            if (release.metrics)
            {
                for (var i = 0; i < release.metrics.length; i++)
                {
                    release.metrics[i].globalDataType = this.getDataTypeName("MetricInfo");
                }
            }
            if (release.provider)
            {
                release.provider.globalDataType = this.getDataTypeName("ProviderInfo");
                if (release.provider.customValues) release.provider.customValues = this.modCustomValues(release.provider.customValues);
            }
            if (release.ratings)
            {
                for (var i = 0; i < release.ratings.length; i++)
                {
                    release.ratings[i].globalDataType = this.getDataTypeName("Rating");
                }
            }
            // backwards compatibility helper
            if (release.URL)
            {
                release.url = release.URL;
            }
        }
        return release;
    }                
    this.modCustomValues = function(customValues)
    {
        for (var i = 0; i < customValues.length; i++)
        {
            customValues[i].globalDataType = this.getDataTypeName("CustomValue");
        }
        return customValues;
    }
    this.modCategories = function(categories)
    {
        for (var i = 0; i < categories.length; i++)
        {
            categories[i].globalDataType = this.getDataTypeName("CategoryInfo");
        }
        return categories;
    }
    this.modClip = function(clip)
    {
        if (clip)
        {
            //set the class names correctly for casting in as3.
            clip.globalDataType = this.getDataTypeName("Clip");
            var baseClip = clip.baseClip;
            if (!baseClip) baseClip = new Object();
            if (clip.banners) baseClip.banners = clip.banners;
            if (clip.overlays) baseClip.overlays = clip.overlays;
            clip.baseClip = this.modBaseClip(baseClip);
            if (clip.chapter) clip.chapter.globalDataType = this.getDataTypeName("Chapter");
        }
        return clip;
    }
    this.modBaseClip = function(baseClip)
    {
        if (!baseClip) baseClip = new Object();
        baseClip.globalDataType = this.getDataTypeName("BaseClip");
        if (baseClip.moreInfo)
        {
            baseClip.moreInfo.globalDataType = this.getDataTypeName("HyperLink");
            if (baseClip.moreInfo.clickTrackingUrls) baseClip.moreInfo.clickTrackingUrls = this.modTracking(baseClip.moreInfo.clickTrackingUrls);
        }
        if (baseClip.banners)
        {
            for (var i = 0; i < baseClip.banners.length; i++)
            {
                baseClip.banners[i].globalDataType = this.getDataTypeName("Banner");
                if (baseClip.banners[i].clickTrackingUrls) baseClip.banners[i].clickTrackingUrls = this.modTracking(baseClip.banners[i].clickTrackingUrls)
            }
        }
        if (baseClip.overlays)
        {
            for (var i = 0; i < baseClip.overlays.length; i++)
            {
                baseClip.overlays[i].globalDataType = this.getDataTypeName("Overlay");
                if (baseClip.overlays[i].clickTrackingUrls) baseClip.overlays[i].clickTrackingUrls = this.modTracking(baseClip.overlays[i].clickTrackingUrls)
            }
        }
        if (baseClip.availableSubtitles)
        {
            for (var i = 0; i < baseClip.availableSubtitles; i++)
            {
                baseClip.availableSubtitles[i].globalDataType = this.getDataTypeName("Subtitles");
            }
        }
        if (baseClip.categories) baseClip.categories = this.modCategories(baseClip.categories);
        if (baseClip.adPattern) baseClip.adPattern.globalDataType = this.getDataTypeName("AdPattern");
        if (baseClip.trackingURLs) baseClip.trackingURLs = this.modTracking(baseClip.trackingURLs);
        if (baseClip.contentCustomData) baseClip.contentCustomData.globalDataType = this.getDataTypeName("CustomData");
        if (baseClip.ownerCustomData) baseClip.ownerCustomData.globalDataType = this.getDataTypeName("CustomData");
        if (baseClip.outletCustomData) baseClip.outletCustomData.globalDataType = this.getDataTypeName("CustomData");
        return baseClip;
    }
    this.modTracking = function(trackingUrls)
    {
        for (var i = 0; i < trackingUrls.length; i++)
        {
            trackingUrls.globalDataType = this.getDataTypeName("TrackingUrl")
        }
        return trackingUrls
    }
    this.shutDown = function()
    {
        var args = [];
        this.callFunction("shutDown", args, ["*"]);
        this.isShutDown = true;//prevent any more messages        
    }
    //vars initialized
    this.events = new Object();
    this.functions = new Object();
    this.isLoading = true;
    this.canMessage = false;
    this.messageQueue = new Array();
    this.priorityQueue = new Array();
    this.sendQueue = new Array();//yet another queue for timing externalInterface calls
    this.isSending = false;
    this.sendInterval;
    this.shutdownIDs;//array to keep all the controller ids for shutdown
    this.isShutDown = false;
    this.blankString = "__blank_string__";
    this.defaultScope = {globalDataType:this.getDataTypeName("ScopeInfo"),controlId:"javascript",isGlobal:true,isAny:false,isEmpty:false,scopeIds:["javascript","default"]};
    
}

function tpReceiveMessage(destination, message)
{
    //tpDebug("---message received---" + message.name + " dest:" + destination)
    tpController.receiveMessage(destination, message);
}


//functions for controlling external players

var tpHolderName = "pdkHolder";
var tpExternalJS;

function tpSetPlayerIDForExternal(playerName){};//no longer needed

function tpSetHolderIDForExternal(holderName)//needed
{
    tpHolderName = holderName;
}

function tpLoadExternalMediaJS()
{
    tpExternalJS = tpLoadExternalMediaJS.arguments;
    
    for (var i = 0; i < tpExternalJS.length; i++)
    {
        tpLoadScript(tpExternalJS[i]);
    }
}

function tpCleanupExternal()
{
    if (tpExternalJS)//if there's no external js, then nothing was loaded in
    {
        var scripts = window.document.getElementsByTagName('head')[0].getElementsByTagName('script');
        for (var i = 0; i < scripts.length;i++)
        {
            for (var j = 0; j < tpExternalJS.length; j++)
            {
                if (scripts[i].src == tpExternalJS[j])
                {
                    window.document.getElementsByTagName('head')[0].removeChild(scripts[i]);
                    break;
                }
            }
        }
        tpExternalJS.length = 0;
    }
    if (tpExternalController)
    {
        tpExternalController.cleanup();
    }
}


/////////////////////////////////////////////////////////////////////

tpScriptLoader = new ScriptLoader();

// called from flash via ExternalInterface
function tpLoadJScript(scriptFile, callback, id, atts)
{
    tpScriptLoader.addScript(scriptFile, callback, id, atts);
}

// need to wrap method to fix scoping issue on callback
function callbackDispatcher(loadObj) { tpScriptLoader.callbackDispatcher(loadObj) }
function invokeCallbacks(loadObj) { tpScriptLoader.invokeCallbacks() }

/////////////////////////////////////////////////////////////////////
//                    L O A D   O B J E C T
/////////////////////////////////////////////////////////////////////
function LoadObj(scriptFile, callback, id, atts)
{
    this.script = scriptFile;
    this.callback = callback;
    this.id = id;
    this.atts = atts;
}

/////////////////////////////////////////////////////////////////////
//                    S C R I P T   L O A D E R
/////////////////////////////////////////////////////////////////////


// constructor
function ScriptLoader()
{
    // queued up for loading scripts
    this.scriptQueue = new Array();
    
    // queued up for invoking callbacks
    this.callbackQueue = new Array();
}

/////////////////////////////////////////////////////////////////////
ScriptLoader.prototype.addScript = function(scriptFile, callback, id, atts)
{
    var loadObj = new LoadObj( scriptFile, callback, id, atts );
    this.scriptQueue.push(loadObj);
    
    // if the queue was empty, we need to kick
    // off the queue processing again.
    
    if (this.scriptQueue.length == 1)
        this.checkScriptQueue();
}

/////////////////////////////////////////////////////////////////////
ScriptLoader.prototype.checkScriptQueue = function()
{
    if (this.scriptQueue.length)
    {
        var loadObj = this.scriptQueue.shift();
        this.loadScript(loadObj);
    }
    else
    {
        // as a timing precaution, we wait until the queue
        // empties out before we invoke callbacks
        interval_id = setInterval("invokeCallbacks()",100) // more timing precautions :-/
        //this.invokeCallbacks();
    }
}
    
/////////////////////////////////////////////////////////////////////
ScriptLoader.prototype.callbackDispatcher = function(loadObj)
{
    for (var i in this.callbackQueue)
    {
        if (this.callbackQueue[i] == loadObj)
        {
            this.checkScriptQueue();
            return;
        }
    }
    this.callbackQueue.push(loadObj);
    this.checkScriptQueue();
}

/////////////////////////////////////////////////////////////////////
ScriptLoader.prototype.invokeCallbacks = function()
{
    clearInterval(interval_id);
    while (this.callbackQueue.length)
    {
        var loadObj = this.callbackQueue.shift();
        eval(loadObj.callback)(loadObj.script);
    }
}

/////////////////////////////////////////////////////////////////////
ScriptLoader.prototype.loadScript = function(loadObj)
{
    var scriptFilename = loadObj.script;
    var callbackFunction = loadObj.callback;
    var id = loadObj.id;
    var atts = loadObj.atts;
    
    // Create script element and set it to load the requested script
    var scriptEl = window.document.createElement('script');
    scriptEl.charset = "utf-8";
    if (id) scriptEl.id = id;
    scriptEl.type = "text/javascript";
    //scriptEl.defer = true;
    if (atts)
    {
        for (var i = 0; i < atts.length; i++)
            scriptEl.setAttribute(atts[i].att, atts[i].value);
    }
    scriptEl.src = scriptFilename;
    
    if (callbackFunction)
    {
        // Function to be called when script has finished loading
        var _onFinished = function(_loadObj, _callback)
        {
            // Invoke the callback function
            _callback(_loadObj)

            // Clean up event handlers
            this.onreadystatechange = null;
            this.onload = null;
            this.onerror = null;
        };

        // Set callback for IE
        // In defiance of MSDN documentation IE's script object has no onload handler
        scriptEl.onreadystatechange = function()
        {
            _onFinished(loadObj, callbackDispatcher);
        };

        // Set callback for W3C-compatible browsers
        scriptEl.onload = function()
        {
            _onFinished(loadObj, callbackDispatcher);
        };
        // Set another callback for W3C-compatible browsers
        // since onreadystatechange for IE also fires in case of an error
        scriptEl.onerror = function()
        {
            _onFinished(loadObj, callbackDispatcher);
        };
    }

    // Add script element to the document
    window.document.getElementsByTagName('head')[0].appendChild(scriptEl);
}

/////////////////////////////////////////////////////////////////////
// ORIGINAL LOADSCRIPT - USED BY MOVENETWORKS 
/////////////////////////////////////////////////////////////////////
function tpLoadScript(scriptFilename, callbackFunction, id, atts)
{
       // Create script element and set it to load the requested script
       var scriptEl = window.document.createElement('script');
       scriptEl.charset = "utf-8";
       if (id) scriptEl.id = id;
       scriptEl.type = "text/javascript";
       //scriptEl.defer = true;
       if (atts)
       {
           for (var i = 0; i < atts.length; i++)
           {
               scriptEl.setAttribute(atts[i].att, atts[i].value);
           }
       }
       scriptEl.src = scriptFilename;
       if (callbackFunction)
       {
           // Function to be called when script has finished loading
           var _onFinished = function(_callbackFunction, _scriptFilename)
           {
               // Invoke the callback function
               _callbackFunction(_scriptFilename);
   
               // Clean up event handlers
               this.onreadystatechange = null;
               this.onload = null;
               this.onerror = null;
           };
   
           // Set callback for IE
           // In defiance of MSDN documentation IE's script object has no onload handler
           scriptEl.onreadystatechange = function()
           {
               _onFinished(callbackFunction, scriptFilename);
           };
   
           // Set callback for W3C-compatible browsers
           scriptEl.onload = function()
           {
               _onFinished(callbackFunction, scriptFilename);
           };
           // Set another callback for W3C-compatible browsers
           // since onreadystatechange for IE also fires in case of an error
           scriptEl.onerror = function()
           {
               _onFinished(callbackFunction, scriptFilename);
           };
       }
   
       // Add script element to the document
       window.document.getElementsByTagName('head')[0].appendChild(scriptEl);
}

/////////////////////////////////////////////////////////////////////



//constructor for tpExternalControl
function tpExternalControllerClass()
{
    this.playerTypes = new Object();//keep a lookup of classes
    this.extPlayers = new Object();//keep the instances here
    
    this.registerExternalPlayer = function(type, playerClass)
    {
        this.playerTypes[type] = playerClass;//keep the class as a string
    }
    
    this.routeMessage = function(swfId, controllerId, streamType, funcName, args)
    {
        var curController = this.extPlayers[controllerId];//see if we have the existing controller lookup
        if (!curController) curController = this.extPlayers[controllerId] = {};//make a lookup for the controller
        var curPlayer = curController[streamType];//now see if we have the actual player object instantiated
        if (!curPlayer)
        {
            var playerClass = this.playerTypes[streamType];
            if (!playerClass) return; //we don't have the correct stream type on hand
            curPlayer = eval("new " + playerClass + "('" + swfId + "', '" + controllerId + "');");//create a new instance of the class
            if (!curPlayer) return;//abort here, too
            curController[streamType] = curPlayer;
        }
        curPlayer[funcName](args);
    }
    
    this.returnMessage = function(swfId, controllerId, funcName, args)
    {
        var obj = tpThisMovie(swfId);
        obj.receiveJSMessage(controllerId, funcName, args);
    }
    
    this.cleanup = function()
    {
        for (var controllerId in this.extPlayers)
        {
            var players = this.extPlayers[controllerId];
            for (var player in players)
            {
                players[player].cleanup();
                delete players[player];
            }
            delete this.extPlayers[controllerId];
        }
    }
}

function tpExternalMessage(swfId, controllerId, streamType, funcName, args)
{
    tpExternalController.routeMessage(swfId, controllerId, streamType, funcName, args);
}

//build this without needing the commManager
tpExternalController = new tpExternalControllerClass();

function tpShowAlert(alertCode) 
{
    switch(alertCode)
    {
        case "FULLSCREEN_DISABLED":
        //if (deconcept.SWFObjectUtil.getPlayerVersion().major < 9)
        alert("Full screen is only available with Flash 9 or later")
        break;
    }
}

