/*
 * Galatea Dialog Manager:
 * (c)2005 Takuya NISHIMOTO (nishi@hil.t.u-tokyo.ac.jp)
 * $Id: DeviceManager.java,v 1.52 2008/02/14 03:36:02 nishi Exp $
 */
package galatea.io;

import galatea.dialog.IControllerModeManager;
import galatea.dialog.IInterpretedEventListener;
import galatea.dialog.InterpretedEvent;
import galatea.dialog.RuntimeError;
import galatea.dialog.window.DialogManagerWindow;
import galatea.dialog.window.IDMWindowActionListener;
import galatea.httpclient.NetUtil;
import galatea.httpserver.HttpRequestTask;
import galatea.httpserver.HttpServer;
import galatea.httpserver.HttpSession;
import galatea.httpserver.IHttpRequestResponder;
import galatea.httpserver.IHttpResponseHtmlMaker;
import galatea.io.plugins.AudioPlayerSubModule;
import galatea.io.plugins.GalateaTalkSubModule;
import galatea.io.plugins.GrammarSubModule;
import galatea.io.plugins.JulianSubModule;
import galatea.io.plugins.KeypadSubModule;
import galatea.io.plugins.MotionSubModule;
import galatea.io.wrapper.GRMSubModule;
import galatea.io.wrapper.SNDSubModule;
import galatea.io.wrapper.SRMSubModule;
import galatea.io.wrapper.SSMSubModule;
import galatea.logger.Logger;
import galatea.outitem.AudioOutItem;
import galatea.outitem.BreakOutItem;
import galatea.outitem.GrammarOutItem;
import galatea.outitem.LogOutItem;
import galatea.outitem.NativeOutItem;
import galatea.outitem.OutItem;
import galatea.outitem.VoiceOutItem;
import galatea.relaxer.vxml20.IVxmlBlockMixed;
import galatea.relaxer.vxml20.RString;
import galatea.relaxer.vxml20.VxmlBlock;
import galatea.relaxer.vxml20.VxmlForm;
import galatea.relaxer.vxml20.VxmlNative;
import galatea.relaxer.vxml20.VxmlVxml;
import galatea.submodule.IReceiverFromSubModule;
import galatea.submodule.ISubModule;
import galatea.util.Property;

import java.util.ArrayList;

public class DeviceManager
	implements IReceiverFromSubModule, IHttpRequestResponder
	{

	private Logger dbg = new Logger("DEVMAN", 0);
	private AMThread amt_;
	private BreakThread bt_;
	private HttpSenderToDevice httpDevice_;

	private ArrayList<ISubModule> subModules_ = new ArrayList<ISubModule>();
	private ISubModule ssmSubModule_;
	private ISubModule sndSubModule_;
	private ISubModule srmSubModule_;
	private ISubModule grmSubModule_;
	private ISubModule keypadSubModule_;
	private ISubModule motionSubModule_;

	private ArrayList<IInterpretedEventListener> 
		interpretedEventListeners_ = new ArrayList<IInterpretedEventListener>();
	private ArrayList<IOutputEventListener> 
		outputEventListeners_ = new ArrayList<IOutputEventListener>();
	
	private int runLevel_ = 0;
	private int targetRunLevel_ = 1;
	private int runLevelUnchangedCount_ = 0;
	
	private IControllerModeManager icmode_;
	
	private ArrayList<HttpRequestTask> 
		httpRequestTasks_ = new ArrayList<HttpRequestTask>();
	private HttpServer httpServer_;
	private HttpSession httpSession_;

	private DialogManagerWindow window_;

	public DeviceManager() {
	}
	
	public void start(IControllerModeManager ic0,
			IOutputEventListener ic1,
			IInterpretedEventListener ic2,
			IHttpResponseHtmlMaker ic3,
			IDMWindowActionListener ic4
			) {
		
		window_ = new DialogManagerWindow();
		window_.setDialogManagerWindowActionListener(ic4);
		NetUtil.addListener(window_);
		Logger.addListener(window_);

		int port = Property.getAsInt("DM.HttpServerPort", 0);
		if (port != 0) {
			httpServer_ = new HttpServer();
			httpSession_ = new HttpSession(ic3, this);
			httpServer_.start(httpSession_, port);
			dbg.print("http server started. port:" + port);
		}
		httpDevice_ = new HttpSenderToDevice(httpSession_);
		
		// bt
		dbg.print("BreakThread starting..");
		bt_ = new BreakThread();
		bt_.addOutputEventListener(ic1);
		bt_.start();
		dbg.print("BreakThread started.");
		
		// amt
		dbg.print("AMThread starting..");
		amt_ = new AMThread();
		
		icmode_ = ic0;
		addOutputEventListener(ic1);
		addInterpretedEventListener(ic2);
		
		// SSM
		// use SSM (AM-MCL) wrapper
		if (Property.getAsBoolean("DM.UseWrapper.SSM", true)) {
			ssmSubModule_ = new SSMSubModule();
			amt_.addSubModule(ssmSubModule_, this, window_);
			subModules_.add(ssmSubModule_);
		} else {
			ssmSubModule_ = new GalateaTalkSubModule();
			ssmSubModule_.setReceiver(this);
			ssmSubModule_.setLogger(window_);
			subModules_.add(ssmSubModule_);
		}
		ssmSubModule_.setName("SSM");
		
		// SND
		// use wrapper
		if (Property.getAsBoolean("DM.UseWrapper.SND", false)) {
			sndSubModule_ = new SNDSubModule();
			amt_.addSubModule(sndSubModule_, this, window_);
			subModules_.add(sndSubModule_);
		} else {
			sndSubModule_ = new AudioPlayerSubModule();
			sndSubModule_.setReceiver(this);
			sndSubModule_.setLogger(window_);
			subModules_.add(sndSubModule_);
		}
		sndSubModule_.setName("SND");
		
		// SRM & SIM
		if (Property.getAsBoolean("DM.UseWrapper.SRM", true)) {
			srmSubModule_ = new SRMSubModule();
			amt_.addSubModule(srmSubModule_, this, window_);
			subModules_.add(srmSubModule_);
		} else {
			srmSubModule_ = new JulianSubModule();
			amt_.addSubModule(srmSubModule_, this, window_);
//			srmSubModule_.setReceiver(this);
//			srmSubModule_.setLogger(window_);
			subModules_.add(srmSubModule_);
		}
		srmSubModule_.setName("SRM");

		// GRM
		// use wrapper
		if (Property.getAsBoolean("DM.UseWrapper.GRM", false)) {
			grmSubModule_ = new GRMSubModule();
			amt_.addSubModule(grmSubModule_, this, window_);
			subModules_.add(grmSubModule_);
		} else {
			grmSubModule_ = new GrammarSubModule();
			grmSubModule_.setReceiver(this);
			grmSubModule_.setLogger(window_);
			subModules_.add(grmSubModule_);
		}
		grmSubModule_.setName("GRM");
		
		// keypad
		if (Property.getAsBoolean("DM.UseKeypadSubModule", false)) {
			keypadSubModule_ = new KeypadSubModule();
			amt_.addSubModule(keypadSubModule_, this, window_);
			subModules_.add(keypadSubModule_);
			keypadSubModule_.setName("KBD");
		}
		// motion
		if (Property.getAsBoolean("DM.UseMotionSubModule", true)) {
			motionSubModule_ = new MotionSubModule();
			if (httpDevice_ != null) {
				httpDevice_.addSubModule(motionSubModule_, this, window_);
			}
			subModules_.add(motionSubModule_);
			motionSubModule_.setName("MTN");
		}
		
		amt_.start();
		dbg.print("AMThread started.");
	}

	public void doTimerTask() throws RuntimeError {
		for (int i = 0, n = subModules_.size(); i < n; i++) {
			subModules_.get(i).doTimerTask();
		}
	}

	private void addInterpretedEventListener(IInterpretedEventListener l) {
		interpretedEventListeners_.add(l);
	}

	private void addOutputEventListener(IOutputEventListener l) {
		outputEventListeners_.add(l);
	}
	
	// システム全体の runLevel
	private int getRunLevel() {
		return runLevel_;
	}

	// システム全体の runLevel
	private void setRunLevel(int i) {
		runLevel_ = i;
	}
	
	private boolean _isRunLevel2() {
		//dbg.print("runLevel: " + runLevel_ );
		if (getRunLevel() == 2 )
			return true;
		return false;
	}
	
	private void _dispDeviceManagerLog(String str) {
		window_.inputEventReceived(str);
	}

	private void _dispLogOutItem(String str) {
		window_.dispApplog(str);
	}

	private boolean _gotoRunLevel2() {
//		// これを実行しないと runLevel=2 に移行しない
//		String scfile = Property.getAsStr("DM.SubmoduleConfigFile", null);
//		if (scfile != null) {
//			_loadSubmoduleConfigFile(scfile);
//			_outputNative(initOutputCmd_);
//			_outputNative(initRecogCmd_);
//		}
		setRunLevel(1);
		_dispDeviceManagerLog("RunLevel:1");
		amt_.outputNative("rep Run = DEAD");
		
		_setTargetRunLevel(2);
		return true;
	}
	

	private void _setTargetRunLevel(int level) {
		runLevelUnchangedCount_ = subModules_.size();
		targetRunLevel_ = level;
		for (int i = 0, n = subModules_.size(); i < n; i++) {
			subModules_.get(i).setTargetRunLevel(level);
		}
	}

	public boolean waitDevicesForReady() {
		dbg.print("wait AMThread isReady...");
		if (_gotoRunLevel2() == false) {
			dbg.print("failed to gotoRunLevel2.");
			return false;
		}
		while(! _isRunLevel2()) {
			try { 
				Thread.sleep(1);
				doTimerTask();
			} catch (Exception e) {e.printStackTrace();}
		}
		dbg.print("starting done.");
		return true;
	}
	
	// if error : return false
	// mapping OutItem and SubModule 
	public boolean doOutput(OutItem item) {
		if (item instanceof NativeOutItem) {
			NativeOutItem o = (NativeOutItem)item;
			String str = o.getArg();
			amt_.outputNative(str);
			return true;
		} else if (item instanceof BreakOutItem) {
			bt_.outputDeviceStart(item);
			return true;
		} else if (item instanceof LogOutItem) {
			LogOutItem o = (LogOutItem)item;
			_dispLogOutItem(o.getArg());
			return true;
		} else {
			// Chain of Responsibility
			for (int i = 0, n = subModules_.size(); i < n; i++) {
				if (subModules_.get(i).startOutput(item)) {
					return true;
				}
			}
		}
		dbg.print("doOutput failed:" + item.toString());
		return false;
	}
	
	
	// mapping OutItem and SubModule 
	public boolean doStop(OutItem item) {
		if (item instanceof BreakOutItem) {
			bt_.outputDeviceStop(item);
			return true;
		} else {
			// Chain of Responsibility
			for (int i = 0, n = subModules_.size(); i < n; i++) {
				if (subModules_.get(i).stopOutput(item)) {
					return true;
				}
			}
		}
		dbg.print("doStop failed:" + item.toString());
		return false;
	}


	public void receiveInterpreted(InterpretedEvent ev) {
		for (int i = 0; i < interpretedEventListeners_.size(); i++) {
			((IInterpretedEventListener)interpretedEventListeners_.get(i))
				.enqueueInterpretedEvent(ev);
		}
	}
	
	public void updateDialogStateChange(String state) {
		window_.dialogStateChanged(state);
	}
	
	public void receiveOutputBusy(String device, String message) {
		for (int i = 0; i < outputEventListeners_.size(); i++) {
			outputEventListeners_.get(i).enqueueOutputEvent(
					DeviceEvent.newOutputBusyInstance(device,message));
		}
	}

	public void receiveOutputReady(String device, String message) {
		for (int i = 0; i < outputEventListeners_.size(); i++) {
			outputEventListeners_.get(i).enqueueOutputEvent(
				DeviceEvent.newOutputReadyInstance(device,message));
		}
	}

	public void receiveRunLevel(int level, ISubModule src) {
		dbg.print("RunLevel:" + level + " sender:" + src.getName());
		if (targetRunLevel_ == level && runLevelUnchangedCount_ > 0) {
			runLevelUnchangedCount_ --;
			if (runLevelUnchangedCount_ == 0) {
				setRunLevel(targetRunLevel_);
				if (level == 2) {
					amt_.outputNative("rep Run = LIVE");
					_dispDeviceManagerLog("RunLevel:2");
				}
			}
		}
	}

	/**
	 * this method is used from HttpServerResponse
	 * return : result (log message)
	 */
	public String respondToHttpRequest(String module, String ev, String arg) {
		dbg.ASSERT(module != null, "m != null");
		dbg.ASSERT(ev != null, "e != null");
		if (httpDevice_.checkInput(module, ev)) {
			return module + " " + ev + " accepted";
		} else if (module.equals("browser")) {
			synchronized (this) {
				httpRequestTasks_.add(new HttpRequestTask(ev,arg));
			}
			return "task enqueued";
		}
		return "event ignored";
	}

	public synchronized int sizeExternalTask() {
		return httpRequestTasks_.size();
	}

	public synchronized HttpRequestTask removeExternalTask() {
		return httpRequestTasks_.remove(0);
	}

	public void dispDocInfo(String file, String state, String src, String trans) {
		window_.documentLocationChanged(file);
		window_.dialogStateChanged(state);
		window_.srcFileUpdated(src);
		window_.translatedFileUpdated(trans);
	}

	public void dispDebuggerEvalResult(String result) {
		window_.dispDebuggerEvalResult(result);
	}

}
