import { Mesh3D } from '../Mesh3D';
// import { Vector4 } from '../math/Vector4.js';
import { Skeleton3D } from './Skeleton3D';
import { Bone3D } from './Bone3D';
import { Matrix4 } from '../math/Matrix4';
import { Geometry } from '../Geometry';
import { BaseMaterial } from '../materials';

export class SkinnedMesh3D extends Mesh3D {
	public bindMode: "attached" | "detached" = "attached";
	public bindMatrix = new Matrix4();
	public bindMatrixInverse = new Matrix4();
	public skeleton: Skeleton3D;
	constructor(geometry: Geometry, material: BaseMaterial) {
		super(geometry, material)
		this._instanceType = "SkinnedMesh3D";

		var bones = this.initBones();
		var skeleton = new Skeleton3D(bones);

		this.bind(skeleton, this._worldMatrix);

		this.normalizeSkinWeights();
	}

	initBones() {

		var bones: Bone3D[] = [], bone: Bone3D, gbone: Bone3D;
		var i: number, il: number;

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

			// first, create array of 'Bone' objects from geometry data

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

				gbone = this.geometry.bones[i];

				// create new 'Bone' object

				bone = new Bone3D();
				bones.push(bone);

				// apply values

				bone.name = gbone.name;

				bone.position.fromArray(gbone.pos);
				bone.quaternion.fromArray(gbone.rotq);
				if (gbone.scl !== undefined) bone.scale.fromArray(gbone.scl);

			}

			// second, create bone hierarchy

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

				gbone = this.geometry.bones[i];

				if ((gbone.par !== - 1) && (gbone.par !== null) && (bones[gbone.par] !== undefined)) {

					// subsequent bones in the hierarchy

					bones[gbone.par].addChild(bones[i]);

				} else {

					// topmost bone, immediate child of the skinned mesh

					this.addChild(bones[i]);

				}

			}

		}

		// now the bones are part of the scene graph and children of the skinned mesh.
		// let's update the corresponding matrices

		this.updateWorldMatrix();

		return bones;

	}

	bind(skeleton: Skeleton3D, bindMatrix: Matrix4) {

		this.skeleton = skeleton;

		if (bindMatrix === undefined) {

			this.updateWorldMatrix();

			this.skeleton.calculateInverses();

			bindMatrix = this._worldMatrix;

		}

		this.bindMatrix.copy(bindMatrix);
		this.bindMatrixInverse.setInverseOf(bindMatrix);

	}

	pose() {

		this.skeleton.pose();

	}

	normalizeSkinWeights() {
		var scale, i;
		if (this.geometry) {
			var vec = new Vector4();
			var skinWeight = this.geometry._skinWeight;
			var len = skinWeight.length / 4 || 0;
			for (i = 0; i < len; i++) {

				vec.x = skinWeight[i * 4];
				vec.y = skinWeight[i * 4 + 1];
				vec.z = skinWeight[i * 4 + 2];
				vec.w = skinWeight[i * 4 + 3];

				scale = 1.0 / vec.manhattanLength();

				if (scale !== Infinity) {

					vec.multiplyScalar(scale);

				} else {

					vec.set(1, 0, 0, 0); // do something reasonable

				}

				// skinWeight.setXYZW(i, vec.x, vec.y, vec.z, vec.w);
				skinWeight[i * 4] = vec.x;
				skinWeight[i * 4 + 1] = vec.y;
				skinWeight[i * 4 + 2] = vec.z;
				skinWeight[i * 4 + 3] = vec.w;
			}

		}

	}

	updateWorldMatrix() {
		super.updateWorldMatrix()
		if (this.bindMode === 'attached') {
			this.bindMatrixInverse.setInverseOf(this._worldMatrix);
		} else if (this.bindMode === 'detached') {
			this.bindMatrixInverse.setInverseOf(this.bindMatrix);
		} else {

		}
	}
	update() {
		if (!this.visible) return;
		this.skeleton.update();
		super.update();
	}

	clone() {

		return new SkinnedMesh3D(this.geometry, this.material).copy(this);

	}
}

//暂时只有这里用到，就几个方法，简单写
class Vector4 {
	constructor(
		public x: number = 0,
		public y: number = 0,
		public z: number = 0,
		public w = 1
	) { }
	set(x: number, y: number, z: number, w: number) {
		this.x = x;
		this.y = y;
		this.z = z;
		this.w = w;
		return this;
	}

	multiplyScalar(scalar: number) {
		this.x *= scalar;
		this.y *= scalar;
		this.z *= scalar;
		this.w *= scalar;
		return this;
	}
	manhattanLength() {
		return Math.abs(this.x) + Math.abs(this.y) + Math.abs(this.z) + Math.abs(this.w);
	}
}
