/FileExtraction/Java_unseen/X0Ken_FreeMind/plugins/collaboration/socket/CommunicationBase.java
/*FreeMind - A Program for creating and viewing Mindmaps
 *Copyright (C) 2000-2012 Joerg Mueller, Daniel Polansky, Christian Foltin, Dimitri Polivaev and others.
 *
 *See COPYING for Details
 *
 *This program is free software; you can redistribute it and/or
 *modify it under the terms of the GNU General Public License
 *as published by the Free Software Foundation; either version 2
 *of the License, or (at your option) any later version.
 *
 *This program is distributed in the hope that it will be useful,
 *but WITHOUT ANY WARRANTY; without even the implied warranty of
 *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *GNU General Public License for more details.
 *
 *You should have received a copy of the GNU General Public License
 *along with this program; if not, write to the Free Software
 *Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

package plugins.collaboration.socket;

import java.awt.EventQueue;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.SocketTimeoutException;

import freemind.controller.actions.generated.instance.CollaborationActionBase;
import freemind.controller.actions.generated.instance.CollaborationTransaction;
import freemind.controller.actions.generated.instance.XmlAction;
import freemind.main.Tools;
import freemind.modes.mindmapmode.MindMapController;
import freemind.modes.mindmapmode.actions.xml.ActionPair;

/**
 * @author foltin
 * @date 06.09.2012
 */
public abstract class CommunicationBase extends TerminateableThread {

	/**
	 * 
	 */
	private static final String STRING_CONTINUATION_SUFFIX = "<cont>";
	protected Socket mSocket;

	/**
	 * @param pName
	 * @param pClient
	 * @param pController
	 * @param pOut
	 * @param pIn
	 */
	public CommunicationBase(String pName, Socket pClient,
			MindMapController pController, DataOutputStream pOut,
			DataInputStream pIn) {
		super(pName);
		mSocket = pClient;
		mController = pController;
		out = pOut;
		in = pIn;
	}

	protected MindMapController mController;
	protected DataOutputStream out;
	protected DataInputStream in;
	protected static final int ROUNDTRIP_ROUNDS = 200;
	protected static final int ROUNDTRIP_TIMEOUT = 20000;
	protected static final int STATE_IDLE = 0;
	protected static final int STATE_WAIT_FOR_HELLO = 1;
	protected static final int STATE_WAIT_FOR_COMMAND = 2;
	protected static final int STATE_WAIT_FOR_WHO_ARE_YOU = 3;
	protected static final int STATE_WAIT_FOR_WELCOME = 4;
	protected static final int STATE_WAIT_FOR_LOCK = 5;
	protected static final int STATE_LOCK_RECEIVED = 6;
	private static final int MAX_STRING_LENGTH_TO_SEND = 65500;

	private int mCurrentState = STATE_IDLE;
	private String mCurrentStateMutex = "lockme";
	private StringBuffer mCurrentCommand = new StringBuffer();

	/**
	 * @param pMessage
	 * @return true, if successful.
	 */
	public synchronized boolean send(CollaborationActionBase pCommand) {
		try {
			printCommand("Send", pCommand);
			final String marshalledText = Tools.marshall(pCommand);
			logger.fine(getName() + " :Sending " + marshalledText);
			String text = Tools.compress(marshalledText);
			// split into pieces, as the writeUTF method is only able to send
			// 65535 bytes...
			int index = 0;
			while (index + MAX_STRING_LENGTH_TO_SEND < text.length()) {
				out.writeUTF(text.substring(index, index
						+ MAX_STRING_LENGTH_TO_SEND)
						+ STRING_CONTINUATION_SUFFIX);
				index += MAX_STRING_LENGTH_TO_SEND;
			}
			out.writeUTF(text.substring(index));
			return true;
		} catch (IOException e) {
			freemind.main.Resources.getInstance().logException(e);
		}
		return false;
	}

	public boolean processAction() throws Exception {
		boolean didSomething = false;
		try {
			// Non blocking!!
			String text = in.readUTF();
			if (text.endsWith(STRING_CONTINUATION_SUFFIX)) {
				mCurrentCommand.append(text.substring(0, text.length()
						- STRING_CONTINUATION_SUFFIX.length()));
				didSomething = true;
			} else {
				mCurrentCommand.append(text);
				final String textValue = mCurrentCommand.toString();
				mCurrentCommand.setLength(0);
				final String decompressedText = Tools.decompress(textValue);
				logger.fine(getName() + " :Received " + decompressedText);
				final CollaborationActionBase command = (CollaborationActionBase) Tools
						.unMarshall(decompressedText);
				if (command != null) {
					printCommand("Receive", command);
					Runnable runnable = new Runnable() {
						public void run() {
							try {
								processCommand(command);
							} catch (Exception e) {
								freemind.main.Resources.getInstance().logException(e);
							}
						}
					};
					if (command instanceof CollaborationTransaction) {
						// inserted in event queue here, to avoid
						// concurrency issues.
						EventQueue.invokeLater(runnable);						
					} else  {
						// other commands than transactions are processed directly.
						runnable.run();
					}
					didSomething = true;
				}
			}
		} catch (SocketTimeoutException e) {
		}

		mCounter--;
		if (mCounter <= 0) {
			mCounter = 10;
			mController.getController().setTitle();
		}
		return didSomething;
	}

	/**
	 * @param pDirection
	 * @param pCommand
	 */
	private void printCommand(String pDirection,
			CollaborationActionBase pCommand) {
		if (pCommand instanceof CollaborationTransaction) {
			CollaborationTransaction trans = (CollaborationTransaction) pCommand;
			XmlAction doAction = mController.unMarshall(trans.getDoAction());
			String out = pDirection + ": " + Tools.printXmlAction(doAction)
					+ " (Id: " + trans.getId() + ")";
			logger.info(out);
		} else {
			String out = pDirection + ": " + Tools.printXmlAction(pCommand);
			logger.info(out);
			
		}
	}

	int mCounter = 1;

	public abstract void processCommand(CollaborationActionBase command)
			throws Exception;

	protected int getCurrentState() {
		synchronized (mCurrentStateMutex) {
			return mCurrentState;
		}
	}

	protected void setCurrentState(int pCurrentState) {
		synchronized (mCurrentStateMutex) {
			mCurrentState = pCurrentState;
		}
	}

	/**
	 * @param pDoAction
	 * @param pUndoAction
	 * @param pLockId
	 */
	public void sendCommand(String pDoAction, String pUndoAction, String pLockId) {
		CollaborationTransaction trans = new CollaborationTransaction();
		trans.setDoAction(pDoAction);
		trans.setUndoAction(pUndoAction);
		trans.setId(pLockId);
		send(trans);
	}

	public void close() throws IOException {
		mSocket.close();
	}

	public ActionPair getActionPair(CollaborationTransaction trans) {
		return new ActionPair(mController.unMarshall(trans.getDoAction()),
				mController.unMarshall(trans.getUndoAction()));
	}

	public String getIpToSocket() {
		return mSocket.getLocalAddress().getHostAddress();
	}

	protected void printWrongState(CollaborationActionBase pCommand) {
		logger.warning("Wrong state for " + pCommand.getClass() + ": "
				+ printState(getCurrentState()));
	}

	/**
	 * @param pCurrentState
	 * @return
	 */
	protected String printState(int pCurrentState) {
		switch (pCurrentState) {
		case STATE_IDLE:
			return "STATE_IDLE";
		case STATE_WAIT_FOR_HELLO:
			return "STATE_WAIT_FOR_HELLO";
		case STATE_WAIT_FOR_COMMAND:
			return "STATE_WAIT_FOR_COMMAND";
		case STATE_WAIT_FOR_WHO_ARE_YOU:
			return "STATE_WAIT_FOR_WHO_ARE_YOU";
		case STATE_WAIT_FOR_WELCOME:
			return "STATE_WAIT_FOR_WELCOME";
		case STATE_WAIT_FOR_LOCK:
			return "STATE_WAIT_FOR_LOCK";
		case STATE_LOCK_RECEIVED:
			return "STATE_LOCK_RECEIVED";
		}
		return "UNKNOWN: " + pCurrentState;
	}

}