package com.team5.aLife.BaseStation;

import java.io.IOException;

import android.database.Cursor;
import android.util.Log;

import com.team5.aLife.BaseStation.DBHandler.DB_Power_History;

public class SerialCommunicatonHandler extends BackgroundServices {
	
	private final static String TAG = "SerialCommunicatonHandler:  ";
	
	private final static int PACKET_SIZE = 4;
	
	private static final int ADDRESS = 0;
	private static final int COMMAND = 1;
	private static final int PARAMETER_ONE = 2;
	private static final int PARAMETER_TWO = 3;
	
	private static final int ZIGBEE_BASESTATION_ADDRESS = 1;
	
	private static byte addressPayload;
	private static byte commandPayload;
	private static byte parameterOnePayload;
	private static byte parameterTwoPayload;
	
	private static byte[] serialSendPacket = new byte[PACKET_SIZE];
	private static byte[] serialReceivePacket = new byte[PACKET_SIZE];
	
	private static boolean sendPacketSent = false;
	private static boolean receivePacketFilled = false;
	
	private static DBHandler db = new DBHandler(LoginActivity.myContext);
 
	public static int pollForNewDevice()
	{
		int tempZigbeeAddress = 0;
		
		//  Gets the status
		addressPayload = 1;
		commandPayload = Command.POLL_FOR_NEW_DEVICE;
		parameterOnePayload = 0;
		parameterTwoPayload = 0;
		
//		sendPacketAndFillReceived( );
//		
//		if (!sendPacketSent || !receivePacketFilled)
//			return 0;
//		else
//			resetSendAndReceiveStatus();
//		
//		tempZigbeeAddress = (int)serialReceivePacket[PARAMETER_TWO];
		
		return tempZigbeeAddress;
			
	}
	
	public static boolean removeDeviceFromNetwork(int zigbeeAddress)
	{
		//  Gets the status
		addressPayload = ZIGBEE_BASESTATION_ADDRESS;
		commandPayload = Command.REMOVE_DEVICE;
		parameterOnePayload = 0;
		parameterTwoPayload = (byte)zigbeeAddress;
		
		sendPacketAndFillReceived( );
		
		if (!sendPacketSent || !receivePacketFilled)
			return false;
		else
			resetSendAndReceiveStatus();
		
		int result = (int)serialReceivePacket[PARAMETER_TWO];
		
		return (result == zigbeeAddress);
	}
	
	public static Device getDeviceState(Device selectedDevice) {
		// TODO Auto-generated method stub
		//  Will take in a device object, update selected devices information and update the database.
		//  Returns a device object if necessary
		
		switch (selectedDevice.getDeviceType()){
	 	   
	 	   case Device.DEVICE_TYPE_THERMOSTAT:
	 		   return getDeviceStateThermostat(selectedDevice);
	 	   case Device.DEVICE_TYPE_POWER_MONITOR_SWITCH:
	 		   return getDeviceStatePowerSwitch(selectedDevice);
	 	   case Device.DEVICE_TYPE_SWITCH:
	 		   return getDeviceStateSwitch(selectedDevice);
	 	   case Device.DEVICE_TYPE_WIRELESS_SENSOR:
	 		   return getDeviceStateSensor(selectedDevice);
	 	   default: return null;
	 	   
		}
	}
	
	public static boolean activateDevice(Device selectedDevice) {
		
		//  Activates the devices
		addressPayload = Byte.parseByte(selectedDevice.getDeviceAddress());
		if (selectedDevice.getDeviceType() == Device.DEVICE_TYPE_POWER_MONITOR_SWITCH)
			commandPayload = Command.SET_POWER_STATUS;
		else
			commandPayload = Command.GD_SET_ACT;
		parameterOnePayload = 0;
		parameterTwoPayload = Status.ON;
		
		if (commandPayload == Device.DEVICE_TYPE_POWER_MONITOR_SWITCH && BackgroundServices.powerSwitchNotificationAlreadySetup)
			BackgroundServices.powerSwitchStateChangedToOn = true;
		else if ((commandPayload == Device.DEVICE_TYPE_SWITCH || commandPayload == Device.DEVICE_TYPE_WIRELESS_SENSOR) && BackgroundServices.garageDoorNotificationAlreadySetup)
			BackgroundServices.garageDoorStateChangedToOn = true;
			
		sendPacketAndFillReceived( );
		
		if (!sendPacketSent || !receivePacketFilled)
			return false;
		else
			resetSendAndReceiveStatus();
		
		selectedDevice.setDeviceStatus(Status.ON);
		
		updateDeviceInDatabase(selectedDevice);
		
		return true;
	}

	public static boolean deactivateDevice(Device selectedDevice) {
		
		//  Deactivates the device
		addressPayload = Byte.parseByte(selectedDevice.getDeviceAddress());
		if (selectedDevice.getDeviceType() == Device.DEVICE_TYPE_POWER_MONITOR_SWITCH)
			commandPayload = Command.SET_POWER_STATUS;
		else
			commandPayload = Command.GD_SET_ACT;
		parameterOnePayload = 0;
		parameterTwoPayload = Status.OFF;
		
		sendPacketAndFillReceived( );
		
		if (!sendPacketSent || !receivePacketFilled)
			return false;
		else
			resetSendAndReceiveStatus();
		
		selectedDevice.setDeviceStatus(Status.OFF);
		
		updateDeviceInDatabase(selectedDevice);
		
		return true;
	}

	private static Device getDeviceStateSensor(Device selectedDevice) {
		//  Need to make call to serial data and update the object before sending it back
		
		int tempStatus = 0;
		
		//  Gets the status
		addressPayload = Byte.parseByte(selectedDevice.getDeviceAddress());
		commandPayload = Command.DOOR_GET_STATUS;
		parameterOnePayload = 0;
		parameterTwoPayload = 0;
		
		sendPacketAndFillReceived( );
		
		if (!sendPacketSent || !receivePacketFilled)
			return null;
		else
			resetSendAndReceiveStatus();
		
		tempStatus = (int)serialReceivePacket[PARAMETER_TWO];
		
		selectedDevice.setDeviceStatus(tempStatus);
		
		updateDeviceInDatabase(selectedDevice);
		
		return selectedDevice;
	}

	private static Device getDeviceStateSwitch(Device selectedDevice) {
		//  Need to make call to serial data and update the object before sending it back
		
		int tempStatus = 0;
		
		//  Gets the status
		addressPayload = Byte.parseByte(selectedDevice.getDeviceAddress());
		commandPayload = Command.DOOR_GET_STATUS;
		parameterOnePayload = 0;
		parameterTwoPayload = 0;
		
		sendPacketAndFillReceived( );
		
		if (!sendPacketSent || !receivePacketFilled)
			return null;
		else
			resetSendAndReceiveStatus();
		
		tempStatus = (int)serialReceivePacket[PARAMETER_TWO];
		
		selectedDevice.setDeviceStatus(tempStatus);
		
		updateDeviceInDatabase(selectedDevice);
		
		return selectedDevice;
	}

	private static Device getDeviceStatePowerSwitch(Device selectedDevice) {
		//  Need to make call to serial data and update the object before sending it back
		
		int tempStatus = 0;
		int tempHighBitsInstantPower;
		int tempLowBitsInstantPower;
		int tempInstantPower = 0;
		int tempTotalPower = 0;
		
		//  Gets the status
		addressPayload = Byte.parseByte(selectedDevice.getDeviceAddress());
		commandPayload = Command.GET_POWER_STATUS;
		parameterOnePayload = 0;
		parameterTwoPayload = 0;
		
		sendPacketAndFillReceived( );
		
		if (!sendPacketSent || !receivePacketFilled)
			return null;
		else
			resetSendAndReceiveStatus();
		
		tempStatus = (int)serialReceivePacket[PARAMETER_TWO];
		
		//  Gets the instantaneous power 
		addressPayload = Byte.parseByte(selectedDevice.getDeviceAddress());
		commandPayload = Command.GET_POWER;
		parameterOnePayload = 0;
		parameterTwoPayload = 0;
		
		sendPacketAndFillReceived( );
		
		if (!sendPacketSent || !receivePacketFilled)
			return null;
		else
			resetSendAndReceiveStatus();
		
		tempHighBitsInstantPower = (int)serialReceivePacket[PARAMETER_ONE];
		tempLowBitsInstantPower = (int)serialReceivePacket[PARAMETER_TWO];
		
		//  Bitwsie shift the high bits and then bitwise-or them back together
		tempHighBitsInstantPower <<= 8;
		
		tempInstantPower = tempHighBitsInstantPower | tempLowBitsInstantPower;
		
		//  Put recorded data in the database
		addInstantPowerToDatabase(selectedDevice.getDeviceID(), tempInstantPower);
		
		//  Calculate total power usage
		tempTotalPower = caculateTotalPower();
		
		PowerSwitchDevice updatedDevice = new PowerSwitchDevice(selectedDevice.getDeviceID(),
				 selectedDevice.getDeviceType(), 
				 selectedDevice.getDeviceName(), 
				 selectedDevice.getDeviceAddress(),
				 Status.ON, 
				 tempStatus, 
				 tempInstantPower, 
				 tempTotalPower);

		
		updateDeviceInDatabase(updatedDevice);
		
		return updatedDevice; 
	}
	
	
	private static Device getDeviceStateThermostat(Device selectedDevice) {
		//  Need to make call to serial data and update the object before sending it back
		
		int tempZigbeeStatus = 1;
		int tempDeviceStatus = 0;
		int tempCurrentTemp = 0;
		int tempSetTemp = 0;
		String tempMode = "";
		
		
		//  Gets the mode
		addressPayload = Byte.parseByte(selectedDevice.getDeviceAddress());
		commandPayload = Command.GET_MODE;
		parameterOnePayload = 0;
		parameterTwoPayload = 0;
		
		sendPacketAndFillReceived( );
		
		if (!sendPacketSent || !receivePacketFilled)
			return null;
		else
			resetSendAndReceiveStatus();

		switch ((int)serialReceivePacket[PARAMETER_TWO]){
		//  Sets the temp mode and zigbee state
		case Mode.OFF:
			tempMode = "Off";
			tempDeviceStatus = 0;
			break;
		case Mode.COOL:
			tempMode = "Cool";
			tempDeviceStatus = 1;
			break;
		case Mode.HEAT:
			tempMode = "Heat";
			tempDeviceStatus = 1;
			break;
		case Mode.FAN:
			tempMode = "Fan";
			tempDeviceStatus = 1;
			break;
		default:
			break;
		
		}
		
		//  Gets the current set temp
		addressPayload = Byte.parseByte(selectedDevice.getDeviceAddress());
		commandPayload = Command.GET_TEMP_LIMIT_H;
		parameterOnePayload = 0;
		parameterTwoPayload = 0;
		
		sendPacketAndFillReceived( );
		
		if (!sendPacketSent || !receivePacketFilled)
			return null;
		else
			resetSendAndReceiveStatus();
		
		tempSetTemp = serialReceivePacket[PARAMETER_TWO];
		
		//  Gets the current temp
		addressPayload = Byte.parseByte(selectedDevice.getDeviceAddress());
		commandPayload = Command.GET_CURRENT_TEMP;
		parameterOnePayload = 0;
		parameterTwoPayload = 0;
		
		sendPacketAndFillReceived( );
		
		if (!sendPacketSent || !receivePacketFilled)
			return null;
		else
			resetSendAndReceiveStatus();
		
		tempCurrentTemp = serialReceivePacket[PARAMETER_TWO];
		
		Thermostat updatedDevice = new Thermostat(selectedDevice.getDeviceID(),
				  selectedDevice.getDeviceType(), 
				  selectedDevice.getDeviceName(), 
				  selectedDevice.getDeviceAddress(),
				  tempZigbeeStatus ,   
				  tempDeviceStatus,   
				  tempCurrentTemp,								  
				  tempSetTemp,								  
				  tempMode);
			
		updateDeviceInDatabase(updatedDevice);	
		
		return updatedDevice;				  
	}

	public static boolean setThermostat(Thermostat updatedThermostat) {
		//  Need to take the information from the thermostat object and send it serially to the zigbee base
		
		//  Send the Mode
		addressPayload = Byte.parseByte(updatedThermostat.getDeviceAddress());
		commandPayload = Command.SET_MODE;
		parameterOnePayload = 0;
		parameterTwoPayload = 0;
		
		if (updatedThermostat.getCurrentMode().equals("Cool"))
		{
			parameterTwoPayload = Mode.COOL;
		}
		else if(updatedThermostat.getCurrentMode().equals("Heat"))
		{
			parameterTwoPayload = Mode.HEAT;
		}
		else if (updatedThermostat.getCurrentMode().equals("Fan"))
		{
			parameterTwoPayload = Mode.FAN;
		}
		else
		{
			parameterTwoPayload = Mode.OFF;
		}
		
		sendPacketAndFillReceived( );
		
		if (!sendPacketSent || !receivePacketFilled)
			return false;
		else
			resetSendAndReceiveStatus();
		
		//  Send the Set Temp
		addressPayload = Byte.parseByte(updatedThermostat.getDeviceAddress());
		commandPayload = Command.SET_TEMP_LIMIT_H;
		parameterOnePayload = 0;
		parameterTwoPayload = (byte)updatedThermostat.getCurrentSetTemp();
		
		sendPacketAndFillReceived( );
		
		if (!sendPacketSent || !receivePacketFilled)
			return false;
		else
			resetSendAndReceiveStatus();
		
		return true;
	}

	private static void sendPacketAndFillReceived( )
	{
		serialSendPacket[ADDRESS] = addressPayload;
		serialSendPacket[COMMAND] = commandPayload;
		serialSendPacket[PARAMETER_ONE] = parameterOnePayload;
		serialSendPacket[PARAMETER_TWO] = parameterTwoPayload;
		
		Log.i(TAG+"sendPacketAndFillReceived", "About to right to outputstream" + "\n"
			  + "addressPayload:  " + addressPayload + "\n"
			  + "commandPayload:  " + commandPayload + "\n"
			  + "parameterOnePayload:  " + parameterOnePayload + "\n"
			  + "parameterTwoPayload:  " + parameterTwoPayload);
		if (mOutputStream != null && mInputStream != null)
		{
			try {
				mOutputStream.write(serialSendPacket);
				sendPacketSent = true;
				Log.i(TAG+"sendPacketAndFillReceived", "Should have wrote data");
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
			try {
				int header = 0;
				Log.e(TAG+"sendPacketAndFillReceived", "I am about to read");
				//while (header != 255)
					//header = mInputStream.read();
				mInputStream.read(serialReceivePacket, 0, 4);
				receivePacketFilled = true;
				Log.e(TAG+"sendPacketAndFillReceived", "Should have received the data" + "\n"
						  + "addressPayload:  " + serialReceivePacket[0] + "\n"
						  + "commandPayload:  " + serialReceivePacket[1] + "\n"
						  + "parameterOnePayload:  " + serialReceivePacket[2] + "\n"
						  + "parameterTwoPayload:  " + serialReceivePacket[3]);
				Log.e(TAG+"sendPacketAndFillReceived", new String(serialReceivePacket, 0, 
					  serialReceivePacket.length));
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
	}
	
	private static void resetSendAndReceiveStatus()
	{
		sendPacketSent = false;
		receivePacketFilled = false;
	}
	
	private static void addInstantPowerToDatabase(int deviceID, int powerConsumption) {
		
		db.open();
		
		db.insertPowerHistoryLogEntry(deviceID, (int)(System.currentTimeMillis()/1000), powerConsumption);
		
		db.close();
	}
	
	private static int caculateTotalPower() {
		// TODO Auto-generated method stub
		
		int runningPowerTotal = 0;
		int timeBeggining = 0;
		int timeEnd = 0;
		int count = 0;
		
		db.open();
		
		Cursor c = db.getPowerHistoryLog();
		
		int instantPowerCol = c.getColumnIndex(DB_Power_History.POWER_CONSUMPTION);
		int timeCol = c.getColumnIndex(DB_Power_History.TIME);
		
		// Check if our result was valid. 
        if (c != null) {
        	
                // Check if at least one Result was returned. 
                if (c.moveToFirst()) {
                	
                	count++;
                	do{
	                    //  Calculate energy totals and add them up 
	                    int instantPower = c.getInt(instantPowerCol);
	                    int time = c.getInt(timeCol);
                    
	                    if (count == 1)
	                    	timeBeggining = time;
	                    else
	                    	timeEnd = time;
                    
	                    runningPowerTotal += instantPower;
	                    
                	} while (c.moveToNext());
   
                }
        }
		
		db.close();
		
		int totalEnergy = (runningPowerTotal / count) * (timeEnd - timeBeggining);
		
		return totalEnergy;
	}
	
	public static void updateDeviceInDatabase(Device device)
	{
		db.open();
		
		db.updateDevice(device);
		
		db.close();
	}

}

final class Command {
	
	public static final byte GET_MODE = 0;
    public static final byte SET_MODE = 1;
    public static final byte GET_TEMP_LIMIT_L = 2;
    public static final byte SET_TEMP_LIMIT_L = 3;
    public static final byte GET_TEMP_LIMIT_H = 4;
    public static final byte SET_TEMP_LIMIT_H = 5;
    public static final byte GET_CURRENT_TEMP = 6;
    
    public static final byte GET_POWER_STATUS = 7;
    public static final byte SET_POWER_STATUS = 8;
    public static final byte GET_POWER = 9;
    
    public static final byte GD_SET_ACT = 10;
    public static final byte DOOR_GET_STATUS = 11;
    public static final byte POLL_FOR_NEW_DEVICE = 12;
    public static final byte REMOVE_DEVICE = 13;
	
}

final class Mode {
	
	public static final byte OFF = 0;
    public static final byte COOL = 1;
    public static final byte HEAT = 2;
    public static final byte FAN = 3;
   
}

final class Status {
	
	public static final byte OFF = 0;
    public static final byte ON = 1;
    
    public static final byte CLOSED = 0;
    public static final byte OPENED = 1;
	
}