/**
 * @author mikael emtinger / http://gomo.se/
 */

THREE.SkinnedMesh = function( geometry, materials ) {

	THREE.Mesh.call( this, geometry, materials );

	// init bones

	this.identityMatrix = new THREE.Matrix4();
	this.bones = [];
	this.boneMatrices = [];

	var b, bone, gbone, p, q, s;

	if ( this.geometry.bones !== undefined ) {

		for ( b = 0; b < this.geometry.bones.length; b++ ) {

			gbone = this.geometry.bones[ b ];

			p = gbone.pos;
			q = gbone.rotq;
			s = gbone.scl;

			bone = this.addBone();

			bone.name = gbone.name;
			bone.position.set( p[0], p[1], p[2] ); 
			bone.quaternion.set( q[0], q[1], q[2], q[3] );
			bone.useQuaternion = true;

			if ( s !== undefined ) {

				bone.scale.set( s[0], s[1], s[2] );

			} else {

				bone.scale.set( 1, 1, 1 );

			}

		}

		for ( b = 0; b < this.bones.length; b++ ) {

			gbone = this.geometry.bones[ b ];
			bone = this.bones[ b ];

			if ( gbone.parent === -1 ) {

				this.addChild( bone );

			} else {

				this.bones[ gbone.parent ].addChild( bone );

			}

		}

		this.boneMatrices = new Float32Array( 16 * this.bones.length );

		this.pose();

	}

};

THREE.SkinnedMesh.prototype = new THREE.Mesh();
THREE.SkinnedMesh.prototype.constructor = THREE.SkinnedMesh;


/*
 * Update
 */

THREE.SkinnedMesh.prototype.update = function ( parentMatrixWorld, forceUpdate, camera ) {

	// visible?

	if ( this.visible ) {

		// update local

		if ( this.matrixAutoUpdate ) {

			forceUpdate |= this.updateMatrix();

		}


		// update global

		if ( forceUpdate || this.matrixWorldNeedsUpdate ) {

			if ( parentMatrixWorld ) {

				this.matrixWorld.multiply( parentMatrixWorld, this.matrix );

			} else {

				this.matrixWorld.copy( this.matrix );

			}

			this.matrixWorldNeedsUpdate = false;
			forceUpdate = true;

		}


		// update children

		var child, i, l = this.children.length;

		for ( i = 0; i < l; i++ ) {

			child = this.children[ i ];

			if ( child instanceof THREE.Bone ) {

				child.update( this.identityMatrix, false, camera );

			} else {

				child.update( this.matrixWorld, forceUpdate, camera );

			}

		}


		// flatten to array

		var b, bl = this.bones.length;
			ba = this.bones;
			bm = this.boneMatrices;

		for ( b = 0; b < bl; b++ ) {

			ba[ b ].skinMatrix.flattenToArrayOffset( bm, b * 16 );

		}

	}

};


/*
 * Add 
 */

THREE.SkinnedMesh.prototype.addBone = function( bone ) {

	if ( bone === undefined ) {

		bone = new THREE.Bone( this );

	}

	this.bones.push( bone );

	return bone;

};

/*
 * Pose
 */

THREE.SkinnedMesh.prototype.pose = function() {

	this.update( undefined, true );

	var bim, bone, boneInverses = [];

	for ( var b = 0; b < this.bones.length; b++ ) {

		bone = this.bones[ b ];

		boneInverses.push( THREE.Matrix4.makeInvert( bone.skinMatrix ) );

		bone.skinMatrix.flattenToArrayOffset( this.boneMatrices, b * 16 );

	}

	// project vertices to local 

	if ( this.geometry.skinVerticesA === undefined ) {

		this.geometry.skinVerticesA = [];
		this.geometry.skinVerticesB = [];

		var orgVertex, vertex;

		for ( var i = 0; i < this.geometry.skinIndices.length; i++ ) {

			orgVertex = this.geometry.vertices[ i ].position;

			var indexA = this.geometry.skinIndices[ i ].x;
			var indexB = this.geometry.skinIndices[ i ].y;

			vertex = new THREE.Vector3( orgVertex.x, orgVertex.y, orgVertex.z );
			this.geometry.skinVerticesA.push( boneInverses[ indexA ].multiplyVector3( vertex ) );

			vertex = new THREE.Vector3( orgVertex.x, orgVertex.y, orgVertex.z );
			this.geometry.skinVerticesB.push( boneInverses[ indexB ].multiplyVector3( vertex ) );

			// todo: add more influences

			// normalize weights

			if ( this.geometry.skinWeights[ i ].x + this.geometry.skinWeights[ i ].y !== 1 ) {

				var len = ( 1.0 - ( this.geometry.skinWeights[ i ].x + this.geometry.skinWeights[ i ].y )) * 0.5;
				this.geometry.skinWeights[ i ].x += len;
				this.geometry.skinWeights[ i ].y += len;

			}

		}

	}

};
