function Calculator(emModule) {
  /* emModule is an optional parameter.
   * In addition to the ones supported by Emscripten, here are the values that
   * this object can have:
   *  - element: a DOM element containing a copy of 'calculator.html' as
   *    generated by 'layout.py'
   *  - mirrorCanvas: a DOM element where the main canvas will be mirrored
   */

  // Configure emModule
  var emModule = (typeof emModule === 'undefined') ? {} : emModule;
  var calculatorElement = emModule.element || document.querySelector('.calculator');
  var mainCanvas = calculatorElement.querySelector("canvas");
  if (typeof emModule.mirrorCanvas === 'undefined') {
    /* If emModule.mirrorCanvas is defined as null, don't do anything */
    emModule.mirrorCanvas = document.querySelector('.calculator-mirror canvas');
  }
  var mirrorCanvasContext = emModule.mirrorCanvas ? emModule.mirrorCanvas.getContext('2d') : null;
  var defaultModule = {
    canvas: mainCanvas,
    arguments: [
      '--language',
      document.documentElement.lang || window.navigator.language.split('-')[0]
    ],
    onEpsilonIdle: function() {
      calculatorElement.classList.remove('loading');
    },
    downloadScreenshot: function() {
      // toDataURL needs the canvas to be refreshed
      this._IonDisplayForceRefresh();
      var link = document.createElement('a');
      link.download = 'screenshot.png';
      link.href = mainCanvas.toDataURL('image/png').replace('image/png', 'image/octet-stream');
      link.click();
    }
  };
  if (mirrorCanvasContext) {
    defaultModule.onDisplayRefresh = function() {
      mirrorCanvasContext.drawImage(mainCanvas, 0, 0);
    }
  }
  for (var attrname in defaultModule) {
    if (!emModule.hasOwnProperty(attrname)) {
      emModule[attrname] = defaultModule[attrname];
    }
  }

  // Load and run Epsilon
  Epsilon(emModule);

  /* Install event handlers
   * This needs to be done after loading Epsilon, otherwise the _IonSimulator*
   * functions haven't been defined just yet. */
  function eventHandler(keyHandler) {
    return function(ev) {
      var key = this.getAttribute('data-key');
      keyHandler(key);
      /* Always prevent default action of event.
       * First, this will prevent the browser from delaying that event. Indeed
       * the browser would otherwise try to see if that event could have any
       * other meaning (e.g. a click) and might delay it as a result.
       * Second, this prevents touch events to be handled twice. Indeed, for
       * backward compatibility reasons, mobile browsers usually create a fake
       * mouse event after each real touch event. This allows desktop websites
       * to work unmodified on mobile devices. But here we are explicitly
       * handling both touch and mouse events. We therefore need to disable
       * the default action of touch events, otherwise the handler would get
       * called twice. */
      ev.preventDefault();
    };
  }
  var downHandler = eventHandler(emModule._IonSimulatorKeyboardKeyDown);
  var upHandler = eventHandler(emModule._IonSimulatorKeyboardKeyUp);

  calculatorElement.querySelectorAll('span').forEach(function(span){
    /* We decide to hook both to touch and mouse events
     * On most mobile browsers, mouse events are generated if addition to touch
     * events, so this could seem pointless. But those mouse events are not
     * generated in real time: instead, they are buffered and eventually fired
     * in a very rapid sequence. This prevents Epsilon from generating an event
     * since this quick sequence will trigger the debouncer. */
    ['touchstart', 'mousedown'].forEach(function(type){
      span.addEventListener(type, downHandler);
    });
    ['touchend', 'mouseup'].forEach(function(type){
      span.addEventListener(type, upHandler);
    });
  });
}

if (typeof exports === 'object' && typeof module === 'object') {
  module.exports = Calculator;
}
