var $builtinmodule = function(name)
{
  var mod = {__name__: new Sk.builtin.str("webgl")};

  var makeFailHTML = function(msg) {
    return '' +
      '<table style="background-color: #8CE; width: 100%; height: 100%;"><tr>' +
      '<td align="center">' +
      '<div style="display: table-cell; vertical-align: middle;">' +
      '<div style="">' + msg + '</div>' +
      '</div>' +
      '</td></tr></table>';
  };

  var GET_A_WEBGL_BROWSER = '' +
    'This page requires a browser that supports WebGL.<br/>' +
    '<a href="http://get.webgl.org">Click here to upgrade your browser.</a>';

  var NEED_HARDWARE = '' +
    "It doesn't appear your computer can support WebGL.<br/>" +
    '<a href="http://get.webgl.org">Click here for more information.</a>';
  
  var create3DContext = function(canvas) {
    var names = ["webgl", "experimental-webgl", "webkit-3d", "moz-webgl"];
    var gl = null;
    for (var ii = 0; ii < names.length; ++ii) {
      try {
        gl = canvas.getContext(names[ii]);
      }
      catch(e) {
      }
      if (gl) {
          break;
      }
    }
    if (gl) {
      // Disallow selection by default. This keeps the cursor from changing to an
      // I-beam when the user clicks and drags. It's easier on the eyes.
      function returnFalse() {
        return false;
      }

      canvas.onselectstart = returnFalse;
      canvas.onmousedown = returnFalse;
    }
    return gl;
  };

  var setupWebGL = function(canvasContainerId, opt_canvas) {
    var container = document.getElementById(canvasContainerId);
    var context;
    if (!opt_canvas) {
      opt_canvas = container.getElementsByTagName("canvas")[0];
    }
    if (!opt_canvas) {
        // this browser doesn't support the canvas tag at all. Not even 2d.
      container.innerHTML = makeFailHTML(GET_A_WEBGL_BROWSER);
      return;
    }

    var gl = create3DContext(opt_canvas);
    if (!gl) {
      // TODO(gman): fix to official way to detect that it's the user's machine, not the browser.
      var browserStrings = navigator.userAgent.match(/(\w+\/.*? )/g);
      var browsers = {};
      try {
        for (var b = 0; b < browserStrings.length; ++b) {
          var parts = browserStrings[b].match(/(\w+)/g);
          var bb = [];
          for (var ii = 1; ii < parts.length; ++ii) {
            bb.push(parseInt(parts[ii]));
          }
          browsers[parts[0]] = bb;
        }
      }
      catch (e) {
      }
      if (browsers.Chrome &&
           (browsers.Chrome[0] > 7 ||
                 (browsers.Chrome[0] == 7 && browsers.Chrome[1] > 0) ||
                 (browsers.Chrome[0] == 7 && browsers.Chrome[1] == 0 && browsers.Chrome[2] >= 521))) {
        container.innerHTML = makeFailHTML(NEED_HARDWARE);
      }
      else {
        container.innerHTML = makeFailHTML(GET_A_WEBGL_BROWSER);
      }
    }
    return gl;
  };

  /**
   * The Context encapsulates the underlying WebGL native JavaScript API.
   */
  mod.Context = Sk.misceval.buildClass(mod, function($gbl, $loc) {
    $loc.__init__ = new Sk.builtin.func(
      function(self, canvasid) {
        var canvas = document.getElementById(canvasid.v);
        var gl = setupWebGL(canvasid.v, canvas)
        if (!gl) {
          throw new Error("Your browser does not appear to support WebGL.");
        }

        self.gl = gl;

        // Copy symbolic constants and functions from native WebGL, encapsulating where necessary.       
        for (var k in gl.__proto__) {
          if (typeof gl.__proto__[k] === 'number') {
            Sk.abstr.objectSetItem(self['$d'], new Sk.builtin.str(k), gl.__proto__[k]);
          }
          else if (typeof gl.__proto__[k] === "function") {
            switch(k) {
              case 'bufferData': {
              }
              break;
              case 'clearColor': {
              }
              break;
              case 'drawArrays': {
              }
              break;
              case 'getAttribLocation': {
              }
              break;
              case 'getUniformLocation': {
              }
              break;
              case 'shaderSource': {
              }
              break;
              case 'uniformMatrix4fv': {
              }
              break;
              case 'vertexAttribPointer': {
              }
              break;
              case 'viewport': {
              }
              break;
              default: {
                (function(key) {
                  Sk.abstr.objectSetItem(self['$d'], new Sk.builtin.str(k), new Sk.builtin.func(function() {
                    var f = gl.__proto__[key];
                    return f.apply(gl, arguments);
                  }));
                 }(k));
              }
            }
          }
        }

        gl.clearColor(100.0/255.0, 149.0/255.0, 237.0/255.0, 1.0);
        gl.clear(gl.COLOR_BUFFER_BIT);
      }
    );

    $loc.tp$getattr = Sk.generic.getAttr;

    $loc.bufferData = new Sk.builtin.func(
      function(self, target, data, usage) {
        self.gl.bufferData(target, data.v, usage);
      }
    );

    $loc.clearColor = new Sk.builtin.func(
      function(self, red, green, blue, alpha) {
        self.gl.clearColor(Sk.builtin.asnum$(red), Sk.builtin.asnum$(green), Sk.builtin.asnum$(blue), Sk.builtin.asnum$(alpha));
      }
    );

    $loc.getAttribLocation = new Sk.builtin.func(
      function(self, program, name) {
        return self.gl.getAttribLocation(program, name.v);
      }
    );

    $loc.getUniformLocation = new Sk.builtin.func(
      function(self, program, name) {
        return self.gl.getUniformLocation(program, name.v);
      }
    );

    $loc.shaderSource = new Sk.builtin.func(
      function(self, shader, src) {
        self.gl.shaderSource(shader, src.v);
      }
    );

    $loc.drawArrays = new Sk.builtin.func(
      function(self, mode, first, count) {
        self.gl.drawArrays(Sk.builtin.asnum$(mode), Sk.builtin.asnum$(first), Sk.builtin.asnum$(count));
      }
    );

    $loc.vertexAttribPointer = new Sk.builtin.func(
      function(self, index, size, type, normalized, stride, dunno) {
        self.gl.vertexAttribPointer(index, Sk.builtin.asnum$(size), Sk.builtin.asnum$(type), normalized, Sk.builtin.asnum$(stride), Sk.builtin.asnum$(dunno));
      }
    );

    $loc.viewport = new Sk.builtin.func(
      function(self, x, y, width, height) {
        self.gl.viewport(Sk.builtin.asnum$(x), Sk.builtin.asnum$(y), Sk.builtin.asnum$(width), Sk.builtin.asnum$(height));
      }
    );

    $loc.uniformMatrix4fv = new Sk.builtin.func(
      function(self, location, transpose, values) {
//        console.log("location  " + (typeof location));
//        console.log("transpose " + (typeof transpose));
//        console.log("values.v  " + (typeof values.v));
        self.gl.uniformMatrix4fv(Sk.builtin.asnum$(location), transpose, values.v);
      }
    );

    $loc.setDrawFunc = new Sk.builtin.func(function(self, func) {
      var startTime = (new Date()).getTime();
      var intervalId = setInterval(
        function() {
          Sk.misceval.callsimArray(func, [self, (new Date()).getTime() - startTime]);
        }, 1000.0 / 60.0); // 60 fps
    });

  }, 'Context', []);

  mod.Float32Array = Sk.misceval.buildClass(mod, function($gbl, $loc) {
    $loc.__init__ = new Sk.builtin.func(function(self, data) {
      if (typeof data === "number") {
        self.v = new Float32Array(data);
      }
      else {
        self.v = new Float32Array(Sk.ffi.remapToJs(data));
      }
    });

    $loc.__repr__ = new Sk.builtin.func(function(self) {
      var copy = [];
      for (var i = 0; i < self.v.length; ++i) {
        copy.push(self.v[i]);
      }
      return new Sk.builtin.str("[" + copy.join(', ') + "]");
     });
  }, 'Float32Array', []);

  /**
   * A 4x4 (mutable) matrix suitable for OpenGL.
   *
   * Mutability is chosen for performance.
   * The inderlying implementation is Float32Array.
   * The indexing of the elements is
   * 0 4  8 12
   * 1 5  9 13
   * 2 6 10 14
   * 3 7 11 15
   */
  mod.Matrix4x4 = Sk.misceval.buildClass(mod, function($gbl, $loc) {
    $loc.__init__ = new Sk.builtin.func(function(self, data) {
      self.v = new Float32Array(Sk.ffi.remapToJs(data));
    });

    $loc.identity = new Sk.builtin.func(
      function(self) {

        var m = self.v;

        m[0]  = 1;
        m[1]  = 0;
        m[2]  = 0;
        m[3]  = 0;

        m[4]  = 0;
        m[5]  = 1;
        m[6]  = 0;
        m[7]  = 0;

        m[8]  = 0;
        m[9]  = 0;
        m[10] = 1;
        m[11] = 0;

        m[12] = 0;
        m[13] = 0;
        m[14] = 0;
        m[15] = 1;
      }
    );

    $loc.perspective = new Sk.builtin.func(
      function(self, fov, aspect, near, far) {
        
        var t = Math.tan(Math.PI * 0.5 - 0.5 * (Sk.builtin.asnum$(fov) * Math.PI / 180));
        var a = Sk.builtin.asnum$(aspect)
        var n = Sk.builtin.asnum$(near)
        var f = Sk.builtin.asnum$(far)
        var k = 1.0 / (n - f);

        var m = self.v;

        m[0]  = t / a;
        m[1]  = 0;
        m[2]  = 0;
        m[3]  = 0;

        m[4]  = 0;
        m[5]  = t;
        m[6]  = 0;
        m[7]  = 0;

        m[8]  = 0;
        m[9]  = 0;
        m[10] = (n + f) * k;
        m[11] = -1;

        m[12] = 0;
        m[13] = 0;
        m[14] = n * f * k * 2;
        m[15] = 0;
      }
    );

    $loc.translate = new Sk.builtin.func(
      function(self, translation) {

        var m = self.v;
        var t = Sk.ffi.remapToJs(translation);

        m[0]  = 1;
        m[1]  = 0;
        m[2]  = 0;
        m[3]  = 0;

        m[4]  = 0;
        m[5]  = 1;
        m[6]  = 0;
        m[7]  = 0;

        m[8]  = 0;
        m[9]  = 0;
        m[10] = 1;
        m[11] = 0;

        m[12] = t[0];
        m[13] = t[1];
        m[14] = t[2];
        m[15] = 1;
      }
    );

    $loc.__repr__ = new Sk.builtin.func(function(self) {
      var copy = [];
      for (var i = 0; i < self.v.length; ++i) {
        copy.push(self.v[i]);
      }
      return new Sk.builtin.str("[" + copy.join(', ') + "]");
     });
  }, 'Matrix4x4', []);

  return mod;
};
