package jp.hasc.hasctool.core.runtime.hierarchize.remote;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

import jp.hasc.hasctool.core.runtime.AbstractTask;
import jp.hasc.hasctool.core.runtime.RuntimeContext;
import jp.hasc.hasctool.core.runtime.hierarchize.InnerXbdExecutor;
import jp.hasc.hasctool.core.runtime.hierarchize.XbdExecutionRequest;
import jp.hasc.hasctool.core.runtime.sink.XStreamLogSink;

import com.thoughtworks.xstream.XStream;


/**
 * {@link XERequestReceiver}から、TCP経由で{@link XbdExecutionRequest}を受信して、実行します。
 * @author iwasaki
 */
public class XERequestReceiver extends AbstractTask {
	/** logger for this class */
	private final static org.apache.commons.logging.Log LOG = org.apache.commons.logging.LogFactory
			.getLog(XERequestReceiver.class);
	
	public static final int DEFAULT_TCP_PORT = 6666;
	
	private int port_=DEFAULT_TCP_PORT;

	private ServerSocket serverSocket_;

	private XStream xstream_;
	
	@Override
	public void setup(RuntimeContext context) {
		super.setup(context);
		context.addStopTasksListener(new Runnable() {
			public void run() {
				doClose();
			}
		});
		try{
			LOG.info("bind "+port_);
			serverSocket_ = new ServerSocket(port_);
			xstream_ = createXStreamForTCPMessaging();
		} catch (IOException ex) {
			LOG.warn("IOException",ex);
		}
	}
	
	public static void sendMessage(XStream xs, Object obj, DataOutputStream dout) throws IOException {
		String msg = xs.toXML(obj);
		dout.writeUTF(msg);
		dout.flush();
	}
	public static Object receiveMessage(XStream xs, DataInputStream din) throws IOException {
		String msg=din.readUTF();
		return xs.fromXML(msg);
	}
	
	private boolean shutdownOnDisconnect_ = false;
	
	private Socket clientSocket_ = null;
	
	private void doClose() {
		if (clientSocket_!=null) {
			try {
				LOG.debug("receiver force close");
				clientSocket_.close();
			} catch (IOException ex) {
				LOG.warn("IOException",ex);
			}
		}
		if (serverSocket_!=null) {
			LOG.debug("serverSocket force close");
			try {
				serverSocket_.close();
			} catch (IOException ex) {
				LOG.warn("IOException",ex);
			}
		}
	}
	
	@Override
	protected void run() throws InterruptedException {
		while(!isShutdown()) {
			try {
				Socket socket = clientSocket_ = serverSocket_.accept();
				LOG.debug("accepted");
				DataInputStream din = new DataInputStream(socket.getInputStream());
				DataOutputStream dout = new DataOutputStream(socket.getOutputStream());
				try{
					RuntimeContext outerContext = getRuntimeContext();
					while(!isShutdown()) {
						// send ready message
						sendMessage(xstream_,XERequestEnumCommand.READY,dout);
						
						// receive message
						Object msg=receiveMessage(xstream_,din);
						if (msg==null) break;
						
						// parse message
						if (msg instanceof XbdExecutionRequest) {
							// execute XERequest
							XbdExecutionRequest req=(XbdExecutionRequest)msg;
							//
							if (req.getXbdFilePath()==null) throw new NullPointerException("xbdFilePath==null");
							LOG.debug("execute "+req.getXbdFilePath());
							//
							InnerXbdExecutor innerXbdExecutor=new InnerXbdExecutor();
							innerXbdExecutor.load(outerContext, req);
							innerXbdExecutor.start();
							innerXbdExecutor.awaitTasksTermination();
							//innerXbdExecutor.dispose();
							//
						}else if (XERequestEnumCommand.CLOSE_CONNECTION == msg) {
							LOG.debug("receiver close");
							sendMessage(xstream_,XERequestEnumCommand.CLOSE_CONNECTION,dout);
							break;
						}
					}
				}finally{
					dout.close();
					din.close();
					clientSocket_.close();
					clientSocket_=null;
				}
			} catch (IOException ex) {
				LOG.warn("IOException",ex);
			}
			if (shutdownOnDisconnect_) break;
		}
		try {
			serverSocket_.close();
			serverSocket_=null;
		} catch (IOException ex) {
			LOG.warn("IOException",ex);
		}
	}
	
	public static XStream createXStreamForTCPMessaging() {
		return XStreamLogSink.createXStreamForLogging();
	}

	public void setPort(int port) {
		port_ = port;
	}

	public void setShutdownOnDisconnect(boolean shutdownOnDisconnect) {
		shutdownOnDisconnect_ = shutdownOnDisconnect;
	}
}
