/**
 * @author takahiro / http://github.com/takahirox
 *
 * Dependencies
 *  - mmd-parser https://github.com/takahirox/mmd-parser
 */

import {
	Matrix4,
	Quaternion,
	Vector3
} from "../../../build/three.module.js";
import { MMDParser } from "../libs/mmdparser.module.js";

var MMDExporter = function () {

	// Unicode to Shift_JIS table
	var u2sTable;

	function unicodeToShiftjis( str ) {

		if ( u2sTable === undefined ) {

			var encoder = new MMDParser.CharsetEncoder();
			var table = encoder.s2uTable;
			u2sTable = {};

			var keys = Object.keys( table );

			for ( var i = 0, il = keys.length; i < il; i ++ ) {

				var key = keys[ i ];

				var value = table[ key ];
				key = parseInt( key );

				u2sTable[ value ] = key;

			}

		}

		var array = [];

		for ( var i = 0, il = str.length; i < il; i ++ ) {

			var code = str.charCodeAt( i );

			var value = u2sTable[ code ];

			if ( value === undefined ) {

				throw 'cannot convert charcode 0x' + code.toString( 16 );

			} else if ( value > 0xff ) {

				array.push( ( value >> 8 ) & 0xff );
				array.push( value & 0xff );

			} else {

				array.push( value & 0xff );

			}

		}

		return new Uint8Array( array );

	}

	function getBindBones( skin ) {

		// any more efficient ways?
		var poseSkin = skin.clone();
		poseSkin.pose();
		return poseSkin.skeleton.bones;

	}

	/* TODO: implement
	// mesh -> pmd
	this.parsePmd = function ( object ) {

	};
	*/

	/* TODO: implement
	// mesh -> pmx
	this.parsePmx = function ( object ) {

	};
	*/

	/*
	 * skeleton -> vpd
	 * Returns Shift_JIS encoded Uint8Array. Otherwise return strings.
	 */
	this.parseVpd = function ( skin, outputShiftJis, useOriginalBones ) {

		if ( skin.isSkinnedMesh !== true ) {

			console.warn( 'THREE.MMDExporter: parseVpd() requires SkinnedMesh instance.' );
			return null;

		}

		function toStringsFromNumber( num ) {

			if ( Math.abs( num ) < 1e-6 ) num = 0;

			var a = num.toString();

			if ( a.indexOf( '.' ) === - 1 ) {

				a += '.';

			}

			a += '000000';

			var index = a.indexOf( '.' );

			var d = a.slice( 0, index );
			var p = a.slice( index + 1, index + 7 );

			return d + '.' + p;

		}

		function toStringsFromArray( array ) {

			var a = [];

			for ( var i = 0, il = array.length; i < il; i ++ ) {

				a.push( toStringsFromNumber( array[ i ] ) );

			}

			return a.join( ',' );

		}

		skin.updateMatrixWorld( true );

		var bones = skin.skeleton.bones;
		var bones2 = getBindBones( skin );

		var position = new Vector3();
		var quaternion = new Quaternion();
		var quaternion2 = new Quaternion();
		var matrix = new Matrix4();

		var array = [];
		array.push( 'Vocaloid Pose Data file' );
		array.push( '' );
		array.push( ( skin.name !== '' ? skin.name.replace( /\s/g, '_' ) : 'skin' ) + '.osm;' );
		array.push( bones.length + ';' );
		array.push( '' );

		for ( var i = 0, il = bones.length; i < il; i ++ ) {

			var bone = bones[ i ];
			var bone2 = bones2[ i ];

			/*
			 * use the bone matrix saved before solving IK.
			 * see CCDIKSolver for the detail.
			 */
			if ( useOriginalBones === true &&
				bone.userData.ik !== undefined &&
				bone.userData.ik.originalMatrix !== undefined ) {

				matrix.fromArray( bone.userData.ik.originalMatrix );

			} else {

				matrix.copy( bone.matrix );

			}

			position.setFromMatrixPosition( matrix );
			quaternion.setFromRotationMatrix( matrix );

			var pArray = position.sub( bone2.position ).toArray();
			var qArray = quaternion2.copy( bone2.quaternion ).conjugate().multiply( quaternion ).toArray();

			// right to left
			pArray[ 2 ] = - pArray[ 2 ];
			qArray[ 0 ] = - qArray[ 0 ];
			qArray[ 1 ] = - qArray[ 1 ];

			array.push( 'Bone' + i + '{' + bone.name );
			array.push( '  ' + toStringsFromArray( pArray ) + ';' );
			array.push( '  ' + toStringsFromArray( qArray ) + ';' );
			array.push( '}' );
			array.push( '' );

		}

		array.push( '' );

		var lines = array.join( '\n' );

		return ( outputShiftJis === true ) ? unicodeToShiftjis( lines ) : lines;

	};

	/* TODO: implement
	// animation + skeleton -> vmd
	this.parseVmd = function ( object ) {

	};
	*/

};

export { MMDExporter };
