/**
 * @author Inscrutabilis / mailto:iinscrutabilis@gmail.com
 */

/*
 * Generates a geodesic sphere geometry
 * By default, it is 1x1x1 octahedron, as a first approximation to sphere
 * It will return more sphere-like object if you specify iterations > 0
 * But please keep in mind that it generates (4 ^ iterations) * 8 faces
 * Radius argument overrides default sphere radius (1)
 */
var GeodesicSphere = function(iterations, radius) {
  iterations = iterations || 0;
  radius = radius || 1;
  THREE.Geometry.call(this);
  
  // initial 6 vertices of octahedron
  this.vertices.push(new THREE.Vertex((new THREE.Vector3(0, 1, 0)).normalize().multiplyScalar(radius))); // #0
  this.vertices.push(new THREE.Vertex((new THREE.Vector3(-1, 0, 1)).normalize().multiplyScalar(radius))); // #1
  this.vertices.push(new THREE.Vertex((new THREE.Vector3(1, 0, 1)).normalize().multiplyScalar(radius))); // #2
  this.vertices.push(new THREE.Vertex((new THREE.Vector3(1, 0, -1)).normalize().multiplyScalar(radius))); // #3
  this.vertices.push(new THREE.Vertex((new THREE.Vector3(-1, 0, -1)).normalize().multiplyScalar(radius))); // #4
  this.vertices.push(new THREE.Vertex((new THREE.Vector3(0, -1, 0)).normalize().multiplyScalar(radius))); // #5
  
  // and these duplicates we need to correctly map texture
  /*this.vertices.push(new THREE.Vertex((new THREE.Vector3(0, 0, 1)).normalize().multiplyScalar(radius))); // #6 (#0 duplicate)
  this.vertices.push(new THREE.Vertex((new THREE.Vector3(0, 0, 1)).normalize().multiplyScalar(radius))); // #7 (#0 duplicate)
  this.vertices.push(new THREE.Vertex((new THREE.Vector3(0, 0, 1)).normalize().multiplyScalar(radius))); // #8 (#0 duplicate)
  this.vertices.push(new THREE.Vertex((new THREE.Vector3(-1, 1, 0)).normalize().multiplyScalar(radius))); // #9 (#1 duplicate)
  this.vertices.push(new THREE.Vertex((new THREE.Vector3(0, 0, -1)).normalize().multiplyScalar(radius))); // #10 (#5 duplicate)
  this.vertices.push(new THREE.Vertex((new THREE.Vector3(0, 0, -1)).normalize().multiplyScalar(radius))); // #11 (#5 duplicate)
  this.vertices.push(new THREE.Vertex((new THREE.Vector3(0, 0, -1)).normalize().multiplyScalar(radius))); // #12 (#5 duplicate)*/
  
  // now init UVs for the above; we can't store them yet
  var tuvs = [];
  tuvs.push(new THREE.UV(0.125, 0)); // #0
  tuvs.push(new THREE.UV(0, 0.5)); // #1
  tuvs.push(new THREE.UV(0.25, 0.5)); // #2
  tuvs.push(new THREE.UV(0.5, 0.5)); // #3
  tuvs.push(new THREE.UV(0.75, 0.5)); // #4
  tuvs.push(new THREE.UV(0.125, 1)); // #5
  tuvs.push(new THREE.UV(0.375, 0)); // #6
  tuvs.push(new THREE.UV(0.625, 0)); // #7
  tuvs.push(new THREE.UV(0.875, 0)); // #8
  tuvs.push(new THREE.UV(1, 0.5)); // #9
  tuvs.push(new THREE.UV(0.375, 1)); // #10
  tuvs.push(new THREE.UV(0.625, 1)); // #11
  tuvs.push(new THREE.UV(0.875, 1)); // #12
  
  // FIXME: if someone comes up with idea on how to reuse all of the generated vertices, implement it ASAP
  // FIXME: due to nature of faces, we need to pass texture UVs down to the last step. Absolutely logical, but looks non-elegant
  // FIXME: it is imperfect universe
  var faceGen = function faceGenerator(g, ia, ib, ic, ta, tb, tc, iterations) {
    if (iterations <= 0) {
      // this is the last iteration -- just save face and its UVs
      g.faces.push(new THREE.Face3(ia, ib, ic));
      g.uvs.push([ta, tb, tc]);
    } else {
      var va = g.vertices[ia];
      var vb = g.vertices[ib];
      var vc = g.vertices[ic];
      
      // calculate midpoints
      var pab = new THREE.Vector3((va.position.x + vb.position.x) / 2, (va.position.y + vb.position.y) / 2, (va.position.z + vb.position.z) / 2);
      var pbc = new THREE.Vector3((vb.position.x + vc.position.x) / 2, (vb.position.y + vc.position.y) / 2, (vb.position.z + vc.position.z) / 2);
      var pca = new THREE.Vector3((vc.position.x + va.position.x) / 2, (vc.position.y + va.position.y) / 2, (vc.position.z + va.position.z) / 2);
      // normalize them
      pab = pab.normalize().multiplyScalar(radius);
      pbc = pbc.normalize().multiplyScalar(radius);
      pca = pca.normalize().multiplyScalar(radius);
      
      // and their texture coordinates
      var tab = new THREE.UV((ta.u + tb.u) / 2, (ta.v + tb.v) / 2);
      var tbc = new THREE.UV((tb.u + tc.u) / 2, (tb.v + tc.v) / 2);
      var tca = new THREE.UV((tc.u + ta.u) / 2, (tc.v + ta.v) / 2);
      
      // save vertices array length for later use
      var vertArrayLength = g.vertices.length;
      
      // now create vertices and store them
      g.vertices.push(new THREE.Vertex(pab));
      g.vertices.push(new THREE.Vertex(pbc));
      g.vertices.push(new THREE.Vertex(pca));
      
      // and we know their indices
      var iab = vertArrayLength;
      var ibc = vertArrayLength + 1;
      var ica = vertArrayLength + 2;
      
      faceGenerator(g, ica, iab, ia, tca, tab, ta, iterations - 1);
      faceGenerator(g, ibc, ib, iab, tbc, tb, tab, iterations - 1);
      faceGenerator(g, ic, ibc, ica, tc, tbc, tca, iterations - 1);
      faceGenerator(g, ica, ibc, iab, tca, tbc, tab, iterations - 1);
    }
  }
  
  // finally! Do face generation for octahedron planes
  faceGen(this, 2, 1, 0, tuvs[2], tuvs[1], tuvs[0], iterations);
  faceGen(this, 3, 2, 0, tuvs[3], tuvs[2], tuvs[6], iterations);
  faceGen(this, 4, 3, 0, tuvs[4], tuvs[3], tuvs[7], iterations);
  faceGen(this, 1, 4, 0, tuvs[9], tuvs[4], tuvs[8], iterations);
  faceGen(this, 1, 2, 5, tuvs[1], tuvs[2], tuvs[5], iterations);
  faceGen(this, 2, 3, 5, tuvs[2], tuvs[3], tuvs[10], iterations);
  faceGen(this, 3, 4, 5, tuvs[3], tuvs[4], tuvs[11], iterations);
  faceGen(this, 4, 1, 5, tuvs[4], tuvs[9], tuvs[12], iterations);
  
  this.computeCentroids();
	this.computeFaceNormals();
	this.sortFacesByMaterial();
}

GeodesicSphere.prototype = new THREE.Geometry();
GeodesicSphere.prototype.constructor = GeodesicSphere;