( function () {

	/**
 * Dependencies
 *  - mmd-parser https://github.com/takahirox/mmd-parser
 */

	class MMDExporter {

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

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

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

		/*
   * skeleton -> vpd
   * Returns Shift_JIS encoded Uint8Array. Otherwise return strings.
   */
		parseVpd( 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;
				let a = num.toString();

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

					a += '.';

				}

				a += '000000';
				const index = a.indexOf( '.' );
				const d = a.slice( 0, index );
				const p = a.slice( index + 1, index + 7 );
				return d + '.' + p;

			}

			function toStringsFromArray( array ) {

				const a = [];

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

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

				}

				return a.join( ',' );

			}

			skin.updateMatrixWorld( true );
			const bones = skin.skeleton.bones;
			const bones2 = getBindBones( skin );
			const position = new THREE.Vector3();
			const quaternion = new THREE.Quaternion();
			const quaternion2 = new THREE.Quaternion();
			const matrix = new THREE.Matrix4();
			const 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 ( let i = 0, il = bones.length; i < il; i ++ ) {

				const bone = bones[ i ];
				const 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 );
				const pArray = position.sub( bone2.position ).toArray();
				const 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( '' );
			const lines = array.join( '\n' );
			return outputShiftJis === true ? unicodeToShiftjis( lines ) : lines;

		}

	} // Unicode to Shift_JIS table


	let u2sTable;

	function unicodeToShiftjis( str ) {

		if ( u2sTable === undefined ) {

			const encoder = new MMDParser.CharsetEncoder(); // eslint-disable-line no-undef

			const table = encoder.s2uTable;
			u2sTable = {};
			const keys = Object.keys( table );

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

				let key = keys[ i ];
				const value = table[ key ];
				key = parseInt( key );
				u2sTable[ value ] = key;

			}

		}

		const array = [];

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

			const code = str.charCodeAt( i );
			const 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?
		const poseSkin = skin.clone();
		poseSkin.pose();
		return poseSkin.skeleton.bones;

	}

	THREE.MMDExporter = MMDExporter;

} )();
