package jp.sourceforge.acerola3d.a3;

import java.util.*;
import javax.media.j3d.*;
import javax.vecmath.*;

class A3Behavior extends Behavior {
    static long elapsedTime = 33l;
    static Node selected3d_egg = new Selected3D();
    A3Object a3 = null;
    A3BranchGroup topGroup;
    TransformGroup transGroup;
    Transform3D t;
    A3VirtualUniverse universe = null;
    boolean isInterpolate = false;
    boolean autoDirectionControl = false;
    boolean billboardControl = false;
    double nextS = 1.0;
    Quat4d nextQ = new Quat4d(0.0,0.0,0.0,1.0);
    Vector3d nextV = new Vector3d();
    double nowS = 1.0;
    Quat4d nowQ = new Quat4d(0.0,0.0,0.0,1.0);
    Vector3d nowV = new Vector3d();
    boolean needRecalc = true;
    Vector3d nowTrans = new Vector3d();
    BranchGroup selected3d_bg = null;
    TransformGroup selected3d_tg = null;
    double interpolateRatio = 0.1;
    boolean isVisible = true;
    ArrayList<Runnable> runnableQueue = null;
    
    A3Behavior(A3Object a) {
        a3 = a;
        topGroup = new A3BranchGroup();
        topGroup.setCapability(Group.ALLOW_CHILDREN_WRITE);
        topGroup.setCapability(Group.ALLOW_CHILDREN_EXTEND);
        topGroup.setA3(a3);
        t = new Transform3D();
        transGroup = new TransformGroup(t);
        transGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
        transGroup.addChild(this);
        topGroup.addChild(transGroup);
        runnableQueue = new ArrayList<Runnable>();
    }
    void setA3VirtualUniverse(A3VirtualUniverse u) {
        universe = u;
    }
    void setNode(Node n) {
        transGroup.addChild(n);
    }

    //Z軸が上の座標系を使っている場合にtrue、Y軸が上の座標系を使っているか
    //不明な場合はfalseを返すメソッド。
    boolean isZ() {
        if (universe!=null) {
            if (universe.scene.upperDirection==A3Object.UpperDirection.Z) {
                return true;
            } else {
                return false;
            }
        } else {
            return false;
        }
    }

    //現在の回転・位置・拡大率を今すぐオブジェクトに反映させるメソッド
    Quat4d actualQ = new Quat4d();
    static Quat4d fixQ = new Quat4d(1.0*Math.sin(Math.PI/4.0),0.0,0.0,Math.cos(Math.PI/4.0));
    void setNow() {
        //Z軸を上にする座標系でもY軸を上にする座標系用のキャラクタファイルを
        //そのまま使うことができるように、Z軸を上にする座標系の場合に回転の
        //四元数を最後の最後にX軸まわりに90度回転させる。
        if (isZ())
            actualQ.mul(nowQ,fixQ);
        else
            actualQ.set(nowQ);
        double s = isVisible?nowS:0.0;
        t.set(actualQ,nowV,s);
        try {
            transGroup.setTransform(t);
        } catch (BadTransformException e) {
            System.out.println("BadTransformException in A3Behavior.setNow().");
            //e.printStackTrace();
        }
        if (a3 instanceof Action3D) {
            ((Action3D)a3).setSoundLocQuat(nowV,actualQ);
        } else if (a3 instanceof A3Sounds) {
            ((A3Sounds)a3).setSoundLocQuat(nowV,actualQ);
        }
    }
    void init() {
        setNow();
    }
    public void initialize() {
        WakeupOnElapsedTime w = new WakeupOnElapsedTime(elapsedTime);
        wakeupOn(w);
    }
    @SuppressWarnings("unchecked")
    public void processStimulus(Enumeration criteria) {
        long et;
        if (universe==null) {
            et = elapsedTime;
            WakeupOnElapsedTime w = new WakeupOnElapsedTime(elapsedTime);
            wakeupOn(w);
        } else {
            et = universe.elapsedTime;
            WakeupOnBehaviorPost w = null;
            w = new WakeupOnBehaviorPost(universe.getTimerBehavior(),1);
            wakeupOn(w);
        }

        //Runnableの処理
        ArrayList<Runnable> runnableCopy = null;
        synchronized(runnableQueue) {
            runnableCopy = (ArrayList<Runnable>)runnableQueue.clone();
            runnableQueue.clear();
        }
        for (Runnable r:runnableCopy) {
            r.run();
        }

        if (needRecalc==false)
            return;
        double ratio = 1.0-Math.pow(interpolateRatio,((double)et)/1000.0);
        nowTrans.sub(nextV,nowV);
        nowTrans.scale(ratio);
        nowV.add(nowTrans);
        if (autoDirectionControl) {
            autoQuatControl();
        } else if (billboardControl) {
            billboardControl();
        } else {
            nowQ.normalize();
            nowQ.interpolate(nextQ,ratio);
            nowQ.normalize();
        }
        nowS = nowS + ratio*(nextS - nowS);
        setNow();
        if (!isInterpolate) {
            needRecalc = false;
        }
    }

    void autoQuatControl() {
        Vector3d front = new Vector3d(); 
        front.sub(nextV,nowV);
        if (front.length()>universe.scene.cameraNowS/1000.0) {
            Vector3d up = new Vector3d(a3.upperVector);
            if (isZ())
                nowQ.set(Util.frontFacingQuat_A3Z(front,up));
            else
                nowQ.set(Util.frontFacingQuat_A3Y(front,up));
        }
    }
    void billboardControl() {
        Vector3d front = new Vector3d(); 
        front.sub(universe.scene.cameraNowV,nowV);
        if (front.length()>universe.scene.cameraNowS/1000.0) {
            Vector3d up = new Vector3d(a3.upperVector);
            if (isZ())
                nowQ.set(Util.frontFacingQuat_A3Z(front,up));
            else
                nowQ.set(Util.frontFacingQuat_A3Y(front,up));
        }
    }
    void setEnableBehavior(boolean b) {
        isInterpolate = b;
    }
    void setInterpolateRatio(double ir) {
        interpolateRatio = ir;
    }
    void setAutoDirectionControl(boolean b) {
        autoDirectionControl = b;
        if (autoDirectionControl&&billboardControl)
            billboardControl=false;
    }
    void setBillboardControl(boolean b) {
        billboardControl = b;
        if (billboardControl&&autoDirectionControl)
            autoDirectionControl=false;
    }
    void setTransformImmediately(Transform3D t) {
        nowS = t.getScale();
        t.get(nowV);
        t.get(nowQ);
        nextS = nowS;
        nextV.set(nowV);
        nextQ.set(nowQ);
        //setNow();
        needRecalc = true;
    }
    void move(Vector3d v, Quat4d q, double s) {
        if (isInterpolate) {
            nextS = s;
            nextV.set(v);
            nextQ.set(q);
            nextQ.normalize();
        } else {
            nowS = s;
            nowV.set(v);
            nowQ.set(q);
            nextS = s;
            nextV.set(v);
            nextQ.set(q);
        }
        needRecalc = true;
    }
    void moveImmediately(Vector3d v, Quat4d q, double s) {
        nowS = s;
        nowV.set(v);
        nowQ.set(q);
        nextS = s;
        nextV.set(v);
        nextQ.set(q);
        //setNow();
        needRecalc = true;
    }
    void setLoc(Vector3d v) {
        if (isInterpolate) {
            nextV.set(v);
        } else {
            nowV.set(v);
            nextV.set(v);
        }
        needRecalc = true;
    }
    void setLocImmediately(Vector3d v) {
        nowV.set(v);
        nextV.set(v);
        //setNow();
        needRecalc = true;
    }
    void addLoc(Vector3d v) {
        if (isInterpolate) {
            nextV.add(v);
        } else {
            nextV.add(v);
            nowV.set(nextV);
        }
        needRecalc = true;
    }
    void addLocImmediately(Vector3d v) {
        nextV.add(v);
        nowV.set(nextV);
        //setNow();
        needRecalc = true;
    }
    Vector3d getLoc() {
        return new Vector3d(nowV);
    }
    Vector3d getTargetLoc() {
        return new Vector3d(nextV);
    }
    void setQuat(Quat4d q) {
        if (isInterpolate) {
            nextQ.set(q);
        } else {
            nowQ.set(q);
            nextQ.set(q);
        }
        needRecalc = true;
    }
    void setQuatImmediately(Quat4d q) {
        nowQ.set(q);
        nextQ.set(q);
        //setNow();
        needRecalc = true;
    }
    void mulQuat(Quat4d q) {
        if (isInterpolate) {
            nextQ.mul(q);
        } else {
            nextQ.mul(q);
            nowQ.set(nextQ);
        }
        needRecalc = true;
    }
    void mulQuatImmediately(Quat4d q) {
        nextQ.mul(q);
        nowQ.set(nextQ);
        //setNow();
        needRecalc = true;
    }
    Quat4d getQuat() {
        return new Quat4d(nowQ);
    }
    Quat4d getTargetQuat() {
        return new Quat4d(nextQ);
    }
    void setLookAtPoint(Vector3d lookAt) {
        Vector3d front = new Vector3d();
        front.sub(lookAt,nowV);
        Vector3d up = new Vector3d(a3.upperVector);
        Quat4d q = null;
        if (isZ())
            q = Util.frontFacingQuat_A3Z(front,up);
        else
            q = Util.frontFacingQuat_A3Y(front,up);
        nextQ.set(q);
        if (!isInterpolate) {
            nowQ.set(q);
        }
        needRecalc = true;
    }
    void setLookAtPointImmediately(Vector3d lookAt) {
        Vector3d front = new Vector3d();
        front.sub(lookAt,nowV);
        Vector3d up = new Vector3d(a3.upperVector);
        Quat4d q = null;
        if (isZ())
            q = Util.frontFacingQuat_A3Z(front,up);
        else
            q = Util.frontFacingQuat_A3Y(front,up);
        nextQ.set(q);
        nowQ.set(q);
        //setNow();
        needRecalc = true;
    }
    void setScale(double s) {
        if (isInterpolate) {
            nextS = s;
        } else {
            nowS = s;
            nextS = s;
        }
        needRecalc = true;
    }
    void setScaleImmediately(double s) {
        nowS = s;
        nextS = s;
        //setNow();
        needRecalc = true;
    }
    void mulScale(double s) {
        if (isInterpolate) {
            nextS *= s;
        } else {
            nextS *= s;
            nowS = nextS;
        }
        needRecalc = true;
    }
    void mulScaleImmediately(double s) {
        nextS *= s;
        nowS = nextS;
        //setNow();
        needRecalc = true;
    }
    double getScale() {
        return nowS;
    }
    double getTargetScale() {
        return nextS;
    }
    Vector3d getUnitVecX() {
        return Util.trans(nowQ,new Vector3d(1.0,0.0,0.0));
    }
    Vector3d getUnitVecY() {
        return Util.trans(nowQ,new Vector3d(0.0,1.0,0.0));
    }
    Vector3d getUnitVecZ() {
        return Util.trans(nowQ,new Vector3d(0.0,0.0,1.0));
    }
    double getSpeed() {
        if (isInterpolate) {
            if (universe!=null)
                return nowTrans.length()/(universe.elapsedTime/1000.0);
            else
                return nowTrans.length()/(elapsedTime/1000.0);
        } else {
            return 0.0;
        }
    }
    static void setSelected3DMarker(Node n) {
        selected3d_egg = n;
    }
    void setSelected3D(boolean b) {
        if (b&&(selected3d_bg==null)) {
            Node n = selected3d_egg.cloneTree();
            n.setPickable(false);
            selected3d_tg = new TransformGroup();
            selected3d_tg.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
            selected3d_tg.addChild(n);
            selected3d_bg = new BranchGroup();
            selected3d_bg.setCapability(BranchGroup.ALLOW_DETACH);
            selected3d_bg.addChild(selected3d_tg);
            adjustSelected3D();
            topGroup.addChild(selected3d_bg);
        } else if (!b && selected3d_bg!=null) {
            topGroup.removeChild(selected3d_bg);
            selected3d_bg = null;
            selected3d_tg = null;
        }
    }
    void adjustSelected3D() {
        Bounds b = transGroup.getBounds();
        if (b instanceof BoundingSphere) {
            BoundingSphere bs = (BoundingSphere)b;
            Point3d p = new Point3d();
            bs.getCenter(p);
            double r = bs.getRadius();
            Transform3D t = new Transform3D();
            t.setScale(r*nowS);
            p.scale(nowS);
            Vector3d v = new Vector3d();
            v.add(nowV,p);
            t.setTranslation(v);
            selected3d_tg.setTransform(t);
        } else {
            // TODO
        }
    }
    boolean isSelected3D() {
        if (selected3d_bg==null)
            return false;
        else
            return true;
    }
    void setVisible(boolean b) {
        isVisible = b;
        needRecalc = true;
    }
    void addRunnable(Runnable r) {
        synchronized(runnableQueue) {
            runnableQueue.add(r);
        }
    }
}
