text: /*globals module exports resource require BObject BArray FLIP_Y_AXIS SHOW_REDRAW_REGIONS*/
  /*jslint undef: true, strict: true, white: true, newcap: true, browser: true, indent: 4 */
  "use strict";
  
  var util = require('util'),
      geo = require('geometry'),
      ccp = geo.ccp,
      events    = require('events'),
      Scheduler = require('./Scheduler').Scheduler,
      EventDispatcher = require('./EventDispatcher').EventDispatcher,
      Scene = require('./nodes/Scene').Scene;
  
  
  /**
   * requestAnimationFrame for smart animating
   * @see http://paulirish.com/2011/requestanimationframe-for-smart-animating/
   */
  window.requestAnimFrame = (function (){
      return  window.requestAnimationFrame       || 
              window.webkitRequestAnimationFrame || 
              window.mozRequestAnimationFrame    || 
              window.oRequestAnimationFrame      || 
              window.msRequestAnimationFrame     || 
              function (callback) {
                  window.setTimeout(callback, 1000 / 30);
              };
  })();
  
  var Director = BObject.extend(/** @lends cocos.Director# */{
      backgroundColor: 'rgb(0, 0, 0)',
      canvas: null,
      context: null,
      sceneStack: null,
      winSize: null,
      isPaused: false,
      maxFrameRate: 30,
      displayFPS: false,
      preloadScene: null,
      isReady: false,
  
      // Time delta
      dt: 0,
      nextDeltaTimeZero: false,
      lastUpdate: 0,
  
      _nextScene: null,
  
      /**
       * <p>Creates and handles the main view and manages how and when to execute the
       * Scenes.</p>
       *
       * <p>This class is a singleton so don't instantiate it yourself, instead use
       * cocos.Director.get('sharedDirector') to return the instance.</p>
       *
       * @memberOf cocos
       * @constructs
       * @extends BObject
       * @singleton
       */
      init: function () {
          Director.superclass.init.call(this);
  
          this.set('sceneStack', []);
      },
  
      /**
       * Append to a HTML element. It will create a canvas tag
       *
       * @param {HTMLElement} view Any HTML element to add the application to
       */
      attachInView: function (view) {
          if (!view.tagName) {
              throw "Director.attachInView must be given a HTML DOM Node";
          }
  
          while (view.firstChild) {
              view.removeChild(view.firstChild);
          }
  
  
          var canvas = document.createElement('canvas');
          this.set('canvas', canvas);
          canvas.setAttribute('width', view.clientWidth);
          canvas.setAttribute('height', view.clientHeight);
  
          var context = canvas.getContext('2d');
          this.set('context', context);
  
          if (FLIP_Y_AXIS) {
              context.translate(0, view.clientHeight);
              context.scale(1, -1);
          }
  
          view.appendChild(canvas);
  
          this.set('winSize', {width: view.clientWidth, height: view.clientHeight});
  
  
          // Setup event handling
  
          // Mouse events
          var eventDispatcher = EventDispatcher.get('sharedDispatcher');
          var self = this;
          function mouseDown(evt) {
              evt.locationInWindow = ccp(evt.clientX, evt.clientY);
              evt.locationInCanvas = self.convertEventToCanvas(evt);
  
              function mouseDragged(evt) {
                  evt.locationInWindow = ccp(evt.clientX, evt.clientY);
                  evt.locationInCanvas = self.convertEventToCanvas(evt);
  
                  eventDispatcher.mouseDragged(evt);
              }
              function mouseUp(evt) {
                  evt.locationInWindow = ccp(evt.clientX, evt.clientY);
                  evt.locationInCanvas = self.convertEventToCanvas(evt);
  
                  document.body.removeEventListener('mousemove', mouseDragged, false);
                  document.body.removeEventListener('mouseup',   mouseUp,   false);
  
  
                  eventDispatcher.mouseUp(evt);
              }
  
              document.body.addEventListener('mousemove', mouseDragged, false);
              document.body.addEventListener('mouseup',   mouseUp,   false);
  
              eventDispatcher.mouseDown(evt);
          }
          function mouseMoved(evt) {
              evt.locationInWindow = ccp(evt.clientX, evt.clientY);
              evt.locationInCanvas = self.convertEventToCanvas(evt);
  
              eventDispatcher.mouseMoved(evt);
          }
          canvas.addEventListener('mousedown', mouseDown, false);
          canvas.addEventListener('mousemove', mouseMoved, false);
  
          // Keyboard events
          function keyDown(evt) {
              this._keysDown = this._keysDown || {};
              eventDispatcher.keyDown(evt);
          }
          function keyUp(evt) {
              eventDispatcher.keyUp(evt);
          }
          /*
          function keyPress(evt) {
              eventDispatcher.keyPress(evt)
          }
          */
          document.documentElement.addEventListener('keydown', keyDown, false);
          document.documentElement.addEventListener('keyup', keyUp, false);
          /*
          document.documentElement.addEventListener('keypress', keyPress, false);
          */
      },
  
      runPreloadScene: function () {
          var preloader = this.get('preloadScene');
          if (!preloader) {
              var PreloadScene = require('./nodes/PreloadScene').PreloadScene;
              preloader = PreloadScene.create();
              this.set('preloadScene', preloader);
          }
  
          events.addListener(preloader, 'complete', util.callback(this, function (preloader) {
              this.isReady = true;
              events.trigger(this, 'ready', this);
          }));
  
          this.pushScene(preloader);
          this.startAnimation();
      },
  
      /**
       * Enters the Director's main loop with the given Scene. Call it to run
       * only your FIRST scene. Don't call it if there is already a running
       * scene.
       *
       * @param {cocos.Scene} scene The scene to start
       */
      runWithScene: function (scene) {
          if (!(scene instanceof Scene)) {
              throw "Director.runWithScene must be given an instance of Scene";
          }
  
          if (this._runningScene) {
              throw "You can't run a Scene if another Scene is already running. Use replaceScene or pushScene instead";
          }
  
          this.pushScene(scene);
          this.startAnimation();
      },
  
      /**
       * Replaces the running scene with a new one. The running scene is
       * terminated. ONLY call it if there is a running scene.
       *
       * @param {cocos.Scene} scene The scene to replace with
       */
      replaceScene: function (scene) {
          var index = this.sceneStack.length;
  
          this._sendCleanupToScene = true;
          this.sceneStack.pop();
          this.sceneStack.push(scene);
          this._nextScene = scene;
      },
  
      /**
       * Pops out a scene from the queue. This scene will replace the running
       * one. The running scene will be deleted. If there are no more scenes in
       * the stack the execution is terminated. ONLY call it if there is a
       * running scene.
       */
      popScene: function () {
      },
  
      /**
       * Suspends the execution of the running scene, pushing it on the stack of
       * suspended scenes. The new scene will be executed. Try to avoid big
       * stacks of pushed scenes to reduce memory allocation. ONLY call it if
       * there is a running scene.
       *
       * @param {cocos.Scene} scene The scene to add to the stack
       */
      pushScene: function (scene) {
          this._nextScene = scene;
      },
  
      /**
       * The main loop is triggered again. Call this function only if
       * cocos.Directory#stopAnimation was called earlier.
       */
      startAnimation: function () {
  		this.animate();
  
      },
  	animate: function() {
  		this.drawScene();
  		window.requestAnimFrame(util.callback(this, 'animate'), this.canvas);
  	},
  
      /**
       * Stops the animation. Nothing will be drawn. The main loop won't be
       * triggered anymore. If you want to pause your animation call
       * cocos.Directory#pause instead.
       */
      stopAnimation: function () {
          if (this._animationTimer) {
              clearInterval(this._animationTimer);
              this._animationTimer = null;
          }
      },
  
      /**
       * Calculate time since last call
       * @private
       */
      calculateDeltaTime: function () {
          var now = (new Date()).getTime() / 1000;
  
          if (this.nextDeltaTimeZero) {
              this.dt = 0;
              this.nextDeltaTimeZero = false;
          }
  
          this.dt = Math.max(0, now - this.lastUpdate);
  
          this.lastUpdate = now;
      },
  
      /**
       * The main run loop
       * @private
       */
      drawScene: function () {
          this.calculateDeltaTime();
  		
          if (!this.isPaused) {
              Scheduler.get('sharedScheduler').tick(this.dt);
          }
  
  
          var context = this.get('context');
          context.fillStyle = this.get('backgroundColor');
          context.fillRect(0, 0, this.winSize.width, this.winSize.height);
          //this.canvas.width = this.canvas.width
  
  
          if (this._nextScene) {
              this.setNextScene();
          }
  
          var rect = new geo.Rect(0, 0, this.winSize.width, this.winSize.height);
  
          if (rect) {
              context.beginPath();
              context.rect(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
              context.clip();
              context.closePath();
          }
  
          this._runningScene.visit(context, rect);
  
          if (SHOW_REDRAW_REGIONS) {
              if (rect) {
                  context.beginPath();
                  context.rect(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
                  context.fillStyle = "rgba(255, 0, 0, 0.5)";
                  //context.fill();
                  context.closePath();
              }
          }
  
          if (this.get('displayFPS')) {
              this.showFPS();
          }
      },
  
      /**
       * Initialises the next scene
       * @private
       */
      setNextScene: function () {
          // TODO transitions
  
          if (this._runningScene) {
              this._runningScene.onExit();
              if (this._sendCleanupToScene) {
                  this._runningScene.cleanup();
              }
          }
  
          this._runningScene = this._nextScene;
  
          this._nextScene = null;
  
          this._runningScene.onEnter();
      },
  
      convertEventToCanvas: function (evt) {
          var x = this.canvas.offsetLeft - document.documentElement.scrollLeft,
              y = this.canvas.offsetTop - document.documentElement.scrollTop;
  
          var o = this.canvas;
          while ((o = o.offsetParent)) {
              x += o.offsetLeft - o.scrollLeft;
              y += o.offsetTop - o.scrollTop;
          }
  
          var p = geo.ccpSub(evt.locationInWindow, ccp(x, y));
          if (FLIP_Y_AXIS) {
              p.y = this.canvas.height - p.y;
          }
  
          return p;
      },
  
      showFPS: function () {
          if (!this._fpsLabel) {
              var Label = require('./nodes/Label').Label;
              this._fpsLabel = Label.create({string: '', fontSize: 16});
              this._fpsLabel.set('anchorPoint', ccp(0, 1));
              this._frames = 0;
              this._accumDt = 0;
          }
  
  
          this._frames++;
          this._accumDt += this.get('dt');
          
          if (this._accumDt > 1.0 / 3.0)  {
              var frameRate = this._frames / this._accumDt;
              this._frames = 0;
              this._accumDt = 0;
  
              this._fpsLabel.set('string', 'FPS: ' + (Math.round(frameRate * 100) / 100).toString());
          }
  
  
          var s = this.get('winSize');
          this._fpsLabel.set('position', ccp(10, s.height - 10));
  
          this._fpsLabel.visit(this.get('context'));
      }
  
  });
  
  /**
   * Class methods
   */
  util.extend(Director, /** @lends cocos.Director */{
      /**
       * A shared singleton instance of cocos.Director
       *
       * @getter sharedDirector
       * @type cocos.Director
       */
      get_sharedDirector: function (key) {
          if (!this._instance) {
              this._instance = this.create();
          }
  
          return this._instance;
      }
  });
  
  exports.Director = Director;
  