import { math } from '../../core/math/math.js';
import { Color } from '../../core/math/color.js';
import { Quat } from '../../core/math/quat.js';
import { Vec3 } from '../../core/math/vec3.js';
import { Ray } from '../../core/shape/ray.js';
import { Plane } from '../../core/shape/plane.js';
import { PROJECTION_PERSPECTIVE } from '../../scene/constants.js';
import { COLOR_GRAY, COLOR_YELLOW, COLOR_BLUE, COLOR_GREEN, COLOR_RED, color4from3 } from './color.js';
import { Gizmo } from './gizmo.js';

const v1 = new Vec3();
const v2 = new Vec3();
const point = new Vec3();
const ray = new Ray();
const plane = new Plane();
const color = new Color();
const AXES = [
		'x',
		'y',
		'z'
];
class TransformGizmo extends Gizmo {
		static{
				this.EVENT_TRANSFORMSTART = 'transform:start';
		}
		static{
				this.EVENT_TRANSFORMMOVE = 'transform:move';
		}
		static{
				this.EVENT_TRANSFORMEND = 'transform:end';
		}
		constructor(camera, layer, name = 'gizmo:transform'){
				super(camera, layer, name), this._theme = {
						shapeBase: {
								x: color4from3(COLOR_RED, 0.6),
								y: color4from3(COLOR_GREEN, 0.6),
								z: color4from3(COLOR_BLUE, 0.6),
								xyz: color4from3(Color.WHITE, 0.6),
								f: color4from3(Color.WHITE, 0.6)
						},
						shapeHover: {
								x: COLOR_RED.clone(),
								y: COLOR_GREEN.clone(),
								z: COLOR_BLUE.clone(),
								xyz: Color.WHITE.clone(),
								f: COLOR_YELLOW.clone()
						},
						guideBase: {
								x: COLOR_RED.clone(),
								y: COLOR_GREEN.clone(),
								z: COLOR_BLUE.clone(),
								f: COLOR_YELLOW.clone()
						},
						guideOcclusion: 0.8,
						disabled: COLOR_GRAY.clone()
				}, this._rootStartPos = new Vec3(), this._rootStartRot = new Quat(), this._shapes = {}, this._shapeMap = new Map(), this._hovering = new Set(), this._hoverAxis = '', this._hoverIsPlane = false, this._selectedAxis = '', this._selectedIsPlane = false, this._selectionStartPoint = new Vec3(), this._snap = false, this.snapIncrement = 1, this.dragMode = 'selected';
				this.on(Gizmo.EVENT_POINTERDOWN, (x, y, meshInstance)=>{
						const shape = this._shapeMap.get(meshInstance);
						if (shape?.disabled) {
								return;
						}
						if (this._dragging) {
								return;
						}
						if (!meshInstance) {
								return;
						}
						this._hoverAxis = '';
						this._hoverIsPlane = false;
						this._selectedAxis = this._getAxis(meshInstance);
						this._selectedIsPlane = this._getIsPlane(meshInstance);
						this._rootStartPos.copy(this.root.getLocalPosition());
						this._rootStartRot.copy(this.root.getRotation());
						const point = this._screenToPoint(x, y);
						this._selectionStartPoint.copy(point);
						this.fire(TransformGizmo.EVENT_TRANSFORMSTART, point, x, y);
				});
				this.on(Gizmo.EVENT_POINTERMOVE, (x, y, meshInstance)=>{
						const shape = this._shapeMap.get(meshInstance);
						if (shape?.disabled) {
								return;
						}
						this._hover(meshInstance);
						if (!this._dragging) {
								return;
						}
						const point = this._screenToPoint(x, y);
						this.fire(TransformGizmo.EVENT_TRANSFORMMOVE, point, x, y);
				});
				this.on(Gizmo.EVENT_POINTERUP, (_x, _y, meshInstance)=>{
						this._hover(meshInstance);
						if (!this._dragging) {
								return;
						}
						if (meshInstance) {
								this._hoverAxis = this._selectedAxis;
								this._hoverIsPlane = this._selectedIsPlane;
						}
						this._selectedAxis = '';
						this._selectedIsPlane = false;
						this.fire(TransformGizmo.EVENT_TRANSFORMEND);
				});
				this.on(Gizmo.EVENT_NODESDETACH, ()=>{
						this.snap = false;
						this._hoverAxis = '';
						this._hoverIsPlane = false;
						this._hover();
						this.fire(Gizmo.EVENT_POINTERUP);
				});
		}
		set snap(value) {
				this._snap = this.enabled && value;
		}
		get snap() {
				return this._snap;
		}
		get theme() {
				return this._theme;
		}
		set xAxisColor(value) {
				this.setTheme({
						shapeBase: {
								x: value
						},
						shapeHover: {
								x: color4from3(value, this.colorAlpha)
						},
						guideBase: {
								x: value
						},
						guideOcclusion: 1
				});
		}
		get xAxisColor() {
				return this._theme.shapeBase.x;
		}
		set yAxisColor(value) {
				this.setTheme({
						shapeBase: {
								y: value
						},
						shapeHover: {
								y: color4from3(value, this.colorAlpha)
						},
						guideBase: {
								y: value
						},
						guideOcclusion: 1
				});
		}
		get yAxisColor() {
				return this._theme.shapeBase.y;
		}
		set zAxisColor(value) {
				this.setTheme({
						shapeBase: {
								z: value
						},
						shapeHover: {
								z: color4from3(value, this.colorAlpha)
						},
						guideBase: {
								z: value
						},
						guideOcclusion: 1
				});
		}
		get zAxisColor() {
				return this._theme.shapeBase.z;
		}
		set colorAlpha(value) {
				this.setTheme({
						shapeHover: {
								x: color4from3(this._theme.shapeHover.x, value),
								y: color4from3(this._theme.shapeHover.y, value),
								z: color4from3(this._theme.shapeHover.z, value),
								xyz: color4from3(this._theme.shapeHover.xyz, value),
								f: color4from3(this._theme.shapeHover.f, value)
						}
				});
		}
		get colorAlpha() {
				return (this._theme.shapeHover.x.a + this._theme.shapeHover.y.a + this._theme.shapeHover.z.a + this._theme.shapeHover.xyz.a + this._theme.shapeHover.f.a) / 5;
		}
		get _dragging() {
				return !this._hoverAxis && !!this._selectedAxis;
		}
		_getAxis(meshInstance) {
				if (!meshInstance) {
						return '';
				}
				return meshInstance.node.name.split(':')[1];
		}
		_getIsPlane(meshInstance) {
				if (!meshInstance) {
						return false;
				}
				return meshInstance.node.name.indexOf('plane') !== -1;
		}
		_hover(meshInstance) {
				if (this._dragging) {
						return;
				}
				const remove = new Set(this._hovering);
				let changed = false;
				const add = (axis)=>{
						if (remove.has(axis)) {
								remove.delete(axis);
						} else {
								this._hovering.add(axis);
								this._shapes[axis]?.hover(true);
								changed = true;
						}
				};
				this._hoverAxis = this._getAxis(meshInstance);
				this._hoverIsPlane = this._getIsPlane(meshInstance);
				if (this._hoverAxis) {
						if (this._hoverAxis === 'xyz') {
								add('x');
								add('y');
								add('z');
								add('xyz');
						} else if (this._hoverIsPlane) {
								switch(this._hoverAxis){
										case 'x':
												add('y');
												add('z');
												add('yz');
												break;
										case 'y':
												add('x');
												add('z');
												add('xz');
												break;
										case 'z':
												add('x');
												add('y');
												add('xy');
												break;
								}
						} else {
								add(this._hoverAxis);
						}
				}
				for (const axis of remove){
						this._hovering.delete(axis);
						this._shapes[axis]?.hover(false);
						changed = true;
				}
				if (changed) {
						this.fire(Gizmo.EVENT_RENDERUPDATE);
				}
		}
		_createRay(mouseWPos) {
				if (this._camera.projection === PROJECTION_PERSPECTIVE) {
						ray.origin.copy(this._camera.entity.getPosition());
						ray.direction.sub2(mouseWPos, ray.origin).normalize();
						return ray;
				}
				const orthoDepth = this._camera.farClip - this._camera.nearClip;
				ray.origin.sub2(mouseWPos, v1.copy(this._camera.entity.forward).mulScalar(orthoDepth));
				ray.direction.copy(this._camera.entity.forward);
				return ray;
		}
		_createPlane(axis, isFacing, isLine) {
				const facingDir = v1.copy(this.facingDir);
				const normal = plane.normal.set(0, 0, 0);
				if (isFacing) {
						normal.copy(this._camera.entity.forward).mulScalar(-1);
				} else {
						normal[axis] = 1;
						this._rootStartRot.transformVector(normal, normal);
						if (isLine) {
								v2.cross(normal, facingDir).normalize();
								normal.cross(v2, normal).normalize();
						}
				}
				return plane.setFromPointNormal(this._rootStartPos, normal);
		}
		_dirFromAxis(axis, dir) {
				if (axis === 'f') {
						dir.copy(this._camera.entity.forward).mulScalar(-1);
				} else {
						dir.set(0, 0, 0);
						dir[axis] = 1;
				}
				return dir;
		}
		_projectToAxis(point, axis) {
				v1.set(0, 0, 0);
				v1[axis] = 1;
				point.copy(v1.mulScalar(v1.dot(point)));
				const v = point[axis];
				point.set(0, 0, 0);
				point[axis] = v;
		}
		_screenToPoint(x, y, isFacing = false, isLine = false) {
				const mouseWPos = this._camera.screenToWorld(x, y, 1);
				const axis = this._selectedAxis;
				const ray = this._createRay(mouseWPos);
				const plane = this._createPlane(axis, isFacing, isLine);
				if (!plane.intersectsRay(ray, point)) {
						return point;
				}
				return point;
		}
		_drawGuideLines(pos, rot, activeAxis, activeIsPlane) {
				for (const axis of AXES){
						if (activeAxis === 'xyz') {
								this._drawSpanLine(pos, rot, axis);
								continue;
						}
						if (activeIsPlane) {
								if (axis !== activeAxis) {
										this._drawSpanLine(pos, rot, axis);
								}
						} else {
								if (axis === activeAxis) {
										this._drawSpanLine(pos, rot, axis);
								}
						}
				}
		}
		_drawSpanLine(pos, rot, axis) {
				const dir = this._dirFromAxis(axis, v1);
				const base = this._theme.guideBase[axis];
				const from = v1.copy(dir).mulScalar(this._camera.farClip - this._camera.nearClip);
				const to = v2.copy(from).mulScalar(-1);
				rot.transformVector(from, from).add(pos);
				rot.transformVector(to, to).add(pos);
				if (this._theme.guideOcclusion < 1) {
						const occluded = color.copy(base);
						occluded.a *= 1 - this._theme.guideOcclusion;
						this._app.drawLine(from, to, occluded, false, this._layer);
				}
				if (base.a !== 0) {
						this._app.drawLine(from, to, base, true);
				}
		}
		_createTransform() {
				for(const key in this._shapes){
						const shape = this._shapes[key];
						this.root.addChild(shape.entity);
						this.intersectShapes.push(shape);
						for(let i = 0; i < shape.meshInstances.length; i++){
								this._shapeMap.set(shape.meshInstances[i], shape);
						}
				}
		}
		enableShape(shapeAxis, enabled) {
				if (shapeAxis === 'face') {
						shapeAxis = 'f';
				}
				const shape = this._shapes[shapeAxis];
				if (!shape) {
						return;
				}
				shape.disabled = !enabled;
		}
		isShapeEnabled(shapeAxis) {
				if (shapeAxis === 'face') {
						shapeAxis = 'f';
				}
				const shape = this._shapes[shapeAxis];
				if (!shape) {
						return false;
				}
				return !shape.disabled;
		}
		setTheme(partial) {
				const theme = {
						...this._theme,
						...partial
				};
				if (typeof theme !== 'object' || typeof theme.shapeBase !== 'object' || typeof theme.shapeHover !== 'object' || typeof theme.guideBase !== 'object') {
						return;
				}
				for(const axis in theme.shapeBase){
						if (theme.shapeBase[axis] instanceof Color) {
								this._theme.shapeBase[axis].copy(theme.shapeBase[axis]);
						}
				}
				for(const axis in theme.shapeHover){
						if (theme.shapeHover[axis] instanceof Color) {
								this._theme.shapeHover[axis].copy(theme.shapeHover[axis]);
						}
				}
				for(const axis in theme.guideBase){
						if (theme.guideBase[axis] instanceof Color) {
								this._theme.guideBase[axis].copy(theme.guideBase[axis]);
						}
				}
				if (typeof theme.guideOcclusion === 'number') {
						this._theme.guideOcclusion = math.clamp(theme.guideOcclusion, 0, 1);
				}
				if (theme.disabled instanceof Color) {
						this._theme.disabled.copy(theme.disabled);
				}
				for(const name in this._shapes){
						this._shapes[name].hover(!!this._hoverAxis);
				}
		}
		prerender() {
				super.prerender();
				if (!this.enabled) {
						return;
				}
				const gizmoPos = this.root.getLocalPosition();
				const gizmoRot = this.root.getRotation();
				const activeAxis = this._hoverAxis || this._selectedAxis;
				const activeIsPlane = this._hoverIsPlane || this._selectedIsPlane;
				this._drawGuideLines(gizmoPos, gizmoRot, activeAxis, activeIsPlane);
		}
		destroy() {
				super.destroy();
				for(const key in this._shapes){
						this._shapes[key].destroy();
				}
		}
}

export { TransformGizmo };
