define(["three", "util", "random"], function(THREE, Util, Random) {
  var Branch = function(tree) {
    THREE.Object3D.call(this);

    this.tree = tree;

    this.level = 0;
    this.depth = 4;

    this.numChildren = 4;
    this.numChildrenFactor = 0.75;

    this.meshScale = 1.2;
    this.meshScaleFactor = 0.7;

    this.pathLength = 30;
    this.pathLengthFactor = 0.5;

    this.meshCount = 8;
    this.meshCountFactor = 0.9;

    this.mesh = new THREE.Mesh();

    this.meshes = [];
    this.branches = [];

    this.path = new THREE.Line3(
      new THREE.Vector3(0, -30, 0),
      new THREE.Vector3(0, 20, 0)
    );

    this.random = new Random(294);
  }

  Branch.prototype = Object.create(THREE.Object3D.prototype);

  Branch.prototype.generate = function() {
    for(var i = 0; i < this.meshCount; ++i) {
      var mesh = this.mesh.clone();

      var vec = this.path.at(i / (this.meshCount - 1));
      mesh.position.copy(vec);
      mesh.rotation.copy(Util.randomEuler());

      var scale = this.meshScale + (this.random.next(-1, 0) * 0.1);

      mesh.scale.set(scale, scale, scale);

      this.add(mesh);
      this.meshes.push(mesh);
      this.tree.meshes.push(mesh);
      this.tree.originalScales.push(scale);
    }
  };

  Branch.prototype.recurse = function() {
    for(var i = 0; i < this.numChildren; ++i) {
      var branch = new Branch(this.tree);

      branch.level = this.level + 1;
      branch.depth = this.depth;

      branch.numChildren = Math.floor(this.numChildren * this.numChildrenFactor);
      branch.numChildrenFactor = this.numChildrenFactor;

      branch.meshScale = this.meshScale * this.meshScaleFactor;
      branch.meshScaleFactor = this.meshScaleFactor;

      branch.pathLength = this.pathLength * this.pathLengthFactor;
      branch.pathLengthFactor = this.pathLengthFactor;

      branch.meshCount = this.meshCount * this.meshCountFactor;
      branch.meshCountFactor = this.meshCountFactor;

      branch.mesh = this.mesh;
      branch.random = this.random;

      // Generate the new branch path
      var startPoint = this.path.at(this.random.next(0.4, 1.0));

      var randomVec = Util.randomVec3(this.random)
                      .multiplyScalar(branch.pathLength)
                      .multiply(new THREE.Vector3(1, 1, 1));

      randomVec.y = branch.pathLength;
      randomVec.add(startPoint);

      branch.path = new THREE.Line3(startPoint, randomVec);
      branch.generate();

      if(branch.level < branch.depth - 1)
        branch.recurse();

      this.add(branch);
      this.branches.push(branch);
    }
  };

  var MeshTree = function() {
    THREE.Object3D.call(this);
    this.meshes = [];
    this.originalScales = [];
    this.growth = 1;
    this.rootBranch = new Branch(this);
    this.add(this.rootBranch);
  }

  MeshTree.prototype = Object.create(THREE.Object3D.prototype);

  MeshTree.prototype.setGrowth = function(growth) {
    if(growth == this.growth)
      return;

    this.growth = growth;
    
    var s  = 5 / (this.meshes.length - 1);

    for(var i in this.meshes) {
      var n   = i / (this.meshes.length - 1);
      var mesh = this.meshes[i];
      var os = this.originalScales[i];

      var scale = Util.map(growth, n - s, n, 0.0001, os).clamp(0.0001, os);
      
      mesh.scale.set(scale, scale, scale);
    }
  };

  return MeshTree;
})