/* <copyright>
This file contains proprietary software owned by Motorola Mobility, Inc.<br/>
No rights, expressed or implied, whatsoever to this software are provided by Motorola Mobility, Inc. hereunder.<br/>
(c) Copyright 2011 Motorola Mobility, Inc.  All Rights Reserved.
</copyright> */


/*
 *	Manage state instances
 */
stateManager = function()
{
	// a stack of states
	this.stateStack = [];
	
	// number of states on the stack
    this.stateTop       = undefined;
    
    // the states of the context
    this.RDGEInitState	= null;
    this.RDGERunState	= null;
    
    this.currentState = function()
    {
		if(this.stateTop != undefined)
			return this.stateStack[this.stateTop];
			
		return null;
    }
	
	/*
	 *  Push new IRuntime state - engine executes the new state
	 */
	this.PushState = function(state, flags) 
	{
		if(state!=null && typeof state.Init == 'function')
		{
			if(this.stateTop != undefined)
				this.stateStack[this.stateTop].LeaveState();
			
			if(flags == undefined || flags != "noInit")
				state.Init();
				
			this.stateTop = this.stateStack.push(state) - 1;
		}
	}

	/*
	 *  Remove IRuntime state from stack, engine executes previous state
	 */
	this.PopState = function()
	{
		state = this.stateStack.pop();
		if(state!=null)
		{
			state.Shutdown();
		}
		
		this.stateTop = this.stateTop > 0 ? this.stateTop - 1 : 0;
	    
		if(this.stateStack[this.stateTop])
		{
			this.stateStack[this.stateTop].ReInit();
		}
	}

	/*
	 *  Remove all states from the stack
	 */
	this.PopAll = function()
	{
		while(this.stateStack[this.stateTop] != null)
		{
			this.PopState();
		}
	}
	
	this.tick = function(dt)
	{
		if(this.stateStack[this.stateTop]!=null) 
		{
			this.stateStack[this.stateTop].Update(dt);
			this.stateStack[this.stateTop].Resize();
			this.stateStack[this.stateTop].Draw();
		}
	}
}

g_enableBenchmarks = true;
function Engine()
{
	this._assetPath = "assets/";

    // map of scene graphs to names
    this.sceneMap = [];
    
    // number of states on the stack
    this.stateTop       = undefined;
    
    // size of the browser window
    this.lastWindowWidth	= window.innerWidth;
    this.lastWindowHeight	= window.innerHeight;
       
    this.defaultContext = null;
    
    this.lightManager = null;
    
    clearColor = [0.0, 0.0, 0.0, 0.0];
    
    panelObjectManager	= new objectManager();
    
    this.initializeComplete = false;
    
    this.RDGECanvas = null;
    
    /*
     *	a map of canvas names to renderer
     */
    this.canvasToRendererMap = {};
    
    /*
     *	states to canvas map - maps a state stack to the canvas context it belongs to
     */
    this.canvasNameToStateStack = {};
    
    /*
     *	the list of context's that are active
     */
    this.canvasCtxList = [];
    
    /*
     *	regex object to verify runtime object is not some sort of exploit
     */
    invalidObj = new RegExp("([()]|function)");
    
    isValidObj = function( name )
    {
		// do a quick test make sure user isn't trying to execute a function
		if(invalidObj.test(name))
		{
			window.console.error("invalid object name passed to RDGE, " + name + " - looks like a function");
			return false;
		}
		
		return true;
    }
    
    /*
     *	The context definition - every context shares these parameters
     */
    contextDef = function()
    {
        this.id = null;	
        this.renderer = null;
        this.ctxStateManager = null;
        this.startUpState = null;
        this.sceneGraphMap = [];
        this.currentScene = null;
        this.getScene = function()
        {
			return this.sceneGraphMap[this.currentScene];
        }
        this.debug = 
        {
			'frameCounter' : 0,
			'mat4CallCount': 0
        }
	}
    
    // maintains the contexts
    contextManager = new objectManager();
    this.ctxMan = contextManager;
    
    // the context currently being updated
    contextManager.currentCtx = null;
       
    contextManager._addObject = contextManager.addObject;
    contextManager.contextMap = {};
    
    contextManager.addObject = function( context )
    {
		this.contextMap[context.id] = context;
		return this._addObject(context);
    }
    
    contextManager.start = function()
    {
		var len = this.objects.length;
		for(var i = 0; i < len; ++i)
		{
		    // set the current context
		    contextManager.currentCtx = this.objects[i];
		    this.objects[i].ctxStateManager.PushState(this.objects[i].startUpState);
		}
    }
    
    contextManager.forEach = function(cb)
    {
		var len = this.objects.length;
		for(var i = 0; i < len; ++i)
		{
			cb(this.objects[i]);
		}
    }
    
    this.getContext = function( optCanvasID )
    {
		if(!optCanvasID)
		{
			return contextManager.currentCtx;
		}
		else
		{
			return contextManager.contextMap[optCanvasID];
		}
    }

	this.clearContext = function( canvasID )
	{
		contextManager.contextMap[canvasID] = undefined;
	}
    
    /*
     *	give the contextID (canvas id) of the context to set
     */
    this.setContext = function( contextID )
    {
        contextManager.currentCtx = contextManager.contextMap[contextID];
    }
	
	this.tickContext = function(contextID) {
	    var savedCtx = contextManager.currentCtx;
	    contextManager.currentCtx = contextManager.contextMap[contextID];
	    this.objects[i].ctxStateManager.tick(dt);
	    contextManager.currentCtx = savedCtx;

	}

	this.remapAssetFolder = function( url )
	{
		var searchStr = "assets/";
		var index = url.indexOf( searchStr );
		var rtnPath = url;
		if (index >= 0)
		{
			rtnPath = url.substr( index + searchStr.length );
			rtnPath = this._assetPath + rtnPath;
		}
		return rtnPath;
	}
    
}

/*
 *   Initialize the RDGE web engine
 */
Engine.prototype.init = function(userInitState, userRunState, canvasObject)
{
    this.GlInit(canvasObject);

    globalParamFuncSet =  function(param)
    {
		this.data = param.data;
		this.type =param.type;
		
    	this.set  = function(v)
    	{
			var len = this.data ? this.data.length : 0;
			for(var i=0;i<len;++i)
				this.data[i]=v[i];
    	}
    	this.get = function()
    	{
    		if( this.data.length == undefined ) 
    		{
    			return this.data;
    		}
    		else 
    		{    		
    			return this.data.slice();
    		}
    	}
    }
    
    // light manager init before global parameters structure is reconfigured
    this.lightManager = new LightManager(rdgeGlobalParameters.rdge_lights);
    	
    // added getter and setter to global uniforms
    for(var p in rdgeGlobalParameters)
    {
		if(p != "rdge_lights")
		{
			rdgeGlobalParameters[p] = new globalParamFuncSet(rdgeGlobalParameters[p]);
		}
		else
		{
			var lights = rdgeGlobalParameters[p];
			for(var l in lights)
			{
				rdgeGlobalParameters[l] = new globalParamFuncSet(lights[l]);
			}
		}
    }
    
    // initial window
    this.lastWindowWidth = window.innerWidth;
    this.lastWindowHeight = window.innerHeight;

    // setup default render context
    this.defaultContext = new RenderContext();

    this.defaultContext.uniforms = [
        { 'name': "u_matAmbient", 'value': [0.02,0.02,0.02, 1.0] },
        { 'name': "u_matDiffuse", 'value': [1.0, 1.0, 1.0, 1.0] },
        { 'name': "u_matSpecular", 'value': [1.0, 1.0, 1.0, 1.0] },
        { 'name': "u_matShininess", 'value': [128.0] },
        { 'name': "u_matEmission", 'value': [0.0, 0.0, 0.0, 1.0] }
    ];
    
	// startup the contexts
    contextManager.start();
		
    this.initializeComplete = true;
}

// shutdown the engine clears all states
Engine.prototype.Shutdown = function()
{
    this.PopAll();
}

// initialize WebGL
Engine.prototype.GlInit = function( canvasObject)
{
    // Initialize
    var canvases = document.getElementsByTagName("canvas");
    
    // transverse the canvases and create the contexts
    var numCv = canvases.length;
    for( var cvIdx = 0; cvIdx < numCv; ++cvIdx)
    {
	    var canvas;
		
		// if this canvas has a rdge attribute initialize the render context
		var rdgeAttr = canvases[cvIdx].getAttribute("rdge");
		if(rdgeAttr == "true")
		{
			// hack ~ while implementing multi-context
			canvas = canvases[cvIdx];
			this.registerCanvas(canvas);
		}

    }
/*
    canvas.addEventListener("webglcontextlost", contextLostHandler, false);
    canvas.addEventListener("webglcontextrestored", contextRestoredHandler, false);
*/
   
}

Engine.prototype.loadScene = function(name)
{
	var url = "assets_web/mesh/" + name + ".json"
	
	// if we are not in the load state than push it on again
	if(contextManager.currentCtx.stateMan.currentState().name == "RunState")
	{
		contextManager.currentCtx.stateMan.PushState(contextManager.currentCtx.stateMan.RDGEInitState);
		contextManager.currentCtx.loadScene(url, name);
	}
	
}

Engine.prototype.getScene = function(name)
{
	return contextManager.currentCtx.sceneGraphMap[name];
}

Engine.prototype.AddScene = function(name, sceneGraph)
{	
	contextManager.currentCtx.sceneGraphMap[name] = sceneGraph;
	contextManager.currentCtx.currentScene = name;
}


Engine.prototype.createRDGEPanel=function() 
{
	var panel = new utilDbgPanel('tools','WebGL Viewer Settings');
	panel.appendLabel("", "");
	var panelID=panelObjectManager.addObject(panel)	// adding to object manager will fill out the object async

	return panelID;
}

Engine.prototype.getRDGEPanel=function(panelID) 
{
	return panelObjectManager.handleToObject(panelID);
}

Engine.prototype.registerCanvas = function(canvas, runState) {
    if (canvas && this.getContext(canvas.rdgeid))
	    return;
 
    canvas.renderer = new _renderer(canvas); 	// create the renderer for the context
    this.canvasToRendererMap[canvas.rdgeid] = canvas; // store the canvas in the context map
    canvas.renderer.id = canvas.rdgeid;

    // configure the state manager for this context
    var stateMan = new stateManager();

    // add this context to the contextManager and attach the handle to DOM canvas for user retrieval
    var context = new contextDef();

    context.id = canvas.rdgeid;
    context.renderer = canvas.renderer;
    context.ctxStateManager = stateMan;
    context.startUpState;
    context.fpsTracker = new fpsTracker(canvas.rdgeid);

    context.renderer.mvMatrix = mat4.identity();
    context.renderer.invMvMatrix = mat4.identity();
    context.renderer.projectionMatrix = mat4.identity();
    context.renderer.normalMatrix = mat4.identity();

    canvas.rdgeCtxHandle = contextManager.addObject(context);

    // set new context as the current context so that when the runtime object is instantiated
    // it can use the context during construction
    var oldCtx = contextManager.currentCtx;
    contextManager.currentCtx = context;

    var _runState;

    // check for runtime handlers
    if (runState) {
        _runState = runState;
    }
    else {
        var runAttr = canvas.getAttribute("rdgerun");

        if (runAttr) {
            // make sure attribute is valid
            if (!isValidObj(runAttr))
                return;
            try {
                var state = eval(runAttr);
                _runState = new state();
            }
            catch (err) {
                window.console.error("The provided RDGE state object \"" + runAttr + "\" is not defined");
            }
        }
        else {
            _runState = {};
            validateUserState(_runState);
        }
    }

    // check for a scene
    var sceneName = canvas.getAttribute("rdgescene");

    // setup the RDGE states passing the user state or undefined
    stateMan.RDGEInitState = new LoadState(_runState, context);
    stateMan.RDGERunState = new RunState(_runState, context);

    // fill out any user state missing methods with dummy methods
    validateUserState(_runState);

    if (sceneName) {
        stateMan.RDGEInitState.sceneName = sceneName;

        // run is now always the bottom state, loading can happen at any time
        stateMan.PushState(stateMan.RDGERunState, "noInit");

        context.startUpState = stateMan.RDGEInitState;
    }
    else {
        context.startUpState = stateMan.RDGERunState;
    }

    if (this.initializeComplete) {
        context.ctxStateManager.PushState(context.startUpState);
    }

    // restore previous context
    // NOTE: Ninja requires this to be commented out!
    //	if (oldCtx)
    //	{
    //		contextManager.currentCtx = oldCtx;
    //	}
}

Engine.prototype.unregisterCanvas = function(canvas) {
    stat.closePage(canvas.rdgeid + "_fps");
    contextManager.removeObject(canvas.rdgeCtxHandle);
	this.clearContext( canvas.rdgeid );
}

Engine.prototype.getCanvas = function( id )
{
	return this.canvasToRendererMap[id];
}