'use strict';

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// FORBID MOUSE RIGHT-CLICK DEFAULT BEHAVIOR
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// (execute as soon as possible)
$(window).on('contextmenu', function()
{
  return false;
});

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// OPTIMIZED WINDOW RESIZE (https://developer.mozilla.org/en-US/docs/Web/Events/resize)
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

(function() {
  var throttle = function(type, name, obj) {
    var obj = obj || window;
    var running = false;
    var func = function() {
      if (running) { return; }
      running = true;
      requestAnimationFrame(function() {
        obj.dispatchEvent(new CustomEvent(name));
        running = false;
      });
    };
    obj.addEventListener(type, func);
  };

  /* init - you can init any event */
  throttle('resize', 'optimizedResize');
})();

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

/**
 * User input for application - keyboard and mouse. Registers all event handlers and their behavior.
 *
 * @author smrceant (Antonin Smrcek)
 *
 * @constructor
 */
var Control = function()
{
  /**
   * Mapping keys -> actions. We use this to execute correct action when user presses a key.
   *
   * For example: KeyEvent.DOM_VK_LEFT -> 'LOOK_LEFT'.
   *
   * @private
   */
  this._keyboardToActionsMap = {};

  // assign default controls
  this.setKeyMapping   (Control.ACTIONS_TO_KEYBOARD_MAP_DEFAULT);

  // register keyboard handlers
  document.addEventListener('keydown', this._keyboardCallbackDown.bind (this)); // (JS note: have to use bind in this case, otherwise this would refer to document, not control)
  document.addEventListener('keyup', this._keyboardCallbackUp.bind (this));

  // it would be cleaner to register this to the webgl CANVAS, but there is an onscreen <div> for GUI which is over the entire screen,
  // and this <div> eats all the events... and since not all browsers support CSS 'pointer-events: none' so the events could go through, it is safer to register events to the document
  this.registerApplicationMouseListeners (document);
  //this.registerApplicationMouseListeners (gui.$onscreenGui_elem);

  // (add event listeners for mouse wheel for all browsers)
  document.addEventListener('mousewheel',     this._mouseWheelCallback.bind (this));
  document.addEventListener('DOMMouseScroll', this._mouseWheelCallback.bind (this)); // firefox

  window.addEventListener('optimizedResize', this._resizeCallback.bind (this));

  ////////////////////////////////////////////
  // MOUSE
  ////////////////////////////////////////////

  /**
   * Mouse position (absolute screen coordinates, origin in [0,0] of document).
   *
   * @type {THREE.Vector2}
   */
  this.mouse = new THREE.Vector2 (canvas.getBoundingClientRect().left, canvas.getBoundingClientRect().bottom);
  // note: (we init it with canvas left and bottom, so the computeCanvasMouseCoordinates() will return [0,0] at the
  //       start of the application, otherwise picking might try to access outside of screen if mouse was initialized to [0,0];
  //       the problem is, when application is started, no mouse event were fired yet, so the real mouse position is not known and the value is left unchanged)

  /**
   * TRUE from the time when user presses the left button and moves the mouse (it is sufficient to move just once), until
   * the time when he releases it. Used for preventing accidental opening of object infos while looking around.
   *
   * @type {boolean}
   */
  this.mouseWasBeingDragged = false;

  /**
   * Mouse position (OpenGL coordinate system, used for picking).
   *
   * @type {THREE.Vector2}
   */
  this.mouseCanvas = new THREE.Vector2();

  /**
   * (image coordinate system).
   *
   * @type {THREE.Vector2}
   */
  this.mouseCanvas2d = new THREE.Vector2();

  /**
   *
   * @type {boolean}
   * @private
   */
  this._mouseCanvas2dComputed = false;

  /**
   * Is mouse right button pressed?
   *
   * @type {boolean}
   */
  this.mouseRightDown = false;

  /**
   * Is mouse left button pressed?
   *
   * @type {boolean}
   */
  this.mouseLeftDown = false;

  /**
   * Is mouse cursor out of the CANVAS element?
   * @type {boolean}
   */
  this.mouseOutOfCanvas = false;

  /**
   *
   * @type {THREE.Vector2}
   * @private
   */
  this._tmp_mouseMovement = new THREE.Vector2();

  ////////////////////////////////////////////
  // ACTIONS
  ////////////////////////////////////////////

  /**
   * Records which keyboard keys are pressed, i.e. which actions are performed. Keyed by key values from
   * Control.ACTIONS_TO_KEYBOARD_MAP_DEFAULT and similar.
   *
   * @type {Object}
   */
  this.actionsState = {};

  ////////////////////////////////////////////
  // SUSPENSION OF CONTROLS
  ////////////////////////////////////////////

  /**
   * TRUE if application shall not react to keyboard events.
   *
   * @type {boolean}
   */
  this.keyboardSuspended = false;

  /**
   * TRUE if application shall not react to mouse events.
   *
   * @type {boolean}
   */
  this.mouseSuspended = false;

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

   /**
   * TRUE if warning about Edge browser (that it handles key events badly) has been already displayed.
   *
   * For more info on the issue, see for example:
   * https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/9021586/
   * https://forum.unity3d.com/threads/unity-5-5-microsoft-edge-character-sticks-at-direction.448050/
   *
   * @type {boolean}
   */
  this.edgeWarningAlreadyDisplayed = false;
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// EVENT LISTENER REGISTRATION
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * Registers mouse listeners for application. Typically used for CANVAS element, but also for some others (e.g. pickable model caption <div>).
 *
 * @param {HTMLElement|HTMLDocument} element   element which will have registered mouse listeners for application
 */
Control.prototype.registerApplicationMouseListeners = function (element)
{
  element.addEventListener ('mousemove', this._mouseMoveCallback.bind (this));
  element.addEventListener ('mousedown', this._mouseDownCallback.bind (this));
  element.addEventListener ('mouseup',   this._mouseUpCallback.bind   (this));
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MOUSE BUTTON CONSTANTS
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * Values correspond to https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent.which
 *
 * @readonly
 * @enum {number}
 */
Control.Mouse =
{
  MOUSE_NONE:   0,
  MOUSE_LEFT:   1,
  MOUSE_MIDDLE: 2,
  MOUSE_RIGHT:  3
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// KEY MAPPING
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * Default controls - no strafing.
 */
Control.ACTIONS_TO_KEYBOARD_MAP_DEFAULT =
{
  'MOVE_FORWARD':     KeyEvent.DOM_VK_UP,
  'MOVE_BACK':        KeyEvent.DOM_VK_DOWN,
  'STRAFE_LEFT':      KeyEvent.DOM_VK_COMMA,
  'STRAFE_RIGHT':     KeyEvent.DOM_VK_PERIOD,

  'LOOK_UP':          KeyEvent.DOM_VK_W,
  'LOOK_DOWN':        KeyEvent.DOM_VK_X, // S is better, but we have assigned it to some other function
  'LOOK_LEFT':        KeyEvent.DOM_VK_LEFT,
  'LOOK_RIGHT':       KeyEvent.DOM_VK_RIGHT,

  'FLY_UP':           KeyEvent.DOM_VK_PAGE_UP,
  'FLY_DOWN':         KeyEvent.DOM_VK_PAGE_DOWN,

  'JUMP':             KeyEvent.DOM_VK_SHIFT,
  'SWITCH_WALKFLY':   KeyEvent.DOM_VK_CONTROL,

  'OPEN_MAP':         KeyEvent.DOM_VK_TAB,
  'OPEN_HELP':        KeyEvent.DOM_VK_F1,
  'CHOOSE_VIEWPOINT': KeyEvent.DOM_VK_V,
  'RESET_VIEWPOINT':  KeyEvent.DOM_VK_R,

  'PAUSE_TOUR':       KeyEvent.DOM_VK_SPACE,
};
Control.ACTIONS_TO_KEYBOARD_MAP_DEFAULT.__enumNames = Util.getEnumKeys(Control.ACTIONS_TO_KEYBOARD_MAP_DEFAULT);

// (only controls which are different from default ones are redefined)
Control.ACTIONS_TO_KEYBOARD_MAP_STRAFING =
{
  'STRAFE_LEFT':  KeyEvent.DOM_VK_LEFT,
  'STRAFE_RIGHT': KeyEvent.DOM_VK_RIGHT,

  'LOOK_LEFT':    KeyEvent.DOM_VK_A,
  'LOOK_RIGHT':   KeyEvent.DOM_VK_D
};
Control.ACTIONS_TO_KEYBOARD_MAP_STRAFING = Util.inheritUnchangedKeys (Control.ACTIONS_TO_KEYBOARD_MAP_DEFAULT, Control.ACTIONS_TO_KEYBOARD_MAP_STRAFING);
Control.ACTIONS_TO_KEYBOARD_MAP_STRAFING.__enumNames = Util.getEnumKeys(Control.ACTIONS_TO_KEYBOARD_MAP_STRAFING);

/**
 * Same method as _createKeyboardToActionsMap(), but this is intended to be called publicly (thus clearer name).
 *
 * @param {Object}  actionsToKeyboardMap  Control.ACTIONS_TO_KEYBOARD_MAP_STRAFING or Control.ACTIONS_TO_KEYBOARD_MAP_DEFAULT
 */
Control.prototype.setKeyMapping = function (actionsToKeyboardMap)
{
  this._createKeyboardToActionsMap (actionsToKeyboardMap);
};

/**
 * From given mapping (actions -> keyboard), it creates mapping (keyboard -> action).
 *
 * @param   {Object}  actionsToKeyboardMap    mapping (actions -> keyboard)
 * @private
 */
Control.prototype._createKeyboardToActionsMap = function(actionsToKeyboardMap)
{
  var _this = this;

  Object.keys(actionsToKeyboardMap).forEach
  (
      function (action)
      {
        var currentKey = actionsToKeyboardMap[action];
        _this._keyboardToActionsMap[currentKey] = action;
      }
  );

  // also don't forget to update enum names
  _this._keyboardToActionsMap.__enumNames = Util.getEnumKeys (_this._keyboardToActionsMap);
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// TURNING CONTROL PRESETS ON/OFF
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * Turns strafing with side arrows on/off.
 *
 * @param {boolean}   [on]    TRUE if strafing should be turned on (default is FALSE)
 */
Control.prototype._turnStrafing = function (on)
{
  if (!Util.isInitialized(on) || (on === true))
  {
    this.setKeyMapping (Control.ACTIONS_TO_KEYBOARD_MAP_STRAFING);
  }
  else
  {
    this.setKeyMapping (Control.ACTIONS_TO_KEYBOARD_MAP_DEFAULT);
  }
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// DOCUMENT/WINDOW CALLBACKS
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * Called on window resize. Our entire resizing for the application handles gui resize callback (but of course it
 * further delegates)>
 *
 * @private
 */
Control.prototype._resizeCallback = function()
{
  gui.resizeCallback();
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MOUSE CALLBACKS
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * Callback for when mouse wheel is manipulated.
 *
 * @param     {Event}   event
 * @private
 */
Control.prototype._mouseWheelCallback = function (event)
{
  if (appMain.mainLoopSuspended)
  {
    return;
  }
  event.preventDefault();

  if (this.mouseSuspended)
  {
    if (Preferences.Application.isDevMode())
    {
      console.warn ('Mouse suspended.');
    }

    return;
  }

  // http://www.sitepoint.com/html5-javascript-mouse-wheel/
  var delta = Math.max ( -1, Math.min (1, (event.wheelDelta || -event.detail)) );

  // wheel up
  if (delta > 0)
  {
    if (!collision.move (appMain.getCurrentEntity().camera, appMain.getCurrentEntity(), appMain.getCurrentEntity().camera.moveCamera, Camera.MOVE_FORWARD_WHEEL))
    {
      // tell engine that we want to hold red keyboard for a while
      collision.keepForbiddenKeyboardForFrameId = clock.elapsedFrames;
    }
  }
  // wheel down
  else
  {
    if (!collision.move (appMain.getCurrentEntity().camera, appMain.getCurrentEntity(), appMain.getCurrentEntity().camera.moveCamera, Camera.MOVE_BACK_WHEEL))
    {
      collision.keepForbiddenKeyboardForFrameId = clock.elapsedFrames;
    }
  }
};

/**
 * Callback for when mouse button is pressed down.
 *
 * @param   {Event}   event
 * @private
 */
Control.prototype._mouseDownCallback = function (event)
{
  if (appMain.mainLoopSuspended || $(event.target).parents('.dg.ac').length)
  {
    return;
  }

  // this forces Chrome NOT to change cursor to text when dragging, I hope it does not mess something
  event.preventDefault();

  // prevents multiple firing of the event
  event.stopPropagation();

  if (this.mouseSuspended)
  {
    if (Preferences.Application.isDevMode())
    {
      console.warn ('Mouse suspended.');
    }

    return;
  }

  if (event.which === Control.Mouse.MOUSE_RIGHT)
  {
    control.mouseRightDown = true;
  }

  if (event.which === Control.Mouse.MOUSE_LEFT)
  {
    control.mouseLeftDown = true;
  }

  if (event.which === Control.Mouse.MOUSE_MIDDLE)
  {
    appMain.getCurrentControllingEntity().jump();
  }
};

/**
 * Callback for when a mouse button is released.
 *
 * @param   {Event}   event
 * @private
 */
Control.prototype._mouseUpCallback = function (event)
{
  if (appMain.mainLoopSuspended || $(event.target).parents('.dg.ac').length)
  {
    return;
  }

  // prevents multiple firing of the event
  event.stopPropagation();

  if (event.which === Control.Mouse.MOUSE_LEFT)
  {
    control.mouseLeftDown = false;

    // user clicked on the pickable object (only consider click if he wasn't just dragging the view)
		if ($(event.target).is('#pickableObjectUnderMouse')) {
	    if (!this.mouseWasBeingDragged && picking.currentPickableObject && !gui.justClickedGuiButton)
	    {
	      gui.showPickableObjectInfo (picking.currentPickableObject);
	    }
		}

    gui.justClickedGuiButton = false;

    this.mouseWasBeingDragged = false;
  }

  if (event.which === Control.Mouse.MOUSE_RIGHT)
  {
    event.preventDefault();

    control.mouseRightDown = false;
  }
};

/**
 * Callback for when mouse is being moved.
 *
 * @param   {Event}   event
 * @private
 */
Control.prototype._mouseMoveCallback = function (event)
{
  this.resetComputationFlags();

  this.mouseOutOfCanvas = false;

  if (this.mouseLeftDown)
  {
    this._tmp_mouseMovement.x =  event.movementX       ||
                                 event.mozMovementX    ||
                                 0;
    this._tmp_mouseMovement.y =  event.movementY       ||
                                 event.mozMovementY    ||
                                 0;

    this.mouseWasBeingDragged |= this._tmp_mouseMovement.x || this._tmp_mouseMovement.y;

    /////////////////////////////////////////////////
    // CAMERA ANGLE CHANGE DETERMINATION
    /////////////////////////////////////////////////

    // determine camera angle change from sign of the mouse position change (both X and Y change can occur simultaneously)

    //////////////////////////
    // X change (horizontal)
    //////////////////////////

    if (this._tmp_mouseMovement.x < 0)
    {
      appMain.getCurrentEntity().getCamera().moveAngle (Camera.MOVE_LEFT);
    }
    else if (this._tmp_mouseMovement.x > 0)
    {
      appMain.getCurrentEntity().getCamera().moveAngle (Camera.MOVE_RIGHT);
    }

    //////////////////////////
    // Y change (vertical)
    //////////////////////////

    if (this._tmp_mouseMovement.y < 0)
    {
      if (!Preferences.Control.INVERTED_MOUSE_Y)
      {
        appMain.getCurrentEntity().getCamera().moveAngle (Camera.MOVE_FORWARD);

      }
      else
      {
        appMain.getCurrentEntity().getCamera().moveAngle (Camera.MOVE_BACK);
      }
    }
    else if (this._tmp_mouseMovement.y > 0)
    {
      if (!Preferences.Control.INVERTED_MOUSE_Y)
      {
        appMain.getCurrentEntity().getCamera().moveAngle (Camera.MOVE_BACK);
      }
      else
      {
        appMain.getCurrentEntity().getCamera().moveAngle (Camera.MOVE_FORWARD);
      }
    }
    /////////////////////////////////////////////////

  }

  this.mouse.x = event.clientX;
  this.mouse.y = event.clientY;
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// (PHYSICAL) KEYBOARD CALLBACKS
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * Callback for when key is pressed down on the physical keyboard.
 *
 * @param   {Event}  keyEvent
 * @private
 */
Control.prototype._keyboardCallbackDown = function (keyEvent)
{
  var action = this._keyboardToActionsMap[keyEvent.keyCode];

  // stop bubbling and prevent default behavior for keys which we use in the application
  if (this.actionsState[action])
  {
    keyEvent.stopPropagation();
    keyEvent.preventDefault();
  }

  // (there are separate functions for callback and actual processing because sometimes we want to initiate the processing without actual keypress event
  //  happening, e.g. in case of virtual keyboard)
  this._keyboardDown (keyEvent.keyCode);
};

/**
 * Callback for when key is released on the physical keyboard.
 *
 * @param   {Event}  keyEvent
 * @private
 */
Control.prototype._keyboardCallbackUp = function (keyEvent)
{
  var action = this._keyboardToActionsMap[keyEvent.keyCode];

  if (this.actionsState[action])
  {
    keyEvent.stopPropagation();
    keyEvent.preventDefault();
  }

  this._keyboardUp (keyEvent.keyCode);
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// (PHYSICAL AND VIRTUAL) KEYBOARD 'CALLBACKS', WHICH PERFORM ACTUAL ACTIONS
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * Method called when key on the physical or virtual keyboard is released.
 *
 * @param     {number}  keyCode   ASCII code of the released key
 * @private
 */
Control.prototype._keyboardUp = function (keyCode)
{
  // (note: we do not check for keyboard suspension there! we want to release the keys at all times)

  var action = this._keyboardToActionsMap[keyCode];
  this.actionsState[action] = false;
};

Control.prototype.escapeKey = function()
{
    // no opened dialog (window), close/open menu
    if (!WindowDialog.currentlyOpenedDialog) {
      gui.mainMenu.visible ? gui.mainMenu.close() : gui.mainMenu.showMenu();
    }
    else {
      WindowDialog.currentlyOpenedDialog.close();
    }
}

/**
 * Method called when key on the physical or virtual keyboard is pressed down.
 *
 * @param     {number}  keyCode   ASCII code of the pressed key
 * @private
 */
Control.prototype._keyboardDown = function (keyCode)
{
  switch (keyCode)
  {
     case KeyEvent.DOM_VK_ESCAPE:
        this.escapeKey();
        break;

    // some useful shortcuts for fast access
    case KeyEvent.DOM_VK_B:
      Preferences.VisualEffects.BUMP_AND_NORMAL_MAPS = !Preferences.VisualEffects.BUMP_AND_NORMAL_MAPS;
      break;

    case KeyEvent.DOM_VK_N:
      castleScene.addLight();
      break;

    case KeyEvent.DOM_VK_S:
      Preferences.VisualEffects.POST_PROCESSING.SSAO = !Preferences.VisualEffects.POST_PROCESSING.SSAO;
      break;

    case KeyEvent.DOM_VK_F:
      Preferences.VisualEffects.POST_PROCESSING.FXAA = !Preferences.VisualEffects.POST_PROCESSING.FXAA;
      break;

    case KeyEvent.DOM_VK_I:
      Preferences.Picking.INFO_CUBES = !Preferences.Picking.INFO_CUBES;
      break;

    case KeyEvent.DOM_VK_G:
      Preferences.Control.STRAFING = !Preferences.Control.STRAFING;
      break;

    case KeyEvent.DOM_VK_L:
      Preferences.VisualEffects.POST_PROCESSING.LENS_FLARE = !Preferences.VisualEffects.POST_PROCESSING.LENS_FLARE;
      break;

    case KeyEvent.DOM_VK_C:
      Preferences.Collision.COLLISION_DETECTION = !Preferences.Collision.COLLISION_DETECTION;
      break;

    case KeyEvent.DOM_VK_MULTIPLY:
    case KeyEvent.DOM_VK_P:
      Preferences.Control.MOVING_SPEED_RELATIVE += 0.05;
      break;

    case KeyEvent.DOM_VK_O:
      Preferences.Control.MOVING_SPEED_RELATIVE -= 0.05;
			if (Preferences.Control.MOVING_SPEED_RELATIVE < 0.01) {
				Preferences.Control.MOVING_SPEED_RELATIVE = 0.01;
			}
      break;
  }

  if (appMain.mainLoopSuspended)
  {
    return;
  }

  if (this.keyboardSuspended)
  {
    if (Preferences.Application.isDevMode())
    {
      console.warn('Keyboard suspended.');
    }
    return;
  }

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

  switch (keyCode)
  {
    /////////////////////////////////////////////////////
    // [DEV] MAP, MINIMAP MANIPULATION
    /////////////////////////////////////////////////////

    case KeyEvent.DOM_VK_ADD:
      gui.minimap.zoomIn();
      gui.bigmap.zoomIn();
    break;

    case KeyEvent.DOM_VK_SUBTRACT:
      gui.minimap.zoomOut();
      gui.bigmap.zoomOut();
    break;


    /////////////////////////////////////////////////////
    // [DEV] VIEWPOINT SWITCHING
    /////////////////////////////////////////////////////

    // pressing 1 = switches to viewpoint 1 etc.
    case KeyEvent.DOM_VK_NUMPAD1:
    case KeyEvent.DOM_VK_NUMPAD2:
    case KeyEvent.DOM_VK_NUMPAD3:
    case KeyEvent.DOM_VK_NUMPAD4:
    case KeyEvent.DOM_VK_NUMPAD5:
    case KeyEvent.DOM_VK_NUMPAD6:
    case KeyEvent.DOM_VK_NUMPAD7:
    case KeyEvent.DOM_VK_NUMPAD8:
      Preferences.Viewpoint.getPublicViewpoints()[keyCode-KeyEvent.DOM_VK_NUMPAD1].setAsCurrentViewpoint();
      break;

    case KeyEvent.DOM_VK_1:
    case KeyEvent.DOM_VK_2:
    case KeyEvent.DOM_VK_3:
    case KeyEvent.DOM_VK_4:
    case KeyEvent.DOM_VK_5:
    case KeyEvent.DOM_VK_6:
    case KeyEvent.DOM_VK_7:
    case KeyEvent.DOM_VK_8:
      Preferences.Viewpoint.getPublicViewpoints()[keyCode-KeyEvent.DOM_VK_1].setAsCurrentViewpoint();
      break;
  }

  //////////////////////////////////////////////////////////////////////////////////////////////////////////
  // ACTIONS
  //////////////////////////////////////////////////////////////////////////////////////////////////////////

  // flag that we have pressed the key
  var action = this._keyboardToActionsMap[keyCode];
  this.actionsState[action] = true;

  //////////////////////////////////////////////////////////////////////////////////////////////////////////
  // KEY DOWN ACTIONS
  //////////////////////////////////////////////////////////////////////////////////////////////////////////

  if (this.actionsState.OPEN_HELP)
  {
    gui.helpDialog.openDialog();
  }

  if (this.actionsState.OPEN_MAP)
  {
    gui.mapDialog.openDialog();
  }

  if (this.actionsState.SWITCH_WALKFLY)
  {
    gui.switchWalkFly();
  }

  if (this.actionsState.CHOOSE_VIEWPOINT)
  {
    gui.viewpointDialog.openDialog();
  }

  if (this.actionsState.RESET_VIEWPOINT)
  {
    appMain.getCurrentEntity().camera.switchViewpoint (Preferences.Viewpoint.VIEWPOINT_DEFAULT);
  }

  if (this.actionsState.PAUSE_TOUR)
  {
    Tour.TogglePause();
  }
  //////////////////////////////////////////////////////////////////////////////////////////////////////////
  // EDGE WARNING
  //////////////////////////////////////////////////////////////////////////////////////////////////////////

  // key events in Edge messed up currently (or so it seems), so display a warning once
  if (Preferences.Browser.EDGE && !this.edgeWarningAlreadyDisplayed)
  {
	  this.edgeWarningAlreadyDisplayed = true;

	  $.notify(ContentLibrary.getContentString('EDGE_BROWSER_CONTROLS'), {
		 autoHideDelay: 11000
	});
  }
};

/**
 * Checks key states and perform actions. Used for continuous actions like walking (where key for moving forward is pressed down while walking).
 * Allows us to have more keys pressed at once.
 */
Control.prototype.keyboardContinuous = function()
{
    var entity = appMain.getCurrentControllingEntity();
    var rotateCamera = appMain.getCurrentEntity().camera;

    // actions list
    var actions = [
        {move: 1, action: this.actionsState.MOVE_FORWARD, camera: Camera.MOVE_FORWARD},
        {move: 1, action: this.actionsState.MOVE_BACK, camera: Camera.MOVE_BACK},
        {move: 1, action: this.actionsState.STRAFE_LEFT, camera: Camera.MOVE_LEFT},
        {move: 1, action: this.actionsState.STRAFE_RIGHT, camera: Camera.MOVE_RIGHT},
        {move: 1, action: this.actionsState.FLY_UP, camera: Camera.MOVE_UP},
        {move: 1, action: this.actionsState.FLY_DOWN, camera: Camera.MOVE_DOWN},

        {move: 0, action: this.actionsState.LOOK_LEFT, camera: Camera.MOVE_LEFT},
        {move: 0, action: this.actionsState.LOOK_RIGHT, camera: Camera.MOVE_RIGHT},
        {move: 0, action: this.actionsState.LOOK_UP, camera: Camera.MOVE_FORWARD},
        {move: 0, action: this.actionsState.LOOK_DOWN, camera: Camera.MOVE_BACK},

    ];

    // go through possible actions
    for(var i = 0; i < actions.length; i ++) {
        if(actions[i].action) {
            if(actions[i].move === 0) {
                rotateCamera.moveAngle (actions[i].camera);
            }
            collision.move (entity.camera, entity, entity.camera.moveCamera, actions[i].camera);
        }
    }

    if (this.actionsState.JUMP)
    {
        entity.jump();
    }
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MOUSE COORDINATES COMPUTATION
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * Computes mouse coordinates in the browser (image) coordinate system, where [0,0] is at the top-left corner
 * of the CANVAS element.
 */
Control.prototype.computeCanvas2dMouseCoordinates = function()
{
  if (!this._mouseCanvas2dComputed)
  {
    this.mouseCanvas2d.x = this.mouse.x - canvas.getBoundingClientRect().left;
    this.mouseCanvas2d.y = this.mouse.y - canvas.getBoundingClientRect().top;

    this._mouseCanvas2dComputed = true;
  }
};

/**
 * Computes mouse coordinates in the Canvas (i.e. OpenGL) coordinate system, where [0,0] is at the bottom-left corner
 * of the CANVAS element. Coordinates are then scaled by ratio between application WebGL window (the 'true' resolution)
 * and CSS size of the CANVAS element (the 'fake' resolution, possibly smaller, but more often bigger; imagine turning
 * small youtube video to fullscreen), so they remain in the bounds of WebGL window resolution.
 * This is important, because application picking logic depends on those coordinates.
 */
Control.prototype.computeCanvasMouseCoordinates = function()
{
  this.mouseCanvas.x = Math.round ( (this.mouse.x - canvas.getBoundingClientRect().left)   * gui.getWindowOriginalToRenderRatio().x );
  this.mouseCanvas.y = Math.round ( (canvas.getBoundingClientRect().bottom - this.mouse.y) * gui.getWindowOriginalToRenderRatio().y );
};

/**
 * Reset computation flag for mouse canvas 2d position.
 */
Control.prototype.resetComputationFlags = function()
{
  this._mouseCanvas2dComputed = false;
};


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MOUSE COORDINATES COMPUTATION
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * Retrieves mouse 2D canvas coordinates in the image coordinate system.
 *
 * @returns {THREE.Vector2}
 */
Control.prototype.getCanvas2dMouseCoordinates = function()
{
  this.computeCanvas2dMouseCoordinates();

  return this.mouseCanvas2d;
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// SUSPENSION OF THE CONTROLS
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * Resets all flags monitoring pressed state of keys.
 */
Control.prototype.clearPressedKeys = function()
{
  var actions = this._keyboardToActionsMap.__enumNames;

  for (var i = 0; i < actions.length; i++)
  {
    this.actionsState [ actions[i] ] = false;
  }
};

/**
 * Makes keyboard controls unresponsive.
 */
Control.prototype.suspendKeyboard = function()
{
  this.keyboardSuspended = true;

  this.clearPressedKeys();
};

/**
 * Makes keyboard responsive again.
 */
Control.prototype.activateKeyboard = function()
{
  this.keyboardSuspended = false;
};

/**
 * Makes mouse buttons and wheel unresponsive.
 */
Control.prototype.suspendMouse = function()
{
  this.mouseSuspended = true;

  this.mouseLeftDown = false;
  this.mouseRightDown = false;
};

/**
 * Makes mouse buttons and wheel responsive again.
 */
Control.prototype.activateMouse = function()
{
  this.mouseSuspended = false;
};
