Thingiloader = function(event) {
  // Code from https://developer.mozilla.org/En/Using_XMLHttpRequest#Receiving_binary_data
  this.load_binary_resource = function(url) {
  	var req = new XMLHttpRequest();
  	req.open('GET', url, false);
  	// The following line says we want to receive data as Binary and not as Unicode
  	req.overrideMimeType('text/plain; charset=x-user-defined');
  	req.send(null);
  	if (req.status != 200) return '';

  	return req.responseText;
  };

  this.loadSTL = function(url) {
    var looksLikeBinary = function(reader) {
      // STL files don't specify a way to distinguish ASCII from binary.
      // The usual way is checking for "solid" at the start of the file --
      // but Thingiverse has seen at least one binary STL file in the wild
      // that breaks this.

      // The approach here is different: binary STL files contain a triangle
      // count early in the file.  If this correctly predicts the file's length,
      // it is most probably a binary STL file.

      reader.seek(80);  // skip the header
      var count = reader.readUInt32();

      var predictedSize = 80 /* header */ + 4 /* count */ + 50 * count;
      return reader.getSize() == predictedSize;
    };

    workerFacadeMessage({'status':'message', 'content':'Downloading ' + url});
    var file = this.load_binary_resource(url);
    var reader = new BinaryReader(file);

    if (looksLikeBinary(reader)) {
      this.loadSTLBinary(reader);
    } else {
      this.loadSTLString(file);
    }
  };

  this.loadOBJ = function(url) {
    workerFacadeMessage({'status':'message', 'content':'Downloading ' + url});
    var file = this.load_binary_resource(url);
    this.loadOBJString(file);
  };

  this.loadJSON = function(url) {
    workerFacadeMessage({'status':'message', 'content':'Downloading ' + url});
    var file = this.load_binary_resource(url);
    this.loadJSONString(file);
  };

  this.loadPLY = function(url) {
    workerFacadeMessage({'status':'message', 'content':'Downloading ' + url});

    var file = this.load_binary_resource(url);

    if (file.match(/format ascii/i)) {
      this.loadPLYString(file);
    } else {
      this.loadPLYBinary(file);
    }
  };

  this.loadSTLString = function(STLString) {
    workerFacadeMessage({'status':'message', 'content':'Parsing STL String...'});
    workerFacadeMessage({'status':'complete', 'content':this.ParseSTLString(STLString)});
  };

  this.loadSTLBinary = function(STLBinary) {
    workerFacadeMessage({'status':'message', 'content':'Parsing STL Binary...'});
    workerFacadeMessage({'status':'complete', 'content':this.ParseSTLBinary(STLBinary)});
  };

  this.loadOBJString = function(OBJString) {
    workerFacadeMessage({'status':'message', 'content':'Parsing OBJ String...'});
    workerFacadeMessage({'status':'complete', 'content':this.ParseOBJString(OBJString)});
  };

  this.loadJSONString = function(JSONString) {
    workerFacadeMessage({'status':'message', 'content':'Parsing JSON String...'});
    workerFacadeMessage({'status':'complete', 'content':eval(JSONString)});
  };

  this.loadPLYString = function(PLYString) {
    workerFacadeMessage({'status':'message', 'content':'Parsing PLY String...'});
    workerFacadeMessage({'status':'complete_points', 'content':this.ParsePLYString(PLYString)});
  };

  this.loadPLYBinary = function(PLYBinary) {
    workerFacadeMessage({'status':'message', 'content':'Parsing PLY Binary...'});
    workerFacadeMessage({'status':'complete_points', 'content':this.ParsePLYBinary(PLYBinary)});
  };

  this.ParsePLYString = function(input) {
    var properties = [];
    var vertices = [];
    var colors = [];

    var vertex_count = 0;

    var header = /ply\n([\s\S]+)\nend_header/ig.exec(input)[1];
    var data = /end_header\n([\s\S]+)$/ig.exec(input)[1];

    // workerFacadeMessage({'status':'message', 'content':'header:\n' + header});
    // workerFacadeMessage({'status':'message', 'content':'data:\n' + data});

    header_parts = header.split("\n");

    for (i in header_parts) {
      if (/element vertex/i.test(header_parts[i])) {
        vertex_count = /element vertex (\d+)/i.exec(header_parts[i])[1];
      } else if (/property/i.test(header_parts[i])) {
        properties.push(/property (.*) (.*)/i.exec(header_parts[i])[2]);
      }
    }

    // workerFacadeMessage({'status':'message', 'content':'properties: ' + properties});

    data_parts = data.split("\n");

    for (i in data_parts) {
      data_line = data_parts[i];
      data_line_parts = data_line.split(" ");

      vertices.push([
        parseFloat(data_line_parts[properties.indexOf("x")]),
        parseFloat(data_line_parts[properties.indexOf("y")]),
        parseFloat(data_line_parts[properties.indexOf("z")])
      ]);

      colors.push([
        parseInt(data_line_parts[properties.indexOf("red")]),
        parseInt(data_line_parts[properties.indexOf("green")]),
        parseInt(data_line_parts[properties.indexOf("blue")])
      ]);
    }

    // workerFacadeMessage({'status':'message', 'content':'vertices: ' + vertices});

    return [vertices, colors];
  };

  this.ParsePLYBinary = function(input) {
    return false;
  };

  this.ParseSTLBinary = function(input) {
    // Skip the header.
    input.seek(80);

    // Load the number of vertices.
    var count = input.readUInt32();

    // During the parse loop we maintain the following data structures:
    var vertices = [];   // Append-only list of all unique vertices.
    var vert_hash = {};  // Mapping from vertex to index in 'vertices', above.
    var faces    = [];   // List of triangle descriptions, each a three-element
                         // list of indices in 'vertices', above.

    for (var i = 0; i < count; i++) {
      if (i % 100 == 0) {
        workerFacadeMessage({
            'status':'message',
            'content':'Parsing ' + (i+1) + ' of ' + count + ' polygons...'
          });
        workerFacadeMessage({
            'status':'progress',
            'content':parseInt(i / count * 100) + '%'
          });
      }

      // Skip the normal (3 single-precision floats)
      input.seek(input.getPosition() + 12);

      var face_indices = [];
      for (var x = 0; x < 3; x++) {
        var vertex = [input.readFloat(), input.readFloat(), input.readFloat()];

        var vertexIndex = vert_hash[vertex];
        if (vertexIndex == null) {
          vertexIndex = vertices.length;
          vertices.push(vertex);
          vert_hash[vertex] = vertexIndex;
        }

        face_indices.push(vertexIndex);
      }
      faces.push(face_indices);

      // Skip the "attribute" field (unused in common models)
      input.readUInt16();
    }

    return [vertices, faces];
  };

  // build stl's vertex and face arrays
  this.ParseSTLString = function(STLString) {
    var vertexes  = [];
    var faces     = [];

    var face_vertexes = [];
    var vert_hash = {}

    // console.log(STLString);

    // strip out extraneous stuff
    STLString = STLString.replace(/\r/, "\n");
    STLString = STLString.replace(/^solid[^\n]*/, "");
    STLString = STLString.replace(/\n/g, " ");
    STLString = STLString.replace(/facet normal /g,"");
    STLString = STLString.replace(/outer loop/g,"");
    STLString = STLString.replace(/vertex /g,"");
    STLString = STLString.replace(/endloop/g,"");
    STLString = STLString.replace(/endfacet/g,"");
    STLString = STLString.replace(/endsolid[^\n]*/, "");
    STLString = STLString.replace(/\s+/g, " ");
    STLString = STLString.replace(/^\s+/, "");

    // console.log(STLString);

    var facet_count = 0;
    var block_start = 0;

    var points = STLString.split(" ");

    workerFacadeMessage({'status':'message', 'content':'Parsing vertices...'});
    for (var i=0; i<points.length/12-1; i++) {
      if ((i % 100) == 0) {
        workerFacadeMessage({'status':'progress', 'content':parseInt(i / (points.length/12-1) * 100) + '%'});
      }

      var face_indices = [];
      for (var x=0; x<3; x++) {
        var vertex = [parseFloat(points[block_start+x*3+3]), parseFloat(points[block_start+x*3+4]), parseFloat(points[block_start+x*3+5])];

        var vertexIndex = vert_hash[vertex];
        if (vertexIndex == null) {
          vertexIndex = vertexes.length;
          vertexes.push(vertex);
          vert_hash[vertex] = vertexIndex;
        }

        face_indices.push(vertexIndex);
      }
      faces.push(face_indices);

      block_start = block_start + 12;
    }

    return [vertexes, faces];
  };

  this.ParseOBJString = function(OBJString) {
    var vertexes  = [];
    var faces     = [];

    var lines = OBJString.split("\n");

    // var normal_position = 0;

    for (var i=0; i<lines.length; i++) {
      workerFacadeMessage({'status':'progress', 'content':parseInt(i / lines.length * 100) + '%'});

      line_parts = lines[i].replace(/\s+/g, " ").split(" ");

      if (line_parts[0] == "v") {
        vertexes.push([parseFloat(line_parts[1]), parseFloat(line_parts[2]), parseFloat(line_parts[3])]);
      } else if (line_parts[0] == "f") {
        faces.push([parseFloat(line_parts[1].split("/")[0])-1, parseFloat(line_parts[2].split("/")[0])-1, parseFloat(line_parts[3].split("/")[0]-1), 0])
      }
    }

    return [vertexes, faces];
  };

  switch(event.data.cmd) {
    case "loadSTL":
    this.loadSTL(event.data.param);
    break;
    case "loadSTLString":
    this.loadSTLString(event.data.param);
    break;
    case "loadSTLBinary":
    this.loadSTLBinary(event.data.param);
    break;
    case "loadOBJ":
    this.loadOBJ(event.data.param);
    break;
    case "loadOBJString":
    this.loadOBJString(event.data.param);
    break;
    case "loadJSON":
    this.loadJSON(event.data.param);
    break;
    case "loadPLY":
    this.loadPLY(event.data.param);
    break;
    case "loadPLYString":
    this.loadPLYString(event.data.param);
    break;
    case "loadPLYBinary":
    this.loadPLYBinary(event.data.param);
    break;
  }

};

if (typeof(window) === "undefined") {
    onmessage = Thingiloader;
    workerFacadeMessage = postMessage;
    importScripts('binaryReader.js');
} else {
    workerFacadeMessage = WorkerFacade.add(thingiurlbase + "/thingiloader.js", Thingiloader);
}
