/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 *
 * Galatea Behavior Manager:
 *
 * (c)2004 Takuya NISHIMOTO (nishi@hil.t.u-tokyo.ac.jp)
 *
 *  $Id: BehaviorManager.java,v 1.6 2008/02/13 03:47:26 nishi Exp $
 */

package galatea.agent;

import galatea.util.AMBuffer;
import galatea.util.Getopt;
import galatea.util.HashArray;
import galatea.util.Property;
import galatea.util.Util;

import java.io.File;
import java.io.FileInputStream;
import java.nio.charset.Charset;
import java.text.DecimalFormat;
import java.util.List;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class BehaviorManager
{
    java.util.Random random_;
    private Pattern pattern1_, pattern2_, pattern3_, pattern4_;

    private AMBuffer ambuf_;
    private HashArray agents_; // map String => Agent

    private long prevTime_;

    private boolean isFsmAlive_;
    private boolean useAutoMove_;
    private String currAgent_;

    private boolean eventViewerMode_ = false;
    private boolean debugMode_ = false;


    private Vector3D prevEyeRot_ = new Vector3D(0, 0, 0);
    private Vector3D prevHeadRot_ = new Vector3D(0, 0, 0);
    private Vector3D prevBodyRot_ = new Vector3D(0, 0, 0);
    private Vector3D prevBodyTrans_ = new Vector3D(0, 0, 0);
    private Agent prevAgent_ = null;
    private double prevBodyScale_ = 0.0;
    private String prevExpType_ = "NEUTRAL";
    private double prevExpLevel_ = 0.0;

    private DecimalFormat formatter_;

    public BehaviorManager()
    {
        ambuf_ = new AMBuffer(Util.getSystemDefaultCharset()); 

        pattern1_ = Pattern.compile("^From @(\\S+) (\\S+) (\\S+) = (.*)$");
        pattern2_ = Pattern.compile("^(\\S+) (\\S+)$");
        pattern3_ = Pattern.compile("^(\\S+) (\\S+) (\\S+)$");
        pattern4_ = Pattern.compile("^(\\S+) (\\S+) = (.*)$");

        random_ = new java.util.Random();
        formatter_ = new DecimalFormat("0.000");

        isFsmAlive_ = false;
        useAutoMove_ = false;
        agents_ = new HashArray();

/*************
        agents_.put("man01",   new Agent("man01",   "male01",   "bg01", 1.2));
        agents_.put("man02",   new Agent("man02",   "m001",     "bg02", 1.6));
        agents_.put("woman01", new Agent("woman01", "female01", "bg03", 1.2));
        agents_.put("koizumi", new Agent("koizumi", "male01",   "bg04", 1.0));
        currAgent_ = "woman01";
*************/

        currAgent_ = Property.getAsStr("Behavior.DefaultAgent", "woman01");
        int nagent = Property.getAsInt("Behavior.NumAgent", 3);
        for (int i = 0; i < nagent; i++) {
            String key;
            key = "Behavior.Agent." + (i+1);
            String str = Property.getAsStr(key, "woman01 woman01 female01 bg01 1.0");
            // _debugPrint(key + " " + str);
            List<String> v = Util.makeTokenizedList(str, " ");
            double d = Double.parseDouble((String)v.get(4));
            agents_.put
                ((String)v.get(0), 
                 new Agent((String)v.get(1), (String)v.get(2), (String)v.get(3), d));
        }

    }


    private String _format(double d)
    {
        return formatter_.format(d);
    }


//    private void _setEventViewerMode(boolean b)
//    {
//        eventViewerMode_ = b;
//    }
//
//    
//    private void _setDebugMode(boolean b)
//    {
//        debugMode_ = b;
//    }
//
//
//    private void _debugPrint(String str)
//    {
//        System.err.println("BM: " + str);
//    }


    private void _send(String str)
    {
        ambuf_.send(str);
        // _debugPrint("send: " + str);
    }


    private void _sleep(int millisec)
    {
        try {
            Thread.sleep(millisec);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    private Agent _getCurrAgent()
    {
        return (Agent)agents_.get(currAgent_);
    }


    private Agent _getAgentByName(String name)
    {
        return (Agent)agents_.get(name);
    }


//    private void _setAgentByName(String name)
//    {
//        currAgent_ = name;
//    }


    private void _outputEventValue()
    {
        Agent agent = _getCurrAgent();
        Vector3D e = agent.getEyeRot();
        _send("to @GEV set Ex = " + _format(e.x));
        _send("to @GEV set Ey = " + _format(e.y));

        Vector3D h = agent.getHeadRot();
        _send("to @GEV set Hx = " + _format(h.x));
        _send("to @GEV set Hy = " + _format(h.y));
        _send("to @GEV set Hz = " + _format(h.z));
    }

    private void _outputFaceExp()
    {
        Agent agent = _getCurrAgent();
        boolean maskChanged = true;
        if ( agent == prevAgent_ ) {
            maskChanged = false;
        }

        Vector3D e = agent.getEyeRot();
        if ( maskChanged || (! e.equals(prevEyeRot_)) ) {
            _send("to @FSM set EyeRot = " + _format(e.x) + " " + _format(e.y) + " " + _format(e.z));
        }
        
        Vector3D h = agent.getHeadRot();
        if ( maskChanged || (! h.equals(prevHeadRot_)) ) {
            _send("to @FSM set HeadRotAbs.1 = " + _format(h.x) + " " + _format(h.y) + " " + _format(h.z));
        }
        
        Vector3D b = agent.getBodyRot();
        if ( maskChanged || (! b.equals(prevBodyRot_)) ) {
            _send("to @FSM set AgentRot = " + _format(b.x) + " " + _format(b.y) + " " + _format(b.z));
        }
        
        Vector3D t = agent.getBodyTrans();
        if ( maskChanged || (! t.equals(prevBodyTrans_)) ) {
            _send("to @FSM set AgentTrans = " + _format(t.x) + " " + _format(t.y));
        }

        double scale = agent.getBodyScale();
        if ( maskChanged 
             || Math.abs(scale - prevBodyScale_) > 0.01 ) {
            _send("to @FSM set AgentScale = " + _format(scale));
        }

        String exptype = agent.getExpType();
        double explevel = agent.getExpLevel();
        if ( maskChanged 
             || ! exptype.equals(prevExpType_)
             || Math.abs(explevel - prevExpLevel_) > 3.0 ) {
            _send("to @FSM set FaceExp = " + exptype + " 1 " + _format(explevel) + " 0");
        }

        prevAgent_ = agent;
        prevEyeRot_.set(e);
        prevHeadRot_.set(h);
        prevBodyRot_.set(b);
        prevBodyTrans_.set(t);
        prevBodyScale_ = scale;
        prevExpType_ = exptype;
        prevExpLevel_ = explevel;
    }


    private void _setupCurrMask()
    {
        String bg = _getCurrAgent().getBackground();
        String mask = _getCurrAgent().getMask();
        _send("to @FSM set Background = " + bg);
        _send("to @FSM set Mask = " + mask);
        _outputFaceExp();
    }


    private void _handleRep(String module, String slot, String arg)
    {
        if (module.equals("FSM") && slot.equals("Run") && arg.equals("LIVE")) {
            isFsmAlive_ = true;
        }
    }


    private void _sendFadeOut()
    {
/*****
        _send("to @FSM set AgentAlpha = 0.95");
        _sleep(100);
        _send("to @FSM set AgentAlpha = 0.90");
        _sleep(100);
        _send("to @FSM set AgentAlpha = 0.85");
        _sleep(100);
*****/
        _send("to @FSM set AgentEnable = DISABLE");
    }
    
    
    private void _sendFadeIn()
    {
        _send("to @FSM set AgentEnable = ENABLE");
/*****
        _send("to @FSM set AgentAlpha = 0.85");
        _sleep(100);
        _send("to @FSM set AgentAlpha = 0.90");
        _sleep(100);
        _send("to @FSM set AgentAlpha = 0.95");
        _sleep(100);
        _send("to @FSM set AgentAlpha = 1.00");
        _sleep(100);
*****/
    }
    
    
    private void _handleSet(String module, String slot, String arg)
    {
        if (slot.equals("AutoMove")) {
            useAutoMove_ = (Integer.parseInt(arg) == 0) ? false : true;
            if (useAutoMove_) {
                _setupCurrMask();
            }

        } else if (slot.equals("AgentSpeakState")) {
            _getCurrAgent().setAgentSpeakState(Integer.parseInt(arg));

        } else if (slot.equals("Emotion")) {
            Matcher matcher2 = pattern2_.matcher(arg);
            if (matcher2.matches()) {
                String type = matcher2.group(1);
                double level = Double.parseDouble(matcher2.group(2));
                _getCurrAgent().setEmotionTarget(type, level);
            } else {
                _getCurrAgent().setEmotionTarget(arg);
            }
        } else if (slot.equals("EmotionNow")) {
            Matcher matcher2 = pattern2_.matcher(arg);
            if (matcher2.matches()) {
                String type = matcher2.group(1);
                double level = Double.parseDouble(matcher2.group(2));
                _getCurrAgent().setEmotionNow(type, level);
            } else {
                _getCurrAgent().setEmotionNow(arg);
            }
        } else if (slot.equals("Rot")) {
            Matcher matcher3 = pattern3_.matcher(arg);
            if (matcher3.matches()) {
                double tx = Double.parseDouble(matcher3.group(1));
                double ty = Double.parseDouble(matcher3.group(2));
                double tz = Double.parseDouble(matcher3.group(3));
                _getCurrAgent().setRotTarget(tx, ty, tz);
            }
        } else if (slot.equals("RotNow")) {
            Matcher matcher3 = pattern3_.matcher(arg);
            if (matcher3.matches()) {
                double tx = Double.parseDouble(matcher3.group(1));
                double ty = Double.parseDouble(matcher3.group(2));
                double tz = Double.parseDouble(matcher3.group(3));
                _getCurrAgent().setRotNow(tx, ty, tz);
            }
        } else if (slot.equals("Background")) {
            Matcher matcher2 = pattern2_.matcher(arg);
            if (matcher2.matches()) {
                Agent ag = _getAgentByName(matcher2.group(1));
                ag.setBackground(matcher2.group(2));
            }
        } else if (slot.equals("HeadMoveRatio")) {
            Matcher matcher2 = pattern2_.matcher(arg);
            if (matcher2.matches()) {
                double d1 = Double.parseDouble(matcher2.group(1));
                double d2 = Double.parseDouble(matcher2.group(2));
                _getCurrAgent().setHeadMove(d1, d2);
            }
        } else if (slot.equals("Mask")) {
            if (arg.equals("DISABLE")) {
                _sendFadeOut();
            } else {
                if (! arg.equals(currAgent_)) {
                    _sendFadeOut();
                }
                currAgent_ = arg;
                _setupCurrMask();
                _sendFadeIn();
            }
        } else if (slot.equals("FaceMot")) {
            _send("to @FSM set FaceMot = " + arg);

        } else if (slot.equals("Action")) {
            if (arg.equals("Thinking")) {
                _getCurrAgent().setRotTarget(+0.3, 0, 0);
                int v = random_.nextInt(8);
                switch(v) {
                case 0:
                    _getCurrAgent().setEyeRotNow(-5,  0, 0); // up
                    break;
                case 1:
                    _getCurrAgent().setEyeRotNow(+5,  0, 0); // down
                    break;
                case 2:
                    _getCurrAgent().setEyeRotNow( 0, -7, 0); // left
                    break;
                case 3:
                    _getCurrAgent().setEyeRotNow( 0, +7, 0); // right
                    break;
                case 4:
                    _getCurrAgent().setEyeRotNow(-4, -4, 0); //
                    break;
                case 5:
                    _getCurrAgent().setEyeRotNow(-4, +4, 0); //
                    break;
                case 6:
                    _getCurrAgent().setEyeRotNow(+4, -4, 0); //
                    break;
                case 7:
                    _getCurrAgent().setEyeRotNow(+4, +4, 0); //
                    break;
                }
            } else if (arg.equals("InfoRequest")) {
                _getCurrAgent().setRotTarget(0, 0, 0);
            } else if (arg.equals("Speaking")) {
                _getCurrAgent().setRotTarget(-0.5, 0, 0);
            } else if (arg.equals("Busy")) {
                _getCurrAgent().setRotTarget(5, 3, 0);
            } else if (arg.equals("Blink")) {
                _send("to @FSM set FaceMot = BLINK 2");
            }
        } else if (slot.equals("Nod")) {
            double d = Double.parseDouble(arg);
            _getCurrAgent().startNodding(d);
        } else if (slot.equals("Refuse")) {
            double d = Double.parseDouble(arg);
            _getCurrAgent().startRefuse(d);
        } else if (slot.equals("Tilt")) {
            double d = Double.parseDouble(arg);
            _getCurrAgent().setHeadRotTarget(0, 0, d);
        }
    }
    
    
    private void _iteration()
    {
        String str = ambuf_.receive();
        if (str.length() > 0) {
            // _debugPrint("receive: " + str);
            
            Matcher matcher1 = pattern1_.matcher(str);
            if (matcher1.matches()) {
                String module = matcher1.group(1);
                String cmd    = matcher1.group(2);
                String slot   = matcher1.group(3);
                String arg    = matcher1.group(4);
                // _debugPrint("match: " + module + " " + slot + " " + cmd + " " + arg);

                if (cmd.equals("set")) {
                    //_debugPrint("set: " + module + " " + slot + " " + arg);
                    _handleSet(module, slot, arg);
                } else if (cmd.equals("rep")) {
                    //_debugPrint("rep: " + module + " " + slot + " " + arg);
                    _handleRep(module, slot, arg);
                }
            }

            Matcher matcher4 = pattern4_.matcher(str);
            if (matcher4.matches()) {
                String module = "___";
                String cmd    = matcher4.group(1);
                String slot   = matcher4.group(2);
                String arg    = matcher4.group(3);
                if (cmd.equals("set")) {
                    //_debugPrint("set: " + module + " " + slot + " " + arg);
                    _handleSet(module, slot, arg);
                }
            }
        }

        if (useAutoMove_) {
            long currentTime = System.currentTimeMillis();
            long delta = currentTime - prevTime_;
            if (delta >= 100) {
                _getCurrAgent().calc();
                _outputFaceExp();
                if (eventViewerMode_) {
                    _outputEventValue();
                }
                _getCurrAgent().addClock((double)delta / 1000.0);

                prevTime_ = currentTime;
            }
        }
    }

    public void run()
    {
        prevTime_ = System.currentTimeMillis();

        _send("rep Status.power = ON");

        while (true) {
            _iteration();
            if (isFsmAlive_ || debugMode_) {
                _sleep(1);
            } else {
                _send("to @FSM inq Run");
                _sleep(100);
            }
        }
    }


	public static final String COPYRIGHT = 
		"(c)2003-2006 Takuya NISHIMOTO (nishi@hil.t.u-tokyo.ac.jp)\n";
	
	public static void showVersion() {
		System.err.println("BehaviorManager");
		System.err.println(galatea.dialog.Tstamp.TSTAMP);
		System.err.print(COPYRIGHT);
		System.err.println();
	}
	
	public static void showUsage() {
//		System.err.println("Usage:\tgalatea [options] document.vxml");
//		System.err.println("\t -c file : config file");
//		System.err.println("\t -v      : show version");
//		System.err.println("\t -p      : print translation results");
//		System.err.println();
	}

	/**
     * java -cp gdm.jar galatea.BehaviorManager
     *
     * [in]  From @DM set Mask = DISABLE
     * [out] to @FSM set AgentEnable = DISABLE
     *
     */
    public static void main(String argv[])
    {
    	String conf = "gdm.conf";
    	
    	Getopt g = new Getopt("", argv, "a:c:dev");
    	g.setOpterr(false);
    	int c;
    	while ((c = g.getopt()) != -1){
    		switch (c)	{
    		case 'a':
    			// behaviorManager._setAgentByName(g.getOptarg());
    			break;
    		case 'c':
    			conf = g.getOptarg();
    			break;
    		case 'd':
    			// behaviorManager._setDebugMode(true);
    			break;
    		case 'e':
    			// behaviorManager._setEventViewerMode(true);
    			break;
    		case 'v':
    			showVersion();
    			break;
    		}
    	}
    	
    	File conffile = new File(conf);
    	if(conffile.canRead()) {
    		try {
    			Properties props = new Properties(System.getProperties());
    			props.load(new FileInputStream(conffile));
    			System.setProperties(props);
    			// TODO: use System.setProperty(key, value);
    		} catch(Exception e) {
    			e.printStackTrace();
    			System.exit(0);
    		}
    	}
    	
    	BehaviorManager behaviorManager = new BehaviorManager();
    	
    	behaviorManager.run();
    }

}
