/*
 *
 * Galatea Life-Like Behavior Manager:
 *
 * (c)2004 Takuya NISHIMOTO (nishi@hil.t.u-tokyo.ac.jp)
 *
 *  $Id: Agent.java,v 1.2 2007/08/09 02:31:03 nishi Exp $
 */

package galatea.agent;


public class Agent
{
    // 
    private String mask_;
    private String background_;
    private double clock_, clockHead_;
    private double headMoveNoSpeak_, headMoveSpeak_;
    private int agentSpeakState_;

    // bodyTransXXX : only x,y are used
    private Vector3D bodyTrans_;
    private Vector3D bodyTransBase_;
    private double bodyScale_, bodyScaleBase_;

    // bodyRot
    private Vector3D bodyRot_;
    private Vector3D bodyRotBase_;
    private Vector3D bodyRotTargetDelta_;
    private int bodyRotTargetCount_;

    // headRot
    private Vector3D headRot_;
    private Vector3D headRotBase_;
    private Vector3D headRotTargetDelta_;
    private int headRotTargetCount_;

    // eyeRot
    private Vector3D eyeRot_;

    // emotion
    private String expType_, expTypeTarget_;
    private double expLevel_, expLevelBase_, expLevelTarget_, expLevelMax_, expLevelRange_;

    // alpha (transparency)

    // nodding
    private Vector3D headRotNodding_;
    private double noddingRange_;
    private int noddingCount_;
    private int noddingCountMax_;
    private double noddingStartClock_;
    private int noddingType_;

    private void _calcNodding()
    {
        if (noddingCount_ > 0) {
            double amp = (double) noddingCount_ / noddingCountMax_; // Math.sin( Math.PI * noddingCount_ / noddingCountMax_);
            noddingCount_ -= 1;

            double t = clockHead_ - noddingStartClock_;
            switch (noddingType_) {
            case 1:
                // nodding
                double nx = Math.sin(t * 10.0) * noddingRange_ * amp;
                headRotNodding_.set(nx, 0, 0);
                break;
            case 2:
                // refuse
                double nz = Math.sin(t * 10.0) * noddingRange_ * amp;
                headRotNodding_.set(0, 0, nz);
                break;
            default:
                break;
            }
        }
    }


    private void _calcHeadMovement()
    {
        if (headRotTargetCount_ > 0) {
            headRotBase_.add(headRotTargetDelta_);
            headRotTargetCount_ -= 1;
        }

        headRot_.x = headRotBase_.x 
            + Math.cos(clockHead_ * 0.4) * 0.30 
            + Math.cos(clockHead_ * 4.0) * 0.10;
        headRot_.y = headRotBase_.y 
            + Math.cos(clockHead_ * 0.1) * 0.05 
            + Math.cos(clockHead_ * 4.0) * 0.02;
        headRot_.z = headRotBase_.z 
            + Math.cos(clockHead_ * 0.9) * 0.15 
            + Math.cos(clockHead_ * 3.0) * 0.07;

        _calcNodding();
        headRot_.add(headRotNodding_);

        headRot_.applyLimitter();
    }


    private void _calcBodyMovement()
    {
        if (bodyRotTargetCount_ > 0) {
            bodyRotBase_.add(bodyRotTargetDelta_);
            bodyRotTargetCount_ -= 1;
        }

        bodyRot_.x = bodyRotBase_.x + Math.cos(clock_ * 1.6) * 0.20;
        bodyRot_.y = bodyRotBase_.y + Math.cos(clock_ * 1.2) * 0.10;

        bodyTrans_.x = bodyTransBase_.x + Math.cos(clock_ * 2.5) * 0.001;
        bodyTrans_.y = bodyTransBase_.y + Math.cos(clock_ * 1.5) * 0.001;

        bodyScale_ = bodyScaleBase_ + Math.cos(clock_ * 0.2) * 0.005;
    }


    private void _calcEmotionChange()
    {
        if (! expType_.equals(expTypeTarget_) ) {
            if (expLevelBase_ < 10.0) {
                expLevelBase_ = 0;
                expType_ = expTypeTarget_;
            } else {
                expLevelBase_ -= 20.0;
            }
        } else {
            expLevelBase_ += (expLevelTarget_ - expLevelBase_) * 0.05; // 0.5
        }
        expLevel_ = expLevelBase_ - Math.cos(clock_ * 5.0) * expLevelRange_;
        if (expLevel_ > expLevelMax_) { 
            expLevel_ = expLevelMax_; 
        } 
        if (expLevel_ < 0) { 
            expLevel_ = 0; 
        } 
    }


    public Agent(String mask, String speaker, String bg, double scale)
    {
        headMoveNoSpeak_ = 1.0;
        headMoveSpeak_   = 0.9;

        clock_ = 0.0;
        clockHead_ = 0.0;

        mask_ = mask;

        // speaker_ = speaker;
        // ssmspeed_ = 1.0;

        bodyTransBase_ = new Vector3D(0, 0, 0);
        bodyTrans_ = new Vector3D(0, 0, 0);

        bodyScale_ = scale;
        bodyScaleBase_ = scale;

        bodyRot_ = new Vector3D(0, 0, 0);
        bodyRotBase_ = new Vector3D(0, 0, 0);
        bodyRotTargetDelta_ = new Vector3D(0, 0, 0);
        bodyRotTargetCount_ = 0;

        headRot_ = new Vector3D(0, 0, 0);
        headRot_.setLimitter(10, 10, 10);
        headRotBase_ = new Vector3D(0, 0, 0);
        headRotTargetDelta_ = new Vector3D(0, 0, 0);
        headRotTargetCount_ = 0;

        eyeRot_ = new Vector3D(0, 0, 0);
        eyeRot_.setLimitter(20, 10, 20);

        expLevel_ = 0.0;
        expLevelBase_ = 0.0;
        expLevelTarget_ = 0.0;
        expLevelMax_ = 100.0;
        expLevelRange_ = 10.0;
        expType_ = "NEUTRAL";
        expTypeTarget_ = "NEUTRAL";

        agentSpeakState_ = 0;

        noddingRange_ = 0.0;
        noddingCountMax_ = 20;
        noddingCount_ = 0;
        headRotNodding_ = new Vector3D(0, 0, 0);
    }


    public synchronized void calc()
    {
        _calcEmotionChange();
        _calcHeadMovement();
        _calcBodyMovement();
    }


    public synchronized void setHeadMove(double headMoveNoSpeak, double headMoveSpeak)
    {
        headMoveNoSpeak_ = headMoveNoSpeak;
        headMoveSpeak_   = headMoveSpeak;
    }

    public synchronized void addClock(double delta)
    {
        clock_ += delta;
        if (agentSpeakState_ == 2) {
            clockHead_ += delta * headMoveSpeak_;
        } else {
            clockHead_ += delta * headMoveNoSpeak_;
        }
    }

    public synchronized void setAgentSpeakState(int state)
    {
        agentSpeakState_ = state;
    }

    public synchronized void setBackground(String background)
    {
        background_ = background;
    }

    public synchronized String getBackground()
    {
        return background_;
    }

    public synchronized String getMask()
    {
        return mask_;
    }


    public synchronized void startNodding(double level)
    {
        noddingRange_ = level;
        noddingCount_ = noddingCountMax_;
        noddingStartClock_ = clockHead_;
        noddingType_ = 1;
    }


    public synchronized void startRefuse(double level)
    {
        noddingRange_ = level;
        noddingCount_ = noddingCountMax_;
        noddingStartClock_ = clockHead_;
        noddingType_ = 2;
    }


    public synchronized void setEmotionTarget(String emotion, double level)
    {
        expTypeTarget_ = emotion;
        expLevelTarget_ = level;
        expLevelMax_ = level;
    }


    public synchronized void setEmotionTarget(String emotion)
    {
        setEmotionTarget(emotion, 90);
    }


    public synchronized void setEmotionNow(String emotion, double level)
    {
        expType_ = emotion;
        expLevel_ = level;

        expTypeTarget_ = emotion;
        expLevelTarget_ = level;
        expLevelMax_ = level;
    }


    public synchronized void setEmotionNow(String emotion)
    {
        setEmotionNow(emotion, 90);
    }


    public synchronized void setEyeRotNow(double tx, double ty, double tz)
    {
        eyeRot_.set(tx, ty, tz);
        eyeRot_.applyLimitter();
    }

    public synchronized void setBodyRotTarget(double tx, double ty, double tz)
    {
        bodyRotTargetCount_ = 5;
        // TODO: implement Vector3D operator
        bodyRotTargetDelta_.x = (tx - bodyRotBase_.x) / bodyRotTargetCount_;
        bodyRotTargetDelta_.y = (ty - bodyRotBase_.y) / bodyRotTargetCount_;
        headRotTargetDelta_.z = 0.0;
    }

    public synchronized void setBodyRotNow(double tx, double ty, double tz)
    {
        bodyRot_.set(tx, ty, tz);
    }

    public synchronized void setHeadRotTarget(double tx, double ty, double tz)
    {
        headRotTargetCount_ = 3;
        // TODO: implement Vector3D operator
        headRotTargetDelta_.x = (tx - headRotBase_.x) / headRotTargetCount_;
        headRotTargetDelta_.y = (ty - headRotBase_.y) / headRotTargetCount_;
        headRotTargetDelta_.z = (tz - headRotBase_.z) / headRotTargetCount_;
    }

    public synchronized void setHeadRotNow(double tx, double ty, double tz)
    {
        headRot_.set(tx, ty, tz);
    }

    public synchronized void setRotTarget(double tx, double ty, double tz)
    {
        setBodyRotTarget(tx, ty, tz);
        setHeadRotTarget(tx, ty, tz);
        setEyeRotNow(tx, ty, tz);
    }


    public synchronized void setRotNow(double tx, double ty, double tz)
    {
        setBodyRotNow(tx, ty, tz);
        setHeadRotNow(tx, ty, tz);
        setEyeRotNow(tx, ty, tz);
    }


    public synchronized Vector3D getEyeRot()
    {
        return eyeRot_;
    }


    public synchronized Vector3D getHeadRot()
    {
        return headRot_;
    }

    public synchronized Vector3D getBodyRot()
    {
        return bodyRot_;
    }

    public synchronized Vector3D getBodyTrans()
    {
        return bodyTrans_;
    }

    public synchronized double getBodyScale()
    {
        return bodyScale_;
    }
    
    public synchronized String getExpType()
    {
        return expType_;
    }
    
    public synchronized double getExpLevel()
    {
        return expLevel_;
    }


    // for AMMCL.java
    public String getOutputText(String str)
    {
        return str;
    }
    
}
