package de.tudarmstadt.informatik.hostage.sync.bluetooth;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;

import de.tudarmstadt.informatik.hostage.logging.NetworkRecord;
import de.tudarmstadt.informatik.hostage.logging.SyncInfoRecord;
import de.tudarmstadt.informatik.hostage.persistence.HostageDBOpenHelper;
import de.tudarmstadt.informatik.hostage.sync.SyncMessage;
import de.tudarmstadt.informatik.hostage.sync.tracing.TracingSyncService;


import android.bluetooth.BluetoothSocket;
import android.content.Context;
import android.os.Handler;
import android.util.Log;

/**
 * CommunicationThread is responsible for the exchange of synchronization messages between devices.
 * @author Lars Pandikow
 */
public class CommunicationThread extends Thread {
	private final Context context;
	private final BluetoothSocket mmSocket;
	private final ObjectInputStream objectInput;
	private final ObjectOutputStream objectOuput;
	private final Handler mHandler;
	private final HostageDBOpenHelper mdbh;


	public CommunicationThread(Context con, BluetoothSocket socket, Handler handler) {
		mmSocket = socket;
		mHandler = handler;
		context = con;
		mdbh = new HostageDBOpenHelper(context);		
		ObjectInputStream tmpIn = null;
		ObjectOutputStream tmpOut = null;
		

		// Get the input and output streams, using temp objects because
		// member streams are final
		try {
			tmpOut = new ObjectOutputStream(socket.getOutputStream());
			tmpIn = new ObjectInputStream(socket.getInputStream());
		} catch (IOException e) {
			mHandler.obtainMessage(BluetoothSyncActivity.CONNECTION_FAILED).sendToTarget();
			e.printStackTrace();
		}

		objectInput = tmpIn;
		objectOuput = tmpOut;
	}

	/* Call this from the main activity to shutdown the connection */
	public void cancel() {
		try {
			mmSocket.close();
		} catch (IOException e) {
		}
	}

	@Override
	public void run() {	
		HashMap<String, Long> devices = mdbh.getSyncDevices();
		write(new SyncMessage(SyncMessage.SYNC_REQUEST, devices));
		// Keep listening to the InputStream until an exception occurs
		while (true) {
		try {
			Object inputObject = objectInput.readObject();
			if(inputObject instanceof SyncMessage){
				handleMessage((SyncMessage) inputObject);				
			}		
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
			break;
		}
		}
	}
	
	/**
	 * Handles a received synchronization message.
	 * @param message The received message.
	 */
	private void handleMessage(SyncMessage message){
		Log.i("CommunicationThread", "Recieved: " + message.getMessage_code());
		switch(message.getMessage_code()){
			case SyncMessage.SYNC_REQUEST:
				HashMap<String, Long> devices_remote = (HashMap<String, Long>) message.getPayload();
				HashMap<String, Long> devices_local = mdbh.getSyncDevices();
				ArrayList<SyncInfoRecord> syncInfo = mdbh.getSyncInfo();

				long tracing_timestamp = 0;
				if(devices_local.containsKey(TracingSyncService.REMOTE_DEVICE))
					tracing_timestamp = devices_local.get(TracingSyncService.REMOTE_DEVICE);				
					
				for(Iterator<String> i = devices_remote.keySet().iterator(); i.hasNext(); ){
					String key = i.next();
					if((devices_local.containsKey(key) && devices_local.get(key) >= devices_remote.get(key)) 
					    || (tracing_timestamp > devices_remote.get(key))){
						i.remove();
					}
				}
				mdbh.updateSyncDevices(devices_remote);
				
				for ( Iterator<SyncInfoRecord> i = syncInfo.iterator(); i.hasNext(); ){
					SyncInfoRecord info = i.next();
					if(devices_remote.containsKey(info.getDeviceID())){
						i.remove();
					}				    
				}				
								
				write(new SyncMessage(SyncMessage.SYNC_RESPONSE_SYNC_INFO, syncInfo));
				write(new SyncMessage(SyncMessage.SYNC_RESPONSE_NET_INFO, mdbh.getNetworkInformation()));
				break;			
			case SyncMessage.SYNC_RESPONSE_NET_INFO:
				ArrayList<NetworkRecord> netInfo = (ArrayList<NetworkRecord>) message.getPayload();
				mdbh.updateNetworkInformation(netInfo);	
				mHandler.obtainMessage(BluetoothSyncActivity.SYNC_SUCCESSFUL).sendToTarget();	
				break;			
			case SyncMessage.SYNC_RESPONSE_SYNC_INFO:
				ArrayList<SyncInfoRecord> syncInfo_new = (ArrayList<SyncInfoRecord>) message.getPayload();
				mdbh.updateSyncInfo(syncInfo_new);
				break;
		}		
	}
	
	/**
	 * Send a message to the remote device.
	 * @param message The message to send.
	 */
	public void write(SyncMessage message) {
		try {
			objectOuput.writeObject(message);
		} catch (IOException e) {
			mHandler.obtainMessage(BluetoothSyncActivity.CONNECTION_FAILED).sendToTarget();
			e.printStackTrace();
		}
	}
}	
