/FileExtraction/Java_unseen/X0Ken_FreeMind/plugins/collaboration/socket/ClientCommunication.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.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.StringReader;
import java.net.Socket;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;

import plugins.collaboration.socket.SocketBasics.UnableToGetLockException;
import freemind.controller.actions.generated.instance.CollaborationActionBase;
import freemind.controller.actions.generated.instance.CollaborationGoodbye;
import freemind.controller.actions.generated.instance.CollaborationHello;
import freemind.controller.actions.generated.instance.CollaborationReceiveLock;
import freemind.controller.actions.generated.instance.CollaborationRequireLock;
import freemind.controller.actions.generated.instance.CollaborationTransaction;
import freemind.controller.actions.generated.instance.CollaborationUnableToLock;
import freemind.controller.actions.generated.instance.CollaborationUserInformation;
import freemind.controller.actions.generated.instance.CollaborationWelcome;
import freemind.controller.actions.generated.instance.CollaborationWhoAreYou;
import freemind.controller.actions.generated.instance.CollaborationWrongCredentials;
import freemind.extensions.PermanentNodeHook;
import freemind.main.Tools;
import freemind.modes.MapAdapter;
import freemind.modes.NodeAdapter;
import freemind.modes.mindmapmode.MindMapController;
import freemind.modes.mindmapmode.MindMapMapModel;
import freemind.modes.mindmapmode.MindMapNodeModel;

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

	private static final int MAX_LOCK_RETRIES = 5;
	private static final long LOCK_RETRY_SLEEP_TIME = 1000;
	private String mLockId;
	private HashSet mLockIds = new HashSet();
	private String mPassword;
	private SocketConnectionHook mSocketConnectionHook = null;
	private boolean mReceivedGoodbye = false;
	private CollaborationUserInformation mUserInfo;

	/**
	 * @param pName
	 * @param pClient
	 * @param pController
	 * @param pPassword
	 * @param pOut
	 * @param pIn
	 * @throws IOException
	 */
	public ClientCommunication(String pName, Socket pClient,
			MindMapController pController, String pPassword) throws IOException {
		super(pName, pClient, pController, new DataOutputStream(
				pClient.getOutputStream()), new DataInputStream(
				pClient.getInputStream()));
		mPassword = pPassword;
		setCurrentState(STATE_WAIT_FOR_WHO_ARE_YOU);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * plugins.collaboration.socket.CommunicationBase#processCommand(freemind
	 * .controller.actions.generated.instance.CollaborationActionBase)
	 */
	public void processCommand(CollaborationActionBase pCommand)
			throws IOException {
		if (pCommand instanceof CollaborationGoodbye) {
			CollaborationGoodbye goodbye = (CollaborationGoodbye) pCommand;
			logger.info("Goodbye received from " + goodbye.getUserId());
			terminateSocket();
			return;
		}
		boolean commandHandled = false;
		if (pCommand instanceof CollaborationUserInformation) {
			CollaborationUserInformation userInfo = (CollaborationUserInformation) pCommand;
			mUserInfo = userInfo;
			commandHandled = true;
		}
		if (pCommand instanceof CollaborationWhoAreYou) {
			if (getCurrentState() != STATE_WAIT_FOR_WHO_ARE_YOU) {
				printWrongState(pCommand);
			}
			// send hello:
			CollaborationHello helloCommand = new CollaborationHello();
			helloCommand.setUserId(Tools.getUserName());
			helloCommand.setPassword(mPassword);
			send(helloCommand);
			setCurrentState(STATE_WAIT_FOR_WELCOME);
			commandHandled = true;
		}
		if (pCommand instanceof CollaborationWelcome) {
			if (getCurrentState() != STATE_WAIT_FOR_WELCOME) {
				printWrongState(pCommand);
			}
			CollaborationWelcome collWelcome = (CollaborationWelcome) pCommand;
			createNewMap(collWelcome.getMap());
			setCurrentState(STATE_IDLE);
			commandHandled = true;
		}
		if (pCommand instanceof CollaborationWrongCredentials) {
			if (getCurrentState() != STATE_WAIT_FOR_WELCOME) {
				printWrongState(pCommand);
			}
			// Over and out.
			terminateSocket();
			// Display error message!
			getMindMapController().getController().errorMessage(
					getMindMapController().getText("socket_wrong_password"));
			commandHandled = true;
		}
		if (pCommand instanceof CollaborationTransaction) {
			CollaborationTransaction trans = (CollaborationTransaction) pCommand;
			// check if it is from me!
			boolean removeResult;
			synchronized (mLockIds) {
				removeResult = mLockIds.remove(trans.getId());
			}
			if (!removeResult) {
				// it is not from me, so handle it:
				if (getCurrentState() != STATE_IDLE) {
					printWrongState(pCommand);
				}
				if (mSocketConnectionHook != null) {
					mSocketConnectionHook
							.executeTransaction(getActionPair(trans));
				}
			}
			commandHandled = true;
		}
		if (pCommand instanceof CollaborationReceiveLock) {
			if (getCurrentState() != STATE_WAIT_FOR_LOCK) {
				printWrongState(pCommand);
			}
			CollaborationReceiveLock lockReceived = (CollaborationReceiveLock) pCommand;
			this.mLockId = lockReceived.getId();
			synchronized (mLockIds) {
				mLockIds.add(mLockId);
			}
			setCurrentState(STATE_LOCK_RECEIVED);
			commandHandled = true;
		}
		if (pCommand instanceof CollaborationUnableToLock) {
			// no lock possible.
			setCurrentState(STATE_IDLE);
			commandHandled = true;
		}
		if (!commandHandled) {
			logger.warning("Received unknown message of type "
					+ pCommand.getClass());
		}
	}

	public void terminateSocket() {
		mReceivedGoodbye = true;
		if (mSocketConnectionHook != null) {
			// first deregister, as otherwise, the toggle hook command is tried
			// to
			// be sent over the wire.
			mSocketConnectionHook.deregisterFilter();
			// Terminates socket by shutdownHook.
			toggleHook();
		} else {
			// Terminate socket.
			shutdown();
		}
	}

	public void toggleHook() {
		SocketBasics.togglePermanentHook(getMindMapController(),
				SocketBasics.SLAVE_HOOK_LABEL);
	}

	/**
	 * Sends the lock requests, blocks until timeout or answer and returns the
	 * associated id. Exception otherwise.
	 * 
	 * @throws InterruptedException
	 * @throws UnableToGetLockException
	 */
	public synchronized String sendLockRequest() throws InterruptedException,
			UnableToGetLockException {
		// TODO: Global lock needed?
		mLockId = null;
		int lockTries = 0;
		int timeout = 0;
		while(lockTries < MAX_LOCK_RETRIES) {
			CollaborationRequireLock lockRequest = new CollaborationRequireLock();
			setCurrentState(STATE_WAIT_FOR_LOCK);
			if (!send(lockRequest)) {
				setCurrentState(STATE_IDLE);
				throw new SocketBasics.UnableToGetLockException();
			}
			final int sleepTime = ROUNDTRIP_TIMEOUT / ROUNDTRIP_ROUNDS;
			timeout = ROUNDTRIP_ROUNDS;
			while (getCurrentState() == STATE_WAIT_FOR_LOCK && timeout >= 0) {
				sleep(sleepTime);
				timeout--;
			}
			if(getCurrentState() == STATE_LOCK_RECEIVED) {
				break;
			}
			// error case, repeat lock question.
			lockTries ++;
			logger.info("Didn't receive a lock, current try: " + lockTries);
			sleep(LOCK_RETRY_SLEEP_TIME);
		}
		setCurrentState(STATE_IDLE);
		if(lockTries == MAX_LOCK_RETRIES || timeout < 0) {
			throw new SocketBasics.UnableToGetLockException();
		}
		return mLockId;
	}

	void createNewMap(String map) throws IOException {
		{
			// // deregister from old controller:
			// deregisterFilter();
			logger.info("Restoring the map...");
			MindMapController newModeController = (MindMapController) getMindMapController()
					.getMode().createModeController();
			MapAdapter newModel = new MindMapMapModel(getMindMapController()
					.getFrame(), newModeController);
			HashMap IDToTarget = new HashMap();
			StringReader reader = new StringReader(map);
			MindMapNodeModel rootNode = (MindMapNodeModel) newModeController
					.createNodeTreeFromXml(reader, IDToTarget);
			reader.close();
			newModel.setRoot(rootNode);
			rootNode.setMap(newModel);
			getMindMapController().newMap(newModel);
			newModeController.invokeHooksRecursively((NodeAdapter) rootNode,
					newModel);
			setController(newModeController);
			// add new hook
			toggleHook();
			// tell him about this thread.
			Collection activatedHooks = getMindMapController().getRootNode()
					.getActivatedHooks();
			for (Iterator it = activatedHooks.iterator(); it.hasNext();) {
				PermanentNodeHook hook = (PermanentNodeHook) it.next();
				if (hook instanceof SocketConnectionHook) {
					SocketConnectionHook connHook = null;
					connHook = (SocketConnectionHook) hook;
					// Tell the hook about me
					connHook.setClientCommunication(this);
					/* register as listener, as I am a slave. */
					connHook.registerFilter();
					this.mSocketConnectionHook = connHook;
					break;
				}
			}
		}
	}

	/**
	 * @param pNewModeController
	 */
	private void setController(MindMapController pNewModeController) {
		mController = pNewModeController;
	}

	/**
	 * @return
	 */
	private MindMapController getMindMapController() {
		return mController;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see plugins.collaboration.socket.SocketBasics#shutdown()
	 */
	public void shutdown() {
		// we don't want to read anymore, as we send the goodbye anyway!
		commitSuicide();
		try {
			if (!mReceivedGoodbye) {
				// Send only, if own goodbye.
				CollaborationGoodbye goodbye = new CollaborationGoodbye();
				goodbye.setUserId(Tools.getUserName());
				send(goodbye);
				// in between, the socket has been closed.
			}
		} catch (Exception e) {
			freemind.main.Resources.getInstance().logException(e);
		}
		try {
			close();
		} catch (IOException e) {
			freemind.main.Resources.getInstance().logException(e);
		}
	}

	/**
	 * @return
	 */
	public int getPort() {
		return mSocket.getLocalPort();
	}

	public CollaborationUserInformation getUserInfo() {
		return mUserInfo;
	}

}
