/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.jme3.gde.scenecomposer.tools;

import com.jme3.bullet.control.CharacterControl;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.gde.core.undoredo.AbstractUndoableSceneEdit;
import com.jme3.gde.scenecomposer.SceneEditTool;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Quad;
import org.openide.util.lookup.ServiceProvider;

/**
 *
 * @author Nehon
 */
@ServiceProvider(service = MoveManager.class)
public class MoveManager {

    private Vector3f startLoc;
    private Vector3f startWorldLoc;
    private Vector3f lastLoc;
    private Vector3f offset;
    private Node alternativePickTarget = null;
    private Node plane;
    private Spatial spatial;
    protected static final Quaternion XY = new Quaternion().fromAngleAxis(0, new Vector3f(1, 0, 0));
    protected static final Quaternion YZ = new Quaternion().fromAngleAxis(-FastMath.PI / 2, new Vector3f(0, 1, 0));
    protected static final Quaternion XZ = new Quaternion().fromAngleAxis(FastMath.PI / 2, new Vector3f(1, 0, 0));
    //temp vars 
    private Quaternion rot = new Quaternion();
    private Vector3f newPos = new Vector3f();

    public MoveManager() {
        float size = 1000;
        Geometry g = new Geometry("plane", new Quad(size, size));
        g.setLocalTranslation(-size / 2, -size / 2, 0);
        plane = new Node();
        plane.attachChild(g);
    }

    public Vector3f getOffset() {
        return offset;
    }

    public void reset() {
        offset = null;
        startLoc = null;
        startWorldLoc = null;
        lastLoc = null;
        spatial = null;
        alternativePickTarget = null;
    }

    public void initiateMove(Spatial selectedSpatial, Quaternion planeRotation, boolean local) {
        spatial = selectedSpatial;
        startLoc = selectedSpatial.getLocalTranslation().clone();
        startWorldLoc = selectedSpatial.getWorldTranslation().clone();
        if (local) {
            rot.set(selectedSpatial.getWorldRotation());
            plane.setLocalRotation(rot.multLocal(planeRotation));
        } else {
            rot.set(planeRotation);
        }

        plane.setLocalRotation(rot);
        plane.setLocalTranslation(startWorldLoc);

    }

    public void updatePlaneRotation(Quaternion planeRotation) {
        plane.setLocalRotation(rot);
    }

    public boolean move(Camera camera, Vector2f screenCoord) {
        return move(camera, screenCoord, Vector3f.UNIT_XYZ, false);
    }

    public boolean move(Camera camera, Vector2f screenCoord, Vector3f constraintAxis, boolean gridSnap) {
        Node toPick = alternativePickTarget == null ? plane : alternativePickTarget;

        Vector3f planeHit = SceneEditTool.pickWorldLocation(camera, screenCoord, toPick, alternativePickTarget == null ? null : spatial);
        if (planeHit == null) {
            return false;
        }

        Spatial parent = spatial.getParent();
        //we are moving the root node, there is a slight chance that something went wrong.
        if (parent == null) {
            return false;
        }

        //offset in world space
        if (offset == null) {
            offset = planeHit.subtract(spatial.getWorldTranslation()); // get the offset when we start so it doesn't jump
        }

        newPos.set(planeHit).subtractLocal(offset);

        //constraining the translation with the contraintAxis.
        Vector3f tmp = startWorldLoc.mult(Vector3f.UNIT_XYZ.subtract(constraintAxis));
        newPos.multLocal(constraintAxis).addLocal(tmp);
        worldToLocalMove(gridSnap);
        return true;
    }

    private void worldToLocalMove(boolean gridSnap) {
        //snap to grid (grid is assumed 1 WU per cell)
        if (gridSnap) {
            newPos.set(Math.round(newPos.x), Math.round(newPos.y), Math.round(newPos.z));
        }

        //computing the inverse world transform to get the new localtranslation        
        newPos.subtractLocal(spatial.getParent().getWorldTranslation());
        newPos = spatial.getParent().getWorldRotation().inverse().normalizeLocal().multLocal(newPos);
       
        lastLoc = newPos;
        spatial.setLocalTranslation(newPos);

        RigidBodyControl control = spatial.getControl(RigidBodyControl.class);
        if (control != null) {
            control.setPhysicsLocation(spatial.getWorldTranslation());
        }
        CharacterControl character = spatial.getControl(CharacterControl.class);
        if (character != null) {
            character.setPhysicsLocation(spatial.getWorldTranslation());
        }
    }

    public boolean moveAcross(Vector3f constraintAxis, float value, boolean gridSnap) {
        newPos.set(startWorldLoc).addLocal(constraintAxis.mult(value));
        Spatial parent = spatial.getParent();
        //we are moving the root node, there is a slight chance that something went wrong.
        if (parent == null) {
            return false;
        }
        worldToLocalMove(gridSnap);
        return true;
    }

    public MoveUndo makeUndo() {
        return new MoveUndo(spatial, startLoc, lastLoc);
    }

    public void setAlternativePickTarget(Node alternativePickTarget) {
        this.alternativePickTarget = alternativePickTarget;
    }

    protected class MoveUndo extends AbstractUndoableSceneEdit {

        private Spatial spatial;
        private Vector3f before = new Vector3f(), after = new Vector3f();

        MoveUndo(Spatial spatial, Vector3f before, Vector3f after) {
            this.spatial = spatial;
            this.before.set(before);
            if (after != null) {
                this.after.set(after);
            }
        }

        @Override
        public void sceneUndo() {
            spatial.setLocalTranslation(before);
            RigidBodyControl control = spatial.getControl(RigidBodyControl.class);
            if (control != null) {
                control.setPhysicsLocation(spatial.getWorldTranslation());
            }
            CharacterControl character = spatial.getControl(CharacterControl.class);
            if (character != null) {
                character.setPhysicsLocation(spatial.getWorldTranslation());
            }
            //     toolController.selectedSpatialTransformed();
        }

        @Override
        public void sceneRedo() {
            spatial.setLocalTranslation(after);
            RigidBodyControl control = spatial.getControl(RigidBodyControl.class);
            if (control != null) {
                control.setPhysicsLocation(spatial.getWorldTranslation());
            }
            CharacterControl character = spatial.getControl(CharacterControl.class);
            if (character != null) {
                character.setPhysicsLocation(spatial.getWorldTranslation());
            }
            //toolController.selectedSpatialTransformed();
        }

        public void setAfter(Vector3f after) {
            this.after.set(after);
        }
    }
}
