/*******************************************************************************
 * EMSCRIPTEN GLFW 2.7.7 emulation.
 * It tries to emulate the behavior described in
 * http://www.glfw.org/GLFWReference277.pdf
 *
 * What it does:
 * - Creates a GL context.
 * - Manage keyboard and mouse events.
 * - GL Extensions support.
 *
 * What it does not but should probably do:
 * - Transmit events when glfwPollEvents, glfwWaitEvents or glfwSwapBuffers is
 *    called. Events callbacks are called as soon as event are received.
 * - Thread emulation.
 * - Joystick support.
 * - Image/Texture I/O support (that is deleted in GLFW 3).
 * - Video modes detection.
 *
 * Authors:
 * - Éloi Rivard <eloi.rivard@gmail.com>
 * - Thomas Borsos <thomasborsos@gmail.com>
 *
 ******************************************************************************/

var LibraryGLFW = {
  $GLFW: {

    keyFunc: null,
    charFunc: null,
    mouseButtonFunc: null,
    mousePosFunc: null,
    mouseWheelFunc: null,
    resizeFunc: null,
    closeFunc: null,
    refreshFunc: null,
    params: null,
    initTime: null,
    wheelPos: 0,
    buttons: 0,
    keys: 0,
    initWindowWidth: 640,
    initWindowHeight: 480,
    windowX: 0,
    windowY: 0,
    windowWidth: 0,
    windowHeight: 0,

/*******************************************************************************
 * DOM EVENT CALLBACKS
 ******************************************************************************/

    DOMToGLFWKeyCode: function(keycode) {
      switch (keycode) {
        case 0x08: return 295 ; // DOM_VK_BACKSPACE -> GLFW_KEY_BACKSPACE
        case 0x09: return 293 ; // DOM_VK_TAB -> GLFW_KEY_TAB
        case 0x0D: return 294 ; // DOM_VK_ENTER -> GLFW_KEY_ENTER
        case 0x1B: return 257 ; // DOM_VK_ESCAPE -> GLFW_KEY_ESC
        case 0x6A: return 313 ; // DOM_VK_MULTIPLY -> GLFW_KEY_KP_MULTIPLY
        case 0x6B: return 315 ; // DOM_VK_ADD -> GLFW_KEY_KP_ADD
        case 0x6D: return 314 ; // DOM_VK_SUBTRACT -> GLFW_KEY_KP_SUBTRACT
        case 0x6E: return 316 ; // DOM_VK_DECIMAL -> GLFW_KEY_KP_DECIMAL
        case 0x6F: return 312 ; // DOM_VK_DIVIDE -> GLFW_KEY_KP_DIVIDE
        case 0x70: return 258 ; // DOM_VK_F1 -> GLFW_KEY_F1
        case 0x71: return 259 ; // DOM_VK_F2 -> GLFW_KEY_F2
        case 0x72: return 260 ; // DOM_VK_F3 -> GLFW_KEY_F3
        case 0x73: return 261 ; // DOM_VK_F4 -> GLFW_KEY_F4
        case 0x74: return 262 ; // DOM_VK_F5 -> GLFW_KEY_F5
        case 0x75: return 263 ; // DOM_VK_F6 -> GLFW_KEY_F6
        case 0x76: return 264 ; // DOM_VK_F7 -> GLFW_KEY_F7
        case 0x77: return 265 ; // DOM_VK_F8 -> GLFW_KEY_F8
        case 0x78: return 266 ; // DOM_VK_F9 -> GLFW_KEY_F9
        case 0x79: return 267 ; // DOM_VK_F10 -> GLFW_KEY_F10
        case 0x7a: return 268 ; // DOM_VK_F11 -> GLFW_KEY_F11
        case 0x7b: return 269 ; // DOM_VK_F12 -> GLFW_KEY_F12
        case 0x25: return 285 ; // DOM_VK_LEFT -> GLFW_KEY_LEFT
        case 0x26: return 283 ; // DOM_VK_UP -> GLFW_KEY_UP
        case 0x27: return 286 ; // DOM_VK_RIGHT -> GLFW_KEY_RIGHT
        case 0x28: return 284 ; // DOM_VK_DOWN -> GLFW_KEY_DOWN
        case 0x21: return 298 ; // DOM_VK_PAGE_UP -> GLFW_KEY_PAGEUP
        case 0x22: return 299 ; // DOM_VK_PAGE_DOWN -> GLFW_KEY_PAGEDOWN
        case 0x24: return 300 ; // DOM_VK_HOME -> GLFW_KEY_HOME
        case 0x23: return 301 ; // DOM_VK_END -> GLFW_KEY_END
        case 0x2d: return 296 ; // DOM_VK_INSERT -> GLFW_KEY_INSERT
        case 16  : return 287 ; // DOM_VK_SHIFT -> GLFW_KEY_LSHIFT
        case 0x05: return 287 ; // DOM_VK_LEFT_SHIFT -> GLFW_KEY_LSHIFT
        case 0x06: return 288 ; // DOM_VK_RIGHT_SHIFT -> GLFW_KEY_RSHIFT
        case 17  : return 289 ; // DOM_VK_CONTROL -> GLFW_KEY_LCTRL
        case 0x03: return 289 ; // DOM_VK_LEFT_CONTROL -> GLFW_KEY_LCTRL
        case 0x04: return 290 ; // DOM_VK_RIGHT_CONTROL -> GLFW_KEY_RCTRL
        case 18  : return 291 ; // DOM_VK_ALT -> GLFW_KEY_LALT
        case 0x02: return 291 ; // DOM_VK_LEFT_ALT -> GLFW_KEY_LALT
        case 0x01: return 292 ; // DOM_VK_RIGHT_ALT -> GLFW_KEY_RALT
        case 96  : return 302 ; // GLFW_KEY_KP_0
        case 97  : return 303 ; // GLFW_KEY_KP_1
        case 98  : return 304 ; // GLFW_KEY_KP_2
        case 99  : return 305 ; // GLFW_KEY_KP_3
        case 100 : return 306 ; // GLFW_KEY_KP_4
        case 101 : return 307 ; // GLFW_KEY_KP_5
        case 102 : return 308 ; // GLFW_KEY_KP_6
        case 103 : return 309 ; // GLFW_KEY_KP_7
        case 104 : return 310 ; // GLFW_KEY_KP_8
        case 105 : return 311 ; // GLFW_KEY_KP_9
        default  : return keycode;
      };
    },

    // UCS-2 to UTF16 (ISO 10646)
    getUnicodeChar: function(value) {
      var output = '';
      if (value > 0xFFFF) {
        value -= 0x10000;
        output += String.fromCharCode(value >>> 10 & 0x3FF | 0xD800);
        value = 0xDC00 | value & 0x3FF;
      }
      output += String.fromCharCode(value);
      return output;
    },

    onKeyPress: function(event) {
      // charCode is only available whith onKeyPress event
      var char = GLFW.getUnicodeChar(event.charCode);

      if (event.charCode) {
        var char = GLFW.getUnicodeChar(event.charCode);
        if (char !== null && GLFW.charFunc) {
          Runtime.dynCall('vii', GLFW.charFunc, [event.charCode, 1]);
        }
      }
    },

    onKeyChanged: function(event, status) {
      var key = GLFW.DOMToGLFWKeyCode(event.keyCode);
      if (key) {
        GLFW.keys[key] = status;
        if (GLFW.keyFunc) {
          Runtime.dynCall('vii', GLFW.keyFunc, [key, status]);
        }
      }
    },

    onKeydown: function(event) {
      GLFW.onKeyChanged(event, 1);// GLFW_PRESS
      // This logic comes directly from the sdl implementation. We cannot
      // call preventDefault on all keydown events otherwise onKeyPress will
      // not get called
      if (event.keyCode === 8 /* backspace */ || event.keyCode === 9 /* tab */) {
        event.preventDefault();
      }
    },

    onKeyup: function(event) {
      GLFW.onKeyChanged(event, 0);// GLFW_RELEASE
    },

    onMousemove: function(event) {
      /* Send motion event only if the motion changed, prevents
       * spamming our app with uncessary callback call. It does happen in
       * Chrome on Windows.
       */
      var lastX = Browser.mouseX;
      var lastY = Browser.mouseY;
      Browser.calculateMouseEvent(event);
      var newX = Browser.mouseX;
      var newY = Browser.mouseY;

      if (event.target == Module["canvas"] && GLFW.mousePosFunc) {
        event.preventDefault();
        Runtime.dynCall('vii', GLFW.mousePosFunc, [lastX, lastY]);
      }
    },

    onMouseButtonChanged: function(event, status) {
      if (GLFW.mouseButtonFunc == null) {
        return;
      }

      Browser.calculateMouseEvent(event);

      if (event.target != Module["canvas"]) {
        return;
      }

      if (status == 1) {// GLFW_PRESS
        try {
          event.target.setCapture();
        } catch (e) {}
      }

      event.preventDefault();

      // DOM and glfw have different button codes
      var eventButton = event['button'];
      if (eventButton > 0) {
        if (eventButton == 1) {
          eventButton = 2;
        } else {
          eventButton = 1;
        }
      }
      Runtime.dynCall('vii', GLFW.mouseButtonFunc, [eventButton, status]);
    },

    onMouseButtonDown: function(event) {
      GLFW.buttons |= (1 << event['button']);
      GLFW.onMouseButtonChanged(event, 1);// GLFW_PRESS
    },

    onMouseButtonUp: function(event) {
      GLFW.buttons &= ~(1 << event['button']);
      GLFW.onMouseButtonChanged(event, 0);// GLFW_RELEASE
    },

    onMouseWheel: function(event) {
      GLFW.wheelPos -= Browser.getMouseWheelDelta(event);

      if (GLFW.mouseWheelFunc && event.target == Module["canvas"]) {
        Runtime.dynCall('vi', GLFW.mouseWheelFunc, [GLFW.wheelPos]);
        event.preventDefault();
      }
    },

    // TODO add fullscreen API ala:
    // http://johndyer.name/native-fullscreen-javascript-api-plus-jquery-plugin/
    onFullScreenEventChange: function(event) {
      var width;
      var height;
      if (document["fullScreen"] || document["mozFullScreen"] || document["webkitIsFullScreen"]) {
        width = screen["width"];
        height = screen["height"];
      } else {
        width = GLFW.windowWidth;
        height = GLFW.windowHeight;
        // TODO set position
        document.removeEventListener('fullscreenchange', GLFW.onFullScreenEventChange, true);
        document.removeEventListener('mozfullscreenchange', GLFW.onFullScreenEventChange, true);
        document.removeEventListener('webkitfullscreenchange', GLFW.onFullScreenEventChange, true);
      }
      Browser.setCanvasSize(width, height);

      if (GLFW.resizeFunc) {
        Runtime.dynCall('vii', GLFW.resizeFunc, [width, height]);
      }
    },

    requestFullScreen: function() {
      var RFS = Module["canvas"]['requestFullscreen'] ||
                Module["canvas"]['requestFullScreen'] ||
                Module["canvas"]['mozRequestFullScreen'] ||
                Module["canvas"]['webkitRequestFullScreen'] ||
                (function() {});
      RFS.apply(Module["canvas"], []);
    },

    cancelFullScreen: function() {
      var CFS = document['exitFullscreen'] ||
                document['cancelFullScreen'] ||
                document['mozCancelFullScreen'] ||
                document['webkitCancelFullScreen'] ||
          (function() {});
      CFS.apply(document, []);
    }
  },

/*******************************************************************************
 * GLFW FUNCTIONS
 ******************************************************************************/

  /* GLFW initialization, termination and version querying */
  glfwInit: function() {
    GLFW.initTime = Date.now() / 1000;

    window.addEventListener("keydown", GLFW.onKeydown, true);
    window.addEventListener("keypress", GLFW.onKeyPress, true);
    window.addEventListener("keyup", GLFW.onKeyup, true);
    window.addEventListener("mousemove", GLFW.onMousemove, true);
    window.addEventListener("mousedown", GLFW.onMouseButtonDown, true);
    window.addEventListener("mouseup", GLFW.onMouseButtonUp, true);
    window.addEventListener('DOMMouseScroll', GLFW.onMouseWheel, true);
    window.addEventListener('mousewheel', GLFW.onMouseWheel, true);

    __ATEXIT__.push({ func: function() {
      window.removeEventListener("keydown", GLFW.onKeydown, true);
      window.removeEventListener("keypress", GLFW.onKeyPress, true);
      window.removeEventListener("keyup", GLFW.onKeyup, true);
      window.removeEventListener("mousemove", GLFW.onMousemove, true);
      window.removeEventListener("mousedown", GLFW.onMouseButtonDown, true);
      window.removeEventListener("mouseup", GLFW.onMouseButtonUp, true);
      window.removeEventListener('DOMMouseScroll', GLFW.onMouseWheel, true);
      window.removeEventListener('mousewheel', GLFW.onMouseWheel, true);
      Module["canvas"].width = Module["canvas"].height = 1;
    }});

    //TODO: Init with correct values
    GLFW.params = new Array();
    GLFW.params[0x00030001] = true; // GLFW_MOUSE_CURSOR
    GLFW.params[0x00030002] = false; // GLFW_STICKY_KEYS
    GLFW.params[0x00030003] = true; // GLFW_STICKY_MOUSE_BUTTONS
    GLFW.params[0x00030004] = false; // GLFW_SYSTEM_KEYS
    GLFW.params[0x00030005] = false; // GLFW_KEY_REPEAT
    GLFW.params[0x00030006] = true; // GLFW_AUTO_POLL_EVENTS
    GLFW.params[0x00020001] = true; // GLFW_OPENED
    GLFW.params[0x00020002] = true; // GLFW_ACTIVE
    GLFW.params[0x00020003] = false; // GLFW_ICONIFIED
    GLFW.params[0x00020004] = true; // GLFW_ACCELERATED
    GLFW.params[0x00020005] = 0; // GLFW_RED_BITS
    GLFW.params[0x00020006] = 0; // GLFW_GREEN_BITS
    GLFW.params[0x00020007] = 0; // GLFW_BLUE_BITS
    GLFW.params[0x00020008] = 0; // GLFW_ALPHA_BITS
    GLFW.params[0x00020009] = 0; // GLFW_DEPTH_BITS
    GLFW.params[0x0002000A] = 0; // GLFW_STENCIL_BITS
    GLFW.params[0x0002000B] = 0; // GLFW_REFRESH_RATE
    GLFW.params[0x0002000C] = 0; // GLFW_ACCUM_RED_BITS
    GLFW.params[0x0002000D] = 0; // GLFW_ACCUM_GREEN_BITS
    GLFW.params[0x0002000E] = 0; // GLFW_ACCUM_BLUE_BITS
    GLFW.params[0x0002000F] = 0; // GLFW_ACCUM_ALPHA_BITS
    GLFW.params[0x00020010] = 0; // GLFW_AUX_BUFFERS
    GLFW.params[0x00020011] = 0; // GLFW_STEREO
    GLFW.params[0x00020012] = 0; // GLFW_WINDOW_NO_RESIZE
    GLFW.params[0x00020013] = 0; // GLFW_FSAA_SAMPLES
    GLFW.params[0x00020014] = 0; // GLFW_OPENGL_VERSION_MAJOR
    GLFW.params[0x00020015] = 0; // GLFW_OPENGL_VERSION_MINOR
    GLFW.params[0x00020016] = 0; // GLFW_OPENGL_FORWARD_COMPAT
    GLFW.params[0x00020017] = 0; // GLFW_OPENGL_DEBUG_CONTEXT
    GLFW.params[0x00020018] = 0; // GLFW_OPENGL_PROFILE

    GLFW.keys = new Array();

    return 1; // GL_TRUE
  },

  glfwTerminate: function() {},

  glfwGetVersion: function(major, minor, rev) {
    setValue(major, 2, 'i32');
    setValue(minor, 7, 'i32');
    setValue(rev, 7, 'i32');
  },

  /* Window handling */
  glfwOpenWindow__deps: ['$Browser'],
  glfwOpenWindow: function(width, height, redbits, greenbits, bluebits, alphabits, depthbits, stencilbits, mode) {
    if (width == 0 && height > 0) {
      width = 4 * height / 3;
    }
    if (width > 0 && height == 0) {
      height = 3 * width / 4;
    }
    GLFW.params[0x00020005] = redbits; // GLFW_RED_BITS
    GLFW.params[0x00020006] = greenbits; // GLFW_GREEN_BITS
    GLFW.params[0x00020007] = bluebits; // GLFW_BLUE_BITS
    GLFW.params[0x00020008] = alphabits; // GLFW_ALPHA_BITS
    GLFW.params[0x00020009] = depthbits; // GLFW_DEPTH_BITS
    GLFW.params[0x0002000A] = stencilbits; // GLFW_STENCIL_BITS

    if (mode == 0x00010001) {// GLFW_WINDOW
      Browser.setCanvasSize(GLFW.initWindowWidth = width,
                            GLFW.initWindowHeight = height);
      GLFW.params[0x00030003] = true; // GLFW_STICKY_MOUSE_BUTTONS
    } else if (mode == 0x00010002) {// GLFW_FULLSCREEN
      GLFW.requestFullScreen();
      GLFW.params[0x00030003] = false; // GLFW_STICKY_MOUSE_BUTTONS
    } else {
      throw "Invalid glfwOpenWindow mode.";
    }

    var contextAttributes = {
      antialias: (GLFW.params[0x00020013] > 1), // GLFW_FSAA_SAMPLES
      depth: (GLFW.params[0x00020009] > 0), // GLFW_DEPTH_BITS
      stencil: (GLFW.params[0x0002000A] > 0) // GLFW_STENCIL_BITS
    }
    Module.ctx = Browser.createContext(Module['canvas'], true, true, contextAttributes);
    return 1; // GL_TRUE
  },

  glfwOpenWindowHint: function(target, hint) {
    GLFW.params[target] = hint;
  },

  glfwCloseWindow__deps: ['$Browser'],
  glfwCloseWindow: function() {
    if (GLFW.closeFunc) {
      Runtime.dynCall('v', GLFW.closeFunc, []);
    }
    Module.ctx = Browser.destroyContext(Module['canvas'], true, true);
  },

  glfwSetWindowTitle: function(title) {
    document.title = Pointer_stringify(title);
  },

  glfwGetWindowSize: function(width, height) {
    setValue(width, Module['canvas'].width, 'i32');
    setValue(height, Module['canvas'].height, 'i32');
  },

  glfwSetWindowSize: function(width, height) {
    GLFW.cancelFullScreen();
      Browser.setCanvasSize(width, height);
      if (GLFW.resizeFunc) {
        Runtime.dynCall('vii', GLFW.resizeFunc, [width, height]);
      }
  },

  glfwSetWindowPos: function(x, y) {},

  glfwIconifyWindow: function() {},

  glfwRestoreWindow: function() {},

  glfwSwapBuffers: function() {},

  glfwSwapInterval: function(interval) {},

  glfwGetWindowParam: function(param) {
    return GLFW.params[param];
  },

  glfwSetWindowSizeCallback: function(cbfun) {
    GLFW.resizeFunc = cbfun;
    if (GLFW.resizeFunc) {
      Runtime.dynCall('vii', GLFW.resizeFunc, [Module['canvas'].width, Module['canvas'].height]);
    }
  },

  glfwSetWindowCloseCallback: function(cbfun) {
    GLFW.closeFunc = cbfun;
  },

  glfwSetWindowRefreshCallback: function(cbfun) {
    GLFW.refreshFunc = cbfun;
  },

  /* Video mode functions */
  glfwGetVideoModes: function(list, maxcount) { throw "glfwGetVideoModes is not implemented."; },

  glfwGetDesktopMode: function(mode) { throw "glfwGetDesktopMode is not implemented."; },

  /* Input handling */
  glfwPollEvents: function() {},

  glfwWaitEvents: function() {},

  glfwGetKey: function(key) {
    return GLFW.keys[key];
  },

  glfwGetMouseButton: function(button) {
    return (GLFW.buttons & (1 << button)) > 0;
  },

  glfwGetMousePos: function(xpos, ypos) {
    setValue(xpos, Browser.mouseX, 'i32');
    setValue(ypos, Browser.mouseY, 'i32');
  },

  // I believe it is not possible to move the mouse with javascript
  glfwSetMousePos: function(xpos, ypos) {},

  glfwGetMouseWheel: function() {
    return GLFW.wheelPos;
  },

  glfwSetMouseWheel: function(pos) {
    GLFW.wheelPos = pos;
  },

  glfwSetKeyCallback: function(cbfun) {
    GLFW.keyFunc = cbfun;
  },

  glfwSetCharCallback: function(cbfun) {
    GLFW.charFunc = cbfun;
  },

  glfwSetMouseButtonCallback: function(cbfun) {
    GLFW.mouseButtonFunc = cbfun;
  },

  glfwSetMousePosCallback: function(cbfun) {
    GLFW.mousePosFunc = cbfun;
  },

  glfwSetMouseWheelCallback: function(cbfun) {
    GLFW.mouseWheelFunc = cbfun;
  },

  /* Joystick input */
  glfwGetJoystickParam: function(joy, param) { throw "glfwGetJoystickParam is not implemented."; },

  glfwGetJoystickPos: function(joy, pos, numaxes) { throw "glfwGetJoystickPos is not implemented."; },

  glfwGetJoystickButtons: function(joy, buttons, numbuttons) { throw "glfwGetJoystickButtons is not implemented."; },

  /* Time */
  glfwGetTime: function() {
    return (Date.now()/1000) - GLFW.initTime;
  },

  glfwSetTime: function(time) {
    GLFW.initTime = Date.now()/1000 + time;
  },

  glfwSleep__deps: ['sleep'],
  glfwSleep: function(time) {
    _sleep(time);
  },

  /* Extension support */
  glfwExtensionSupported: function(extension) {
    return Module.ctx.getSupportedExtensions().indexOf(Pointer_stringify(extension)) > -1;
  },

  glfwGetProcAddress__deps: ['emscripten_GetProcAddress'],
  glfwGetProcAddress: function(procname) {
    return _emscripten_GetProcAddress(procname);
  },

  glfwGetGLVersion: function(major, minor, rev) {
    setValue(major, 0, 'i32');
    setValue(minor, 0, 'i32');
    setValue(rev, 1, 'i32');
  },

  /* Threading support */
  glfwCreateThread: function(fun, arg) {
    var str = 'v';
    for (var i in arg) {
      str += 'i';
    }
    Runtime.dynCall(str, fun, arg);
    // One single thread
    return 0;
  },

  glfwDestroyThread: function(ID) {},

  glfwWaitThread: function(ID, waitmode) {},

  glfwGetThreadID: function() {
    // One single thread
    return 0;
  },

  glfwCreateMutex: function() { throw "glfwCreateMutex is not implemented."; },

  glfwDestroyMutex: function(mutex) { throw "glfwDestroyMutex is not implemented."; },

  glfwLockMutex: function(mutex) { throw "glfwLockMutex is not implemented."; },

  glfwUnlockMutex: function(mutex) { throw "glfwUnlockMutex is not implemented."; },

  glfwCreateCond: function() { throw "glfwCreateCond is not implemented."; },

  glfwDestroyCond: function(cond) { throw "glfwDestroyCond is not implemented."; },

  glfwWaitCond: function(cond, mutex, timeout) { throw "glfwWaitCond is not implemented."; },

  glfwSignalCond: function(cond) { throw "glfwSignalCond is not implemented."; },

  glfwBroadcastCond: function(cond) { throw "glfwBroadcastCond is not implemented."; },

  glfwGetNumberOfProcessors: function() {
    // Threads are disabled anyway…
    return 1;
  },

  /* Enable/disable functions */
  glfwEnable: function(token) {
    GLFW.params[token] = false;
  },

  glfwDisable: function(token) {
    GLFW.params[token] = true;
  },

  /* Image/texture I/O support */
  glfwReadImage: function(name, img, flags) { throw "glfwReadImage is not implemented."; },

  glfwReadMemoryImage: function(data, size, img, flags) { throw "glfwReadMemoryImage is not implemented."; },

  glfwFreeImage: function(img) { throw "glfwFreeImage is not implemented."; },

  glfwLoadTexture2D: function(name, flags) { throw "glfwLoadTexture2D is not implemented."; },

  glfwLoadMemoryTexture2D: function(data, size, flags) { throw "glfwLoadMemoryTexture2D is not implemented."; },

  glfwLoadTextureImage2D: function(img, flags) { throw "glfwLoadTextureImage2D is not implemented."; },
};

autoAddDeps(LibraryGLFW, '$GLFW');
mergeInto(LibraryManager.library, LibraryGLFW);


