// Copyright (c) 2013 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// TODO(ncbray): re-enable once Emscripten stops including code with octal values.
//"use strict";

var clamp = function(value, min, max) {
  if (value < min) {
    return min;
  } else if (value > max) {
    return max;
  } else {
    return value;
  }
}

// Polyfill for Safari.
if (window.performance === undefined) {
  window.performance = {};
}
if (window.performance.now === undefined) {
  var nowStart = Date.now();
  window.performance.now = function() {
    return Date.now() - nowStart;
  }
}

// Polyfill for IE 10.
if (ArrayBuffer.prototype.slice === undefined) {
  // From https://developer.mozilla.org/en-US/docs/Web/API/ArrayBuffer:
  // Returns a new ArrayBuffer whose contents are a copy of this ArrayBuffer's
  // bytes from begin, inclusive, up to end, exclusive. If either begin or end
  // is negative, it refers to an index from the end of the array, as opposed
  // to from the beginning.
  ArrayBuffer.prototype.slice = function(begin, end) {
    if (begin < 0) {
      begin = this.byteLength + begin;
    }
    begin = clamp(begin, 0, this.byteLength);

    if (end === undefined) {
      end = this.byteLength;
    } else if (end < 0) {
      end = this.byteLength + end;
    }
    end = clamp(end, 0, this.byteLength);

    var length = end - begin;
    if (length < 0) {
      length = 0;
    }

    var src = new Int8Array(this, begin, length);
    var dst = new Int8Array(length);
    dst.set(src);
    return dst.buffer;
  };
}

var getFullscreenElement = function() {
  return document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || null;
}

// Encoding types as numbers instead of string saves ~2kB when minified because closure will inline these constants.
var STRING_RESOURCE = 0;
var ARRAY_BUFFER_RESOURCE = 1;

var INPUT_EVENT_RESOURCE = 2;

var FILE_SYSTEM_RESOURCE = 3;
var FILE_REF_RESOURCE = 4;
var FILE_IO_RESOURCE = 5;

var URL_LOADER_RESOURCE = 6;
var URL_REQUEST_INFO_RESOURCE = 7;
var URL_RESPONSE_INFO_RESOURCE = 8;

var AUDIO_CONFIG_RESOURCE = 9;
var AUDIO_RESOURCE = 10;

var GRAPHICS_2D_RESOURCE = 11;
var IMAGE_DATA_RESOURCE = 12;

var PROGRAM_RESOURCE = 13;
var SHADER_RESOURCE = 14;
var BUFFER_RESOURCE = 15;
var TEXTURE_RESOURCE = 16;
var UNIFORM_LOCATION_RESOURCE = 17;

var INSTANCE_RESOURCE = 18;
var VIEW_RESOURCE = 19;
var GRAPHICS_3D_RESOURCE = 20;

var ARRAY_RESOURCE = 21;
var DICTIONARY_RESOURCE = 22;
var WEB_SOCKET_RESOURCE = 23;

var ResourceManager = function() {
  this.lut = {};
  this.uid = 1;
  this.num_resources = 0;
}

ResourceManager.prototype.checkType = function(type) {
  if (typeof type !== "number") {
    throw "resource type must be a number";
  }
}

ResourceManager.prototype.register = function(type, res) {
  this.checkType(type);
  while (this.uid in this.lut || this.uid === 0) {
    this.uid = (this.uid + 1) & 0xffffffff;
  }
  res.type = type;
  res.uid = this.uid;
  res.refcount = 1;
  this.lut[res.uid] = res;
  this.num_resources += 1;
  this.dead = false;
  return this.uid;
}

ResourceManager.prototype.registerString = function(value, memory, len) {
  return this.register(STRING_RESOURCE, {
      value: value,
      memory: memory,
      len: len,
      destroy: function() {
        _free(this.memory);
      }
  });
}

ResourceManager.prototype.registerArray = function(value) {
  return this.register(ARRAY_RESOURCE, {
      value: value,
      setLength: function(length) {
        while(this.value.length > length) {
          glue.structRelease(this.value.pop());
        }
        while(this.value.length < length) {
          this.value.push({type: ppapi.PP_VARTYPE_UNDEFINED, value: 0});
        }
      },
      destroy: function() {
        var wrapped = this.value;
        this.value = [];
        for (var i = 0; i < wrapped.length; i++) {
          glue.structRelease(wrapped[i]);
        }
      }
  });
}

ResourceManager.prototype.registerDictionary = function(value) {
  return this.register(DICTIONARY_RESOURCE, {
      value: value,
      remove: function(key) {
        if (key in this.value) {
          var e = this.value[key];
          delete this.value[key];
          glue.structRelease(e);
        }
      },
      destroy: function() {
        var wrapped = this.value;
        this.value = {};
        for (var key in wrapped) {
          glue.structRelease(wrapped[key]);
        }
      }
  });
}

ResourceManager.prototype.registerArrayBuffer = function(memory, len) {
  return this.register(ARRAY_BUFFER_RESOURCE, {
      memory: memory,
      len: len,
      destroy: function() {
        _free(this.memory);
      }
  });
}

var uidInfo = function(uid, type) {
  return "(" + uid + " as " + type + ")";
}

ResourceManager.prototype.resolve = function(uid, type, speculative) {
  if (typeof uid !== "number") {
    throw "resources.resolve uid must be an int";
  }
  this.checkType(type);
  if (uid === 0) {
    if (!speculative) {
      console.error("Attempted to resolve an invalid resource ID " + uidInfo(uid, type));
    }
    return undefined;
  }
  var res = this.lut[uid];
  if (res === undefined) {
    console.error("Attempted to resolve a non-existant resource ID " + uidInfo(uid, type));
    return undefined;
  }
  if (res.type !== type) {
    if (!speculative) {
      console.error("Expected resource " + uidInfo(uid, type) + ", but it was " + uidInfo(uid, res.type));
    }
    return undefined;
  }
  return res;
}

ResourceManager.prototype.is = function(uid, type) {
  return this.resolve(uid, type, true) !== undefined;
}

ResourceManager.prototype.addRef = function(uid) {
  var res = this.lut[uid];
  if (res === undefined) {
    throw "Resource does not exist: " + uid;
  }
  res.refcount += 1;
}

ResourceManager.prototype.release = function(uid) {
  var res = this.lut[uid];
  if (res === undefined) {
    throw "Resource does not exist: " + uid;
  }

  res.refcount -= 1;
  if (res.refcount <= 0) {
    if (res.destroy) {
      res.destroy();
    }

    res.dead = true;
    delete this.lut[res.uid];
    this.num_resources -= 1;
  }
}

ResourceManager.prototype.getNumResources = function() {
  return this.num_resources;
}

ResourceManager.prototype.getResourceTypeHistogram = function() {
  var types = {};
  for (var uid in this.lut) {
    var t = this.lut[uid].type;
      types[t] = (types[t] || 0) + 1;
  }
  return types;
}

var resources = new ResourceManager();
var interfaces = {};
var declaredInterfaces = [];

var registerInterface = function(name, functions, supported) {
  // Defer creating the interface until Emscripten's runtime is available.
  declaredInterfaces.push({name: name, functions: functions, supported: supported});
};

var createInterface = function(name, functions) {
  var trace = function(f, i) {
    return function() {
      console.log(">>>", name, i, arguments);
      var result = f.apply(f, arguments);
      console.log("<<<", name, i, result);
      return result;
    }
  };

  var getFuncPtr = function(f) {
    // Memoize - a single function may appear in multiple versions of an interface.
    if (f.func_ptr === undefined) {
      f.func_ptr = Runtime.addFunction(f, 1);
    }
    return f.func_ptr;
  };

  // allocate(...) is bugged for non-i8 allocations, so do it manually
  // TODO(ncbray): static alloc?
  var ptr = allocate(functions.length * 4, 'i8', ALLOC_NORMAL);
  for (var i in functions) {
    var f = functions[i];
    if (false) {
      f = trace(f, i);
    }
    setValue(ptr + i * 4, getFuncPtr(f), 'i32');
  }
  interfaces[name] = ptr;
};

var Module = {
  "noInitialRun": true,
  "noExitRuntime": true,
  "onRuntimeInitialized": function() {
    for (var i = 0; i < declaredInterfaces.length; i++) {
      var inf = declaredInterfaces[i];
      if (inf.supported === undefined || inf.supported()) {
        createInterface(inf.name, inf.functions);
      } else {
        interfaces[inf.name] = 0;
      }
    }
    declaredInterfaces = [];
  }
};

var CreateInstance = function(width, height, shadow_instance) {
  if (shadow_instance === undefined) {
    shadow_instance = document.createElement("span");
    shadow_instance.setAttribute("name", "nacl_module");
    shadow_instance.setAttribute("id", "nacl_module");
  }

  shadow_instance.setAttribute("width", width);
  shadow_instance.setAttribute("height", height);
  shadow_instance.className = "ppapiJsEmbed";

  shadow_instance.style.display = "inline-block";
  shadow_instance.style.width = width + "px";
  shadow_instance.style.height = height + "px";
  shadow_instance.style.overflow = "hidden";

  // Called from external code.
  shadow_instance["postMessage"] = function(message) {
    var instance = this.instance;
    // Fill out the PP_Var structure
    var var_ptr = _malloc(16);
    glue.jsToMemoryVar(message, var_ptr);

    // Post messages are resolved asynchronously.
    glue.defer(function() {
      _DoPostMessage(instance, var_ptr);
      // Note: the callee releases the var so we don't need to.
      // This is different than most interfaces.
      _free(var_ptr);
    });
  };

  // Not compatible with CSP.
  var style = document.createElement("style");
  style.type = "text/css";
  style.innerHTML = ".ppapiJsEmbed {border: 0px; margin: 0px; padding: 0px; outline: none;}";
  style.innerHTML += " .ppapiJsCanvas {outline: none; image-rendering: optimizeSpeed; image-rendering: -moz-crisp-edges; image-rendering: -o-crisp-edges; image-rendering: -webkit-optimize-contrast; image-rendering: optimize-contrast; -ms-interpolation-mode: nearest-neighbor;}";
  // Bug-ish.  Each variation needs to be specified seperately.
  // TODO(ncbray): set width and height to screen.width and screen.height?  This would better match PPAPI's behavior, but it could also cause unanticipated problems.
  var fullscreenCSS = "{position: fixed; top: 0; left: 0; bottom: 0; right: 0; width: 100% !important; height: 100% !important; box-sizing: border-box; object-fit: contain; background-color: black;}";
  style.innerHTML += " .ppapiJsEmbed:-webkit-full-screen " + fullscreenCSS;
  style.innerHTML += " .ppapiJsEmbed:-moz-full-screen " + fullscreenCSS;
  style.innerHTML += " .ppapiJsEmbed:-ms-fullscreen " + fullscreenCSS;
  style.innerHTML += " .ppapiJsEmbed:full-screen " + fullscreenCSS;

  document.getElementsByTagName("head")[0].appendChild(style);

  var last_update = "";
  var updateView = function() {
    // NOTE: this will give the wrong value if the canvas has any margin,
    // border, or padding.  Some browsers may also give non-integer values and
    // rounding is prefered to truncation.
    var bounds = shadow_instance.getBoundingClientRect();
    var rect = {
      point: {
        x: Math.round(bounds.left),
        y: Math.round(bounds.top)
      },
      size: {
        width: Math.round(bounds.right - bounds.left),
        height: Math.round(bounds.bottom - bounds.top)
      }
    };

    // Clip the bounds to the viewport.
    var clipX = clamp(bounds.left, 0, window.innerWidth);
    var clipY = clamp(bounds.top, 0, window.innerHeight);
    var clipWidth = clamp(bounds.right, 0, window.innerWidth) - clipX;
    var clipHeight = clamp(bounds.bottom, 0, window.innerHeight) - clipY;

    // Translate into the coordinate space of the canvas.
    clipX -= bounds.left;
    clipY -= bounds.top;

    // Handle a zero-sized clip region.
    var visible = clipWidth > 0 && clipHeight > 0;
    if (!visible) {
      // clipX and clipY may be outside the canvas if width or height are zero.
      // The PPAPI spec requires we return (0, 0, 0, 0)
      clipX = 0;
      clipY = 0;
      clipWidth = 0;
      clipHeight = 0;
    }
    var event = {
      rect: rect,
      fullscreen: getFullscreenElement() === shadow_instance,
      visible: visible,
      page_visible: 1,
      clip_rect: {
        point: {
          x: clipX,
          y: clipY
        },
        size: {
          width: clipWidth,
          height: clipHeight
        }
      }
    };
    var s = JSON.stringify(event);
    if (s !== last_update) {
      last_update = s;
      var view = resources.register(VIEW_RESOURCE, event);
      _DoChangeView(instance, view);
      resources.release(view);
    }
  };

  var makeFocusCallback = function(hasFocus){
    return function(event) {
      _DoChangeFocus(shadow_instance.instance, hasFocus);
      return true;
    };
  };

  var instance = resources.register(INSTANCE_RESOURCE, {
    element: shadow_instance,
    device: null,
    createCanvas: function(width, height, opaque) {
      var canvas = document.createElement('canvas');
      canvas.className = "ppapiJsCanvas";
      canvas.width = width;
      canvas.height = height;
      canvas.style.border = "0px";
      canvas.style.padding = "0px";
      canvas.style.margin = "0px";
      canvas.style.backgroundColor = opaque ? "black" : "transparent";
      return canvas;
    },
    bind: function(device) {
      this.unbind();
      this.device = device;
      resources.addRef(this.device.uid);
      this.device.notifyBound(this);
    },
    unbind: function() {
      if (this.device) {
        this.device.notifyUnbound(this);
        resources.release(this.device.uid);
        this.device = null;
      }
    },
    destroy: function() {
      this.unbind();
    }
  });

  // Allows shadow_instance.postMessage to work.
  // This is only a UID so there is no circular reference.
  shadow_instance.instance = instance;

  shadow_instance.onselectstart = function(evt) {
      evt.preventDefault();
      return false;
  };
  shadow_instance.setAttribute('tabindex', '0'); // make it focusable
  shadow_instance.addEventListener('focus', makeFocusCallback(true));
  shadow_instance.addEventListener('blur', makeFocusCallback(false));

  // Called from external code.
  shadow_instance["finishLoading"] = function() {
    // Turn the element's attributes into PPAPI's arguments.
    // TODO(ncbray): filter out style attribute?
    var argc = shadow_instance.attributes.length;
    var argn = allocate(argc * 4, 'i8', ALLOC_NORMAL);
    var argv = allocate(argc * 4, 'i8', ALLOC_NORMAL);
    for (var i = 0; i < argc; i++) {
      var attribute = shadow_instance.attributes[i];
      setValue(argn + i * 4, allocate(intArrayFromString(attribute.name), 'i8', ALLOC_NORMAL), 'i32');
      setValue(argv + i * 4, allocate(intArrayFromString(attribute.value), 'i8', ALLOC_NORMAL), 'i32');
    }

    _NativeCreateInstance(instance, argc, argn, argv);

    // Clean up the arguments.
    for (var i = 0; i < argc; i++) {
      _free(getValue(argn + i * 4, 'i32'));
      _free(getValue(argv + i * 4, 'i32'));
    }
    _free(argn);
    _free(argv);

    updateView();

    var sendProgressEvent = function(name) {
      var evt = document.createEvent('Event');
      evt.initEvent(name, true, true);  // bubbles, cancelable
      shadow_instance.dispatchEvent(evt);
    }

    // Fake the load sequence.
    // Note that .readyState will always be "complete" in IE, and
    // this assignment will do nothing.
    shadow_instance.readyState = 4;
    sendProgressEvent('load');
    sendProgressEvent('loadend');
  };

  window.addEventListener('DOMContentLoaded', updateView);
  window.addEventListener('load', updateView);
  window.addEventListener('scroll', updateView);
  window.addEventListener('resize', updateView);

  // TODO(ncbray): element resize.

  var target = shadow_instance;
  if (target.requestFullscreen) {
    document.addEventListener('fullscreenchange', updateView);
  } else if (target.mozRequestFullScreen) {
    document.addEventListener('mozfullscreenchange', updateView);
  } else if (target.webkitRequestFullscreen) {
    document.addEventListener('webkitfullscreenchange', updateView);
  } else if (target.msRequestFullscreen) {
    document.addEventListener('MSFullscreenChange', updateView);
  }

  // TODO handle removal events.
  return shadow_instance;
}

// Entry point
window["CreateInstance"] = CreateInstance;

var glue = {};

glue.k2_32 = 0x100000000;

glue.ToI64 = function(low, high){
  var val = low + (high * glue.k2_32);
  if (((val - low) / glue.k2_32) !== high || (val % glue.k2_32) !== low) {
    throw "Value " + String([low, high]) + " cannot be represented as a Javascript number";
  }
  return val;
};

glue.decodeUTF8 = function(ptr, len) {
  var chars = [];
  var i = 0;
  var val;
  var n;
  var b;

  // If no len is provided, assume null termination
  while (len === undefined || i < len) {
    b = HEAPU8[ptr + i];
    if (len === undefined && b === 0) {
      break;
    }

    i += 1;
    if (b < 0x80) {
      val = b;
      n = 0;
    } else if ((b & 0xE0) === 0xC0) {
      val = b & 0x1f;
      n = 1;
    } else if ((b & 0xF0) === 0xE0) {
      val = b & 0x0f;
      n = 2;
    } else if ((b & 0xF8) === 0xF0) {
      val = b & 0x07;
      n = 3;
    } else if ((b & 0xFC) === 0xF8) {
      val = b & 0x03;
      n = 4;
    } else if ((b & 0xFE) === 0xFC) {
      val = b & 0x01;
      n = 5;
    } else {
      return null;
    }
    if (i + n > len) {
      return null;
    }
    while (n > 0) {
      b = HEAPU8[ptr + i];
      if ((b & 0xC0) !== 0x80) {
        return null;
      }
      val = (val << 6) | (b & 0x3f);
      i += 1;
      n -= 1;
    }
    chars.push(String.fromCharCode(val));
  }
  return chars.join("");
};

glue.getPoint = function(ptr) {
  return {
    x: getValue(ptr, 'i32'),
    y: getValue(ptr + 4, 'i32')
  };
};

glue.setPoint = function(obj, ptr) {
  setValue(ptr, obj.x, 'i32');
  setValue(ptr + 4, obj.y, 'i32');
};

// No need for getFloatPoint, yet.

glue.setFloatPoint = function(obj, ptr) {
  setValue(ptr, obj.x, 'float');
  setValue(ptr + 4, obj.y, 'float');
};

glue.getSize = function(ptr) {
  return {
    width: getValue(ptr, 'i32'),
    height: getValue(ptr + 4, 'i32')
  };
};

glue.setSize = function(obj, ptr) {
  setValue(ptr, obj.width, 'i32');
  setValue(ptr + 4, obj.height, 'i32');
};

glue.getRect = function(ptr) {
  return {
    point: glue.getPoint(ptr),
    size: glue.getSize(ptr + 8)
  };
};

glue.setRect = function(rect, ptr) {
  glue.setPoint(rect.point, ptr);
  glue.setSize(rect.size, ptr + 8);
};

glue.getVarType = function(ptr) {
  // ptr->type is offset 0.
  return getValue(ptr, 'i32');
};

glue.isRefCountedVarType = function(type) {
  return type >= 5;
};

glue.getVarUID = function(ptr) {
  // ptr->value is offset 8.
  return getValue(ptr + 8, 'i32');
};

glue.memoryToStructVar = function(ptr) {
  var type = glue.getVarType(ptr);
  var value;
  if (type == ppapi.PP_VARTYPE_DOUBLE) {
    value = getValue(ptr + 8, 'double');
  } else {
    value = getValue(ptr + 8, 'i32');
  }
  return {type: type, value: value};
};

glue.structAddRef = function(e) {
  if (glue.isRefCountedVarType(e.type)) {
    resources.addRef(e.value);
  }
};

glue.structRelease = function(e) {
  if (glue.isRefCountedVarType(e.type)) {
    resources.release(e.value);
  }
};

glue.memoryToJSVar = function(ptr) {
  return glue.structToJSVar(glue.memoryToStructVar(ptr));
};

glue.structToJSVar = function(e) {
  var type = e.type;
  var value = e.value;
  if (type == ppapi.PP_VARTYPE_UNDEFINED) {
    return undefined;
  } else if (type == ppapi.PP_VARTYPE_NULL) {
    return null;
  } else if (type == ppapi.PP_VARTYPE_BOOL) {
    return 0 != value;
  } else if (type == ppapi.PP_VARTYPE_INT32) {
    return value | 0;
  } else if (type == ppapi.PP_VARTYPE_DOUBLE) {
    return value;
  } else if (type == ppapi.PP_VARTYPE_STRING) {
    return resources.resolve(value, STRING_RESOURCE).value;
  } else if (type == ppapi.PP_VARTYPE_ARRAY) {
    // TODO(ncbray): deduplicate DAG nodes.
    var wrapped = resources.resolve(value, ARRAY_RESOURCE).value;
    var unwrapped = [];
    for (var i = 0; i < wrapped.length; i++) {
      unwrapped.push(glue.structToJSVar(wrapped[i]));
    }
    return unwrapped;
  } else if (type == ppapi.PP_VARTYPE_DICTIONARY) {
    // TODO(ncbray): deduplicate DAG nodes.
    var wrapped = resources.resolve(value, DICTIONARY_RESOURCE).value;
    var unwrapped = {};
    for (var key in wrapped) {
      unwrapped[key] = glue.structToJSVar(wrapped[key]);
    }
    return unwrapped;
  } else if (type == ppapi.PP_VARTYPE_ARRAY_BUFFER) {
    var wrapped = resources.resolve(value, ARRAY_BUFFER_RESOURCE);

    // Note: "buffer" is an implementation detail of Emscripten and is likely
    // not a stable interface.
    var unwrapped = buffer.slice(wrapped.memory, wrapped.memory + wrapped.len);
    return unwrapped;
  } else {
    throw "Var type conversion not implemented: " + type;
  }
};

glue.jsToStructVar = function(obj) {
  var type = 0;
  var value = 0;

  var typen = (typeof obj);
  if (typen === 'string') {
    type = ppapi.PP_VARTYPE_STRING;
    var arr = intArrayFromString(obj);
    var memory = allocate(arr, 'i8', ALLOC_NORMAL);
    // Length is adjusted for null terminator.
    value = resources.registerString(obj, memory, arr.length-1);
  } else if (typen === 'number') {
    // Note this will always create a double, even when the value can be
    // represented as an int32.
    type = ppapi.PP_VARTYPE_DOUBLE;
    value = obj;
  } else if (typen === 'boolean') {
    type = ppapi.PP_VARTYPE_BOOL;
    value = obj ? 1 : 0;
  } else if (typen === 'undefined') {
    type = ppapi.PP_VARTYPE_UNDEFINED;
  } else if (obj === null) {
    type = ppapi.PP_VARTYPE_NULL;
  } else if (obj instanceof Array) {
    // TODO(ncbray): deduplicate DAG nodes.
    type = ppapi.PP_VARTYPE_ARRAY;
    var wrapped = [];
    value = resources.registerArray(wrapped);
    for (var i = 0; i < obj.length; i++) {
      wrapped.push(glue.jsToStructVar(obj[i]));
    }
  } else if (obj instanceof ArrayBuffer) {
    type = ppapi.PP_VARTYPE_ARRAY_BUFFER;
    var memory = _malloc(obj.byteLength);
    // Note: "buffer" is an implementation detail of Emscripten and is likely
    // not a stable interface.
    var memory_view = new Int8Array(buffer, memory, obj.byteLength);
    memory_view.set(new Int8Array(obj));
    value = resources.registerArrayBuffer(memory, obj.byteLength);
  } else if (typen === 'object') {
    // Note that this need to go last because many things are "objects", such as
    // ArrayBuffers.  Is there a good way to distinguish between plain-old
    // objects and other types?

    // TODO(ncbray): deduplicate DAG nodes.
    type = ppapi.PP_VARTYPE_DICTIONARY;
    var wrapped = {};
    value = resources.registerDictionary(wrapped);
    for (var key in obj) {
      wrapped[key] = glue.jsToStructVar(obj[key]);
    }
  } else {
    throw "Var type conversion not implemented: " + typen;
  }
  return {type: type, value: value};
};

glue.structToMemoryVar = function(e, ptr) {
  setValue(ptr, e.type, 'i32');
  if (e.type === ppapi.PP_VARTYPE_DOUBLE) {
    setValue(ptr + 8, e.value, 'double');
  } else {
    // Note: PPAPI defines var IDs as 64-bit values, but in practice we're only
    // using 32 bits, so we can handle UIDs in this catch all.
    setValue(ptr + 8, e.value, 'i32');
    setValue(ptr + 12, 0, 'i32'); // Paranoia.
  }
};

glue.jsToMemoryVar = function(obj, ptr) {
  glue.structToMemoryVar(glue.jsToStructVar(obj), ptr);
};

glue.setIntVar = function(obj, ptr) {
  var typen = (typeof obj);
  var valptr = ptr + 8;
  if (typen === 'number') {
    setValue(ptr, ppapi.PP_VARTYPE_INT32, 'i32');
    setValue(valptr, obj, 'i32');
  } else {
    throw "Not an integer: " + typen;
  }
};

glue.getCompletionCallback = function(ptr) {
  var func = getValue(ptr, 'i32');
  var user_data = getValue(ptr + 4, 'i32');
  return function(result) {
    if (typeof result !== 'number') {
      throw "Invalid argument to callback: " + result;
    }
    Runtime.dynCall('vii', func, [user_data, result]);
  };
};

glue.defer = function(callback) {
  setTimeout(callback, 0);
};


var ppapi = (function() {
  var ppapi = {
    /**
     * This value is returned by a function on successful synchronous completion
     * or is passed as a result to a PP_CompletionCallback_Func on successful
     * asynchronous completion.
     */
    PP_OK: 0,
    /**
     * This value is returned by a function that accepts a PP_CompletionCallback
     * and cannot complete synchronously. This code indicates that the given
     * callback will be asynchronously notified of the final result once it is
     * available.
     */
    PP_OK_COMPLETIONPENDING: -1,
    /**This value indicates failure for unspecified reasons. */
    PP_ERROR_FAILED: -2,
    /**
     * This value indicates failure due to an asynchronous operation being
     * interrupted. The most common cause of this error code is destroying a
     * resource that still has a callback pending. All callbacks are guaranteed
     * to execute, so any callbacks pending on a destroyed resource will be
     * issued with PP_ERROR_ABORTED.
     *
     * If you get an aborted notification that you aren't expecting, check to
     * make sure that the resource you're using is still in scope. A common
     * mistake is to create a resource on the stack, which will destroy the
     * resource as soon as the function returns.
     */
    PP_ERROR_ABORTED: -3,
    /** This value indicates failure due to an invalid argument. */
    PP_ERROR_BADARGUMENT: -4,
    /** This value indicates failure due to an invalid PP_Resource. */
    PP_ERROR_BADRESOURCE: -5,
    /** This value indicates failure due to an unavailable PPAPI interface. */
    PP_ERROR_NOINTERFACE: -6,
    /** This value indicates failure due to insufficient privileges. */
    PP_ERROR_NOACCESS: -7,
    /** This value indicates failure due to insufficient memory. */
    PP_ERROR_NOMEMORY: -8,
    /** This value indicates failure due to insufficient storage space. */
    PP_ERROR_NOSPACE: -9,
    /** This value indicates failure due to insufficient storage quota. */
    PP_ERROR_NOQUOTA: -10,
    /**
     * This value indicates failure due to an action already being in
     * progress.
     */
    PP_ERROR_INPROGRESS: -11,
    /** This value indicates failure due to a file that does not exist. */
    /**
     * The requested command is not supported by the browser.
     */
    PP_ERROR_NOTSUPPORTED: -12,
    /**
     * Returned if you try to use a null completion callback to "block until
     * complete" on the main thread. Blocking the main thread is not permitted
     * to keep the browser responsive (otherwise, you may not be able to handle
     * input events, and there are reentrancy and deadlock issues).
     *
     * The goal is to provide blocking calls from background threads, but PPAPI
     * calls on background threads are not currently supported. Until this
     * support is complete, you must either do asynchronous operations on the
     * main thread, or provide an adaptor for a blocking background thread to
     * execute the operaitions on the main thread.
     */
    PP_ERROR_BLOCKS_MAIN_THREAD: -13,
    PP_ERROR_FILENOTFOUND: -20,
    /** This value indicates failure due to a file that already exists. */
    PP_ERROR_FILEEXISTS: -21,
    /** This value indicates failure due to a file that is too big. */
    PP_ERROR_FILETOOBIG: -22,
    /**
     * This value indicates failure due to a file having been modified
     * unexpectedly.
     */
    PP_ERROR_FILECHANGED: -23,
    /** This value indicates failure due to a time limit being exceeded. */
    PP_ERROR_TIMEDOUT: -30,
    /**
     * This value indicates that the user cancelled rather than providing
     * expected input.
     */
    PP_ERROR_USERCANCEL: -40,
    /**
     * This value indicates failure due to lack of a user gesture such as a
     * mouse click or key input event. Examples of actions requiring a user
     * gesture are showing the file chooser dialog and going into fullscreen
     * mode.
     */
    PP_ERROR_NO_USER_GESTURE: -41,
    /**
     * This value indicates that the graphics context was lost due to a
     * power management event.
     */
    PP_ERROR_CONTEXT_LOST: -50,
    /**
     * Indicates an attempt to make a PPAPI call on a thread without previously
     * registering a message loop via PPB_MessageLoop.AttachToCurrentThread.
     * Without this registration step, no PPAPI calls are supported.
     */
    PP_ERROR_NO_MESSAGE_LOOP: -51,
    /**
     * Indicates that the requested operation is not permitted on the current
     * thread.
     */
    PP_ERROR_WRONG_THREAD: -52,

    PP_GRAPHICS3DATTRIB_ALPHA_SIZE: 0x3021,
    PP_GRAPHICS3DATTRIB_BLUE_SIZE: 0x3022,
    PP_GRAPHICS3DATTRIB_GREEN_SIZE: 0x3023,
    PP_GRAPHICS3DATTRIB_RED_SIZE: 0x3024,
    PP_GRAPHICS3DATTRIB_DEPTH_SIZE: 0x3025,
    PP_GRAPHICS3DATTRIB_STENCIL_SIZE: 0x3026,
    PP_GRAPHICS3DATTRIB_SAMPLES: 0x3031,
    PP_GRAPHICS3DATTRIB_SAMPLE_BUFFERS: 0x3032,
    PP_GRAPHICS3DATTRIB_NONE: 0x3038,
    PP_GRAPHICS3DATTRIB_HEIGHT: 0x3056,
    PP_GRAPHICS3DATTRIB_WIDTH: 0x3057,
    PP_GRAPHICS3DATTRIB_SWAP_BEHAVIOR: 0x3093,
    PP_GRAPHICS3DATTRIB_BUFFER_PRESERVED: 0x3094,
    PP_GRAPHICS3DATTRIB_BUFFER_DESTROYED: 0x3095,
    PP_GRAPHICS3DATTRIB_GPU_PREFERENCE: 0x11000,
    PP_GRAPHICS3DATTRIB_GPU_PREFERENCE_LOW_POWER: 0x11001,
    PP_GRAPHICS3DATTRIB_GPU_PREFERENCE_PERFORMANCE: 0x11002,

    PP_VARTYPE_UNDEFINED: 0,
    PP_VARTYPE_NULL: 1,
    PP_VARTYPE_BOOL: 2,
    PP_VARTYPE_INT32: 3,
    PP_VARTYPE_DOUBLE: 4,
    PP_VARTYPE_STRING: 5,
    PP_VARTYPE_ARRAY: 7,
    PP_VARTYPE_DICTIONARY: 8,
    PP_VARTYPE_ARRAY_BUFFER: 9
  };

  return ppapi;
})();


// Called from generated code.
var _GetBrowserInterface = function(interface_name) {
  var name = Pointer_stringify(interface_name);
  if (!(name in interfaces)) {
    console.error('Requested unknown interface: ' + name);
    return 0;
  }
  var inf = interfaces[name]|0;
  if (inf === 0) {
    // The interface exists, but it is not available for this particular browser.
    console.error('Requested unavailable interface: ' + name);
  }
  return inf;
};
