/*
  CodeBaby JavaScript Library
  Copyright (c) 2004-2006 CodeBaby Corp.
  All rights reserved.
  This source code can only be distributed in accordance with the terms of the license agreement entered into
  with CodeBaby.

  Version: LEX
  $Rev: 4129 $
  $Date: 2008-05-02 09:20:32 -0600 (Fri, 02 May 2008) $
  $URL: http://svn/svn/Development/JSLib/Trunk/Core/cb/javascript/core/CodeBaby.js $
*/	 

/*******************************************************************************
  NOTE: this is a core CodeBaby file, and should not be modified in any way, 
  unless absolutely necessary.
*******************************************************************************/


/**
  CodeBaby Constructor.  Creates an instance of a CodeBaby object, and writes
  it to the DIV.
  
  @author stevem 
*/ 
CodeBaby = function() { 
    
    // initialize members
    this.listeners = new Array();
    this.containerEventQueue = new Array();
    this.isContainerReady = false;
    this.isPlayerVisible = false;
    this.isSpeechBubbleVisible = false;
    this.containerName = "CodeBabyContainerMovie";
    this.speechBubbleName = "CodeBabySpeechBubbleMovie";
    
    // debugging constants
    this.debugQuery = "debug";
    this.debugQueryOn = "on";
    this.debugQueryOff = "off";
    this.debugCookie = "CodeBabyDebug";
    this.speechBubbleRepositionBuffer = 20;
    
    // create the connection strings
    this.containerProxyConnectionString = "CodeBabyContainerProxy" + new Date().getTime();
    this.speechBubbleProxyConnectionString = "CodeBabySpeechBubbleProxy" + new Date().getTime();
    this.containerSpeechConnectionString = "CodeBabyContainerSpeech" + new Date().getTime();
    this.speechContainerConnectionString = "CodeBabySpeechContainer" + new Date().getTime();
    
    // register this object as a listener of itself
    this.addListener(this); // order is very important -- this must be the first listener!
    
}


/**
  Loads the codebaby content (i.e. writes the flash tags)
  
  @author Steve Middleton
*/
CodeBaby.prototype.load = function() {
    
    // container flash tag and proxy
    this.containerProxy = new FlashProxy(
        this.containerProxyConnectionString,
        this.containerName, 
        this.gatewayMovie, 
        this);
    this.containerFlashTag = new FlashTag( 
        this.containerPath + this.stageName + "Container.swf", 
        this.containerWidth, 
        this.containerHeight,
        this.containerFlashVersion);
    this.containerFlashTag.setId(this.containerName);
    this.containerFlashTag.setWmode(this.containerWindowMode);
    this.containerFlashTag.setAllowScriptAccess("always");
    this.containerFlashTag.addFlashVar("fvProxyConnection", escape(this.containerProxyConnectionString)); 
    this.containerFlashTag.addFlashVar("fvSpeechBubbleSendConnection", escape(this.containerSpeechConnectionString)); 
    this.containerFlashTag.addFlashVar("fvSpeechBubbleReceiveConnection", escape(this.speechContainerConnectionString)); 
    this.containerFlashTag.addFlashVar("fvPublishRoot", escape(this.publishRoot)); 
    this.containerFlashTag.addFlashVar("fvProjectXMLURL", escape(this.projectXML)); 
    this.containerFlashTag.addFlashVar("fvStageName", escape(this.stageName));
    this.containerFlashTag.addFlashVar("fvSceneGroupName", escape(this.sceneGroupName));
    this.containerFlashTag.addFlashVar("fvDebug", escape(this.getDebug())); 
    this.containerFlashTag.addFlashVar("fvDefaultSpeechBubbleFadeSpeed", this.defaultSpeechBubbleFadeSpeed);
    
    // speech bubble flash tag and proxy
    this.speechBubbleProxy = new FlashProxy(
        this.speechBubbleProxyConnectionString,
        this.speechBubbleName, 
        this.gatewayMovie,
        this);
    this.speechBubbleFlashTag = new FlashTag( 
        this.speechBubbleMovie, 
        this.speechBubbleWidth, 
        this.speechBubbleHeight,
        this.speechBubbleFlashVersion);
    this.speechBubbleFlashTag.setId(this.speechBubbleName);
    this.speechBubbleFlashTag.setWmode(this.speechBubbleWindowMode);
    this.speechBubbleFlashTag.setAllowScriptAccess("always");
    this.speechBubbleFlashTag.addFlashVar("fvProxyConnection", escape(this.speechBubbleProxyConnectionString)); 
    this.speechBubbleFlashTag.addFlashVar("fvContainerReceiveConnection", escape(this.containerSpeechConnectionString));
    this.speechBubbleFlashTag.addFlashVar("fvContainerSendConnection", escape(this.speechContainerConnectionString));
    this.speechBubbleFlashTag.addFlashVar("fvCloseFadeSpeed", escape(this.defaultSpeechBubbleFadeSpeed)); 
  
    // write out the flash object tags for the speech bubble and Flash container         
    this.writeContainerFlashTag(); 
    this.writeSpeechBubbleFlashTag();
    
}


/**
  Adds a listener object.  All events will be fired to registered listeners.
  
  @param listener A listener object to be added.
  @author stevem
*/
CodeBaby.prototype.addListener = function(listener) {
    this.listeners.push(listener);
}


/**
  Removes a listener object.
  
  @param listener A listener object to be removed.
  @author stevem
*/
CodeBaby.prototype.removeListener = function(listener) {
    for (var i=0; i<listeners.length; i++) {
        if (listeners[i] == listener) {
            listeners.splice(i, 1); // remove the one entry
            break;
        }
    }
}


/**
  Sends an event to the codebaby movie.  Externally, you could just call
  codebabyInstanceName.containerProxy.call, but this makes it a bit more 
  abstract for the end user.
  
  @param eventType Any of the supported events: 
        "load", "play", "pause", "stop", "show", "hide", "userEvent", "setVariable"
  @param param1 Parameter 1 for the event (e.g. for "setVariable" param1 is the variable name).
  @param param2 Parameter 2 for the event (e.g. for "setVariable" param2 is the variable value).
  @param ... Additional eventTypes and params can be added 
         e.g. sendEvents("type1", "param1", "type2", "param2"); 
  @author stevem
*/
CodeBaby.prototype.sendEvents = function(eventType, param1, param2) {
    // create the event list
    var eventList = new Array();
    var messageTypes = "";
    for (var i=0; i<arguments.length; i+=3) {
        var eventObj = new Object();
        eventObj.type = arguments[i];
        eventObj.param1 = arguments[i+1];
        eventObj.param2 = arguments[i+2];
        eventList.push(eventObj);
        messageTypes += eventObj.type + ";";
    }

    this.containerEventQueue = this.containerEventQueue.concat(eventList);

    if (eventList.length > 0) {
        // either send or queue
        if (this.isContainerReady) {
            this.isContainerReady = false;
            this.containerProxy.call("onNewJSEvents", this.containerEventQueue);
            this.containerEventQueue.splice(0,this.containerEventQueue.length);
        }
    }
}


/**
  Fires an event to all of the listener objects.
  
  @param eventName The name of the event.  "on" + eventName will be invoked in
         all listeners.
  @param ... Any additional parameters are passed with the event.
  @author stevem
*/
CodeBaby.prototype.fireEvent = function(eventName) {
    // build the argument list
    var eventParams = new Array();
    for (var i=1; i<arguments.length; i++) {
        eventParams.push(arguments[i]);
    }
    
    // fire the event
    for (var i=0; i<this.listeners.length; i++) {
        if (this.listeners[i]["on" + eventName]) {
            // this.listeners[i]["on" + eventName].apply(this.listeners[i], eventParams);
            // this was changed for IE5 support (now limited to # of params)
            this.listeners[i]["on" + eventName](
                eventParams[0],
                eventParams[1],
                eventParams[2],
                eventParams[3],
                eventParams[4]
            );
        }
    }   
}


/**
  This function writes out the Flash container object tag to the HTML 
  page. The reason for this being placed in a seperate file 
  and not an HTML file is because of the EOLAS patent. If 
  this code was in the HTML page, in Internet Explorer,
  the user would have to click on any interactive Flash 
  movie ( e.g. with a message bubble ) before seeing it
  
  @author kevinp
*/	
CodeBaby.prototype.writeContainerFlashTag = function(){
    
    // get the flash OBJECT tag from the Flash Gateway and 
    // write it out to the html page    
    var container = document.getElementById(this.containerId);
    container.innerHTML = this.containerFlashTag.toString();
    
    // container is initially hidden (but must have one pixel visible with wmode=transparent)
    var topLeft = this.getRelativeTopLeft(container, true);
    container.style.position = "absolute";
    container.style.left = (1 + topLeft.x - this.containerWidth) + "px";
    container.style.top = (1 + topLeft.y - this.containerHeight) + "px";
    container.style.zIndex = 1000;
}  


/**
  This function writes out the Flash Speech Bubble object tag to the HTML 
  page. The reason for this being placed in a seperate file 
  and not an HTML file is because of the EOLAS patent. If 
  this code was in the HTML page, in Internet Explorer,
  the user wou;ld have to click on any interactive Flash 
  movie ( e.g. with a message bubble ) before seeing it
  
  @author kevinp
*/	
CodeBaby.prototype.writeSpeechBubbleFlashTag = function(){

    // get the flash OBJECT tag from the Flash Gateway and 
    // write it out to the html page    
    var speechBubble = document.getElementById(this.speechBubbleId);
    speechBubble.innerHTML =  this.speechBubbleFlashTag.toString();
  
    // set the initial style for the DIV
    // note: this MUST have at least 1 pixel visible in order to be processed
    // in FireFox
    var topLeft = this.getRelativeTopLeft(speechBubble, true);
    speechBubble.style.position = "absolute";
    speechBubble.style.left = (1 + topLeft.x - this.speechBubbleWidth) + "px";
    speechBubble.style.top = (1 + topLeft.y - this.speechBubbleHeight) + "px";
    speechBubble.style.zIndex = 1000;
}


/**
  Gets query string arguments from the URL.  
  (Taken from JavaScript: The Definitive Guide, David Flanagan)
  
  @return Object with properties specified by query vars
  @author stevem
*/
CodeBaby.prototype.getQueryArgs = function() {
    var args = new Object();
    var query = location.search.substring(1); // get query string
    var pairs = query.split("&");
    for (var i=0; i < pairs.length; i++) {
        var pos = pairs[i].indexOf('=');
        if (pos == -1) continue;
        var argname = pairs[i].substring(0,pos);
        var value = pairs[i].substring(pos+1);
        args[argname] = unescape(value);
    } 
    return args;
}


/**
  Gets the value for whether or not the javascript is forcing debug mode.  If
  specified by the query string, we save this value to the cookie.
  
  @return A string: "true" if debug should be on, "false" otherwise.
  @author vincel
*/
CodeBaby.prototype.getDebug = function() {
    // check for debug query string
    var args = this.getQueryArgs();
    if (args[this.debugQuery]) {
    	var debugCookie = new CBCookie(document, "CBDebug");
        if (args[this.debugQuery] == "on") {
            // set the cookie to "on"	
    		debugCookie.debugState = "on";
    		debugCookie.store();
            return "true";
        } else if (args[this.debugQuery] == "off") {
            // set the cookie to "off" 
    		debugCookie.debugState = "off";
    		debugCookie.store();
            return "false";
        }
    } 
    
    // if there is no query arg, look for the cookie
    var debugCookie = new CBCookie(document, "CBDebug");
    debugCookie.load();
    if (!debugCookie.debugState) {
    	return "false";
    } else {
    	if (debugCookie.debugState == "off") {	
    		return "false";
    	} else {
    		return "true";
    	}
    }
}


/**
  Gets the relative coordinates of the window's top left corner.  This is 
  required for proper hiding of the player when wmode=transparent.
  
  @param element The parent of the element we are relative to.
  @param accountForScrolling Boolean.  True if you want the top left of the 
         current view of the webpage (taking into account scrolling offset), or
         false if you want the actual top left of the page. 
  @return An object with "x" and "y" properties.
  @author Steve Middleton
*/
CodeBaby.prototype.getRelativeTopLeft = function(element, accountForScrolling) {
    var result = new Object();
    result.x = 0;
    result.y = 0;
    
    var level = 0;
    while (element) {
        result.x -= element.offsetLeft;
        result.y -= element.offsetTop;
        element = element.offsetParent;
    }
    
    if (accountForScrolling) {
        var scrollOffset = this.getScrollOffset();
        result.x += scrollOffset.x;
        result.y += scrollOffset.y;
    }
     
    return result;
}


/**
  Gets the type of browser.  Used for dragging routines, and for determining
  the top left pixel of the window (for displaying transparent wmode movies)
  
  @return Any one of the possible values: "Explorer", "Safari", or "Netscape",
          which included netscape compatible browsers (e.g. FireFox)
          Returns an empty string if could not determine browser type.
  @author Steve Middleton
*/
CodeBaby.prototype.getBrowserType = function() {
    
    var ua = navigator.userAgent;
    var vendor = navigator.vendor;
    var result = "";
    
    if (ua.indexOf("MSIE") >= 0) {
        result = "Explorer";
    } else if (ua.indexOf("Netscape") >= 0) {
        result = "Netscape";
    } else if (ua.indexOf("Gecko") >=0) {
        result = "Netscape";
    }
    
    if (vendor && vendor.indexOf("Apple") >= 0) {
        result = "Safari";
    }
    
    return result;
}


/**
  Gets the browser's scrolling offset.
  
  @return An object with "x" and "y" number properties.
  @author Steve Middleton
*/  
CodeBaby.prototype.getScrollOffset = function() {
    var scrollX, scrollY;
    switch (this.getBrowserType()) {
        case "Explorer":
            scrollX =   document.documentElement.scrollLeft +
                        document.body.scrollLeft;
            scrollY =   document.documentElement.scrollTop +
                        document.body.scrollTop;
            break;
        default:
            scrollX = window.scrollX;
            scrollY = window.scrollY;
            break;
    }
    
    var result = new Object();
    result.x = scrollX;
    result.y = scrollY;
    
    return result;
}

/**
  Updates the speech bubble position, so that if it is off the screen, we will
  reposition it (to the left of the console usually)

  @author Steve Middleton
*/
CodeBaby.prototype.updateSpeechBubblePosition = function() {
    var clientWidth =   isNaN(window.innerWidth) ? 
                        document.body.clientWidth :
                        window.innerWidth;
    var speechBubble = document.getElementById(this.speechBubbleId);
    
    
    // check if the speech bubble is visible
    if (this.isPlayerVisible) {
        if (this.isSpeechBubbleVisible) {
            // get the offset to the topleft corner from the speech bubble's parent
            var relTopLeft = this.getRelativeTopLeft(speechBubble.offsetParent, false);
            
            // get the browser's scrolling offset
            var scrollOffset = this.getScrollOffset();
            
            var fitsInNormalPosition = (-relTopLeft.x + this.speechBubbleX - 
              scrollOffset.x + this.speechBubbleWidth + 
              this.speechBubbleRepositionBuffer) < clientWidth;
            var fitsInAlternatePosition = (-relTopLeft.x + 
              this.speechBubbleAlternateX - scrollOffset.x - 
              this.speechBubbleRepositionBuffer) > 0;
            if (fitsInNormalPosition || !fitsInAlternatePosition) {
                // it fits in the normal location
                speechBubble.style.left = this.speechBubbleX + "px";
                speechBubble.style.top = this.speechBubbleY + "px";
            } else { 
                // try to place in the alternate location
                speechBubble.style.left = this.speechBubbleAlternateX + "px";
                speechBubble.style.top = this.speechBubbleAlternateY + "px";
            }
        } else {
            // hide it offscreen
            var relTopLeft = this.getRelativeTopLeft(speechBubble.offsetParent, true);
            speechBubble.style.left = (relTopLeft.x - this.speechBubbleWidth) + "px"; 
        }
    } else {
        // hide it offscreen
        var relTopLeft = this.getRelativeTopLeft(speechBubble.offsetParent, true);
        speechBubble.style.left = (relTopLeft.x - this.speechBubbleWidth) + "px";
    }
}


/**
  Updates the position of the container/console.
  
  @author Steve Middleton
*/
CodeBaby.prototype.updateContainerPosition = function() {
    var container = document.getElementById(this.containerId);
    if (this.isPlayerVisible) {
        container.style.left = this.containerX + "px";
        container.style.top = this.containerY + "px";
    } else {
        // can't set display = none because flash movie resets...
        var relTopLeft = this.getRelativeTopLeft(container.offsetParent, true);
        container.style.left = (relTopLeft.x - this.containerWidth) + "px";
    }
}


////////////////////////////////////////////////////////////////////////////////
// Event Handlers //////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/**
  The purpose of this function is to bypass the limitation in Flash of only 
  being able to send one message per frame. In this case we can send many 
  messages disguised as one message ( eventList )
 
  @param eventList a list of events which need to be executed
  @author kevinp 
*/ 
CodeBaby.prototype.onFlashEvents = function( eventList ) {
    for( var i = 0; i < eventList.length; i++ ) {
        this.fireEvent(eventList[i].type, eventList[i].param1, eventList[i].param2);
    }
} 


/**
  Called by the container when it is ready to receive events from JavaScript.
  This is called for the first time after the console is loaded.
  
  @author Steve Middleton
*/
CodeBaby.prototype.onContainerReady = function() {
    this.isContainerReady = true;

    // send the queued events
    var args = new Array();
    while (this.containerEventQueue.length > 0) {
        var eventObj = this.containerEventQueue.shift();
        args.push(eventObj.type);
        args.push(eventObj.param1);
        args.push(eventObj.param2);
    }
    
    if (args.length > 0) {
        // this.sendEvents.apply(this, args);
        // changed this for IE5 support
        this.sendEvents(
            args[0],
            args[1],
            args[2],
            args[3],
            args[4],
            args[5]
        );
    }

}


/**
  Called by the container when a new scene is loaded.
  
  @param sceneName The name of the new scene.
*/
CodeBaby.prototype.onSceneLoad = function(sceneName) {
    this.sceneName = sceneName;
}
 
 
/**
  Called by the container when the play state has changed.  E.g. the user has
  hit the "pause" button.
  
  @param newState The number corresponding to the new play state:
         playing = 0, stopped = 1, paused = 2, buffering = 3.
*/
CodeBaby.prototype.onPlayStateChange = function(newState) {
} 


/**
  Called by the flash container movie when the initialization is complete:
  XML data and the console movie is loaded.
  
  @param sceneList An array of the scenes for this scene group (e.g. "Scene1") 
*/ 
CodeBaby.prototype.onSceneGroupLoad = function(sceneList) {
    // completely hide the player
    this.updateContainerPosition();
}

  
/**
  Called when a cue is finished executing.  Only cues that have "monitorEvents"
  enabled in Production Studio will fire this event.
*/	
CodeBaby.prototype.onPlayerShow = function() { 
    if (!this.isPlayerVisible) {
        this.isPlayerVisible = true;
        this.updateContainerPosition();        
        this.updateSpeechBubblePosition();
    }
}

 
/**
  Called when a cue is executed (at the beginning of a cue).  Only cues that 
  have "monitorEvents" enabled in Production Studio will fire this event.
*/	
CodeBaby.prototype.onPlayerHide = function() { 
    if (this.isPlayerVisible) {
        this.isPlayerVisible = false;
        this.updateContainerPosition();  
        this.updateSpeechBubblePosition();
    }  
}

 
/**
  Called when the user clicks anywhere on the scene movie.
*/ 
CodeBaby.prototype.onSceneClick = function() {
}


/**
  Called when the user has clicked the "draggable" area of the console.
  This tells the JavaScript to start dragging.
*/
CodeBaby.prototype.onConsoleDrag = function() {
    this.isConsoleDragging = true;
}

/**
  Called when the console sends a "custom" event.  This way, you can add custom
  controls to your console which causes some action in JavaScript.
  
  @param param Custom parameter, sent by the console movie.
*/	
CodeBaby.prototype.onConsoleCustomEvent = function(param) { 
}

/**
  Called when an "external action", as entered in Production Composer, is fired.
  
  @param name The name of the external action
  @param description The description entered in Production Composer
*/
CodeBaby.prototype.onExternalAction = function(name, description) {
}


/**
  Called when the flash movie is requesting a url change.  Normally, this could
  just be done within flash, but since we are using the flash gateway (for 
  flash player 7 support), we must do this through JavaScript.

  @param url The url we need to go to.
  @param target The target for the url (e.g. "_blank", "_self")
*/
CodeBaby.prototype.onGetURL = function (url, target) {
    window.open(url, target);
}


/**
  Called when a debug message is sent from the CodeBaby flash container.  When
  debugging, you might write these messages to a DIV for example.
  
  @param message The debug message as a string.   
*/	
CodeBaby.prototype.onDebugMessage = function(message) { 
    if (!this.debugWindowCreated) {
        this.debugWindowCreated = true;
        this.debugWindow = window.open("", "CodeBabyDebugWindow", "location=no,menubar=no,resizable=yes,scrollbars=yes,status=no,toolbar=no");
        this.debugWindow.document.title = "CodeBaby Debug";
        this.debugLog = "";
        window.focus();
    }
    var date = new Date();
    var dateString =    date.getHours() + "h:" + 
                        date.getMinutes() + "m:" + 
                        date.getSeconds() + "s:" + 
                        date.getMilliseconds() + "ms";
    this.debugLog += "<table><tr valign='top'><td width='200px'><b>" + dateString + "</b></td><td>" + message + "</td></tr></table>";
    if (this.debugWindow) {
        this.debugWindow.document.body.innerHTML = this.debugLog;
    }
}


/**
  Called when any speech bubble is displayed.
*/	
CodeBaby.prototype.onShowSpeechBubble = function () {
    if (!this.isSpeechBubbleVisible) {
        this.isSpeechBubbleVisible = true;
        this.updateSpeechBubblePosition();
    }
}


/**
  Called when any speech bubble starts to fade out.
*/	
CodeBaby.prototype.onHideSpeechBubble = function () { 
}


/**
  Called when any speech bubble is hidden/closed.
*/	
CodeBaby.prototype.onSpeechBubbleHidden = function () { 
    if (this.isSpeechBubbleVisible) {
        this.isSpeechBubbleVisible = false;
        this.updateSpeechBubblePosition();
    }
}


/**
  Called when a speech bubble link is clicked (i.e. the user has responded to
  a speech bubble question).
  
  @param speechBubbleName The name of the speech bubble that send the response
  @param linkId The ID of the link that was clicked.
*/	
CodeBaby.prototype.onResponse = function(speechBubbleName, linkId) {
}


/**
  Called by CodeBabyDrag indicating that dragging has stopped.
*/
CodeBaby.prototype.onDragStop = function() {
    // reset the dragging flag (set by onConsoleDrag)
    this.isConsoleDragging = false;
}

