/**
 * Copyright (c) 2010-2018 by the respective copyright holders.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 */
package org.openhab.binding.greeair.internal.discovery;

import java.io.StringReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;

import org.openhab.binding.greeair.handler.GreeAirHandler;
import org.openhab.binding.greeair.internal.encryption.CryptoUtil;
import org.openhab.binding.greeair.internal.gson.GreeBindRequest4Gson;
import org.openhab.binding.greeair.internal.gson.GreeBindRequestPack4Gson;
import org.openhab.binding.greeair.internal.gson.GreeBindResponse4Gson;
import org.openhab.binding.greeair.internal.gson.GreeBindResponsePack4Gson;
import org.openhab.binding.greeair.internal.gson.GreeExecCommand4Gson;
import org.openhab.binding.greeair.internal.gson.GreeExecResponse4Gson;
import org.openhab.binding.greeair.internal.gson.GreeExecResponsePack4Gson;
import org.openhab.binding.greeair.internal.gson.GreeExecuteCommandPack4Gson;
import org.openhab.binding.greeair.internal.gson.GreeReqStatus4Gson;
import org.openhab.binding.greeair.internal.gson.GreeReqStatusPack4Gson;
import org.openhab.binding.greeair.internal.gson.GreeScanResponse4Gson;
import org.openhab.binding.greeair.internal.gson.GreeStatusResponse4Gson;
import org.openhab.binding.greeair.internal.gson.GreeStatusResponsePack4Gson;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.gson.Gson;
import com.google.gson.stream.JsonReader;

/**
 * The GreeDevice object repesents a Gree Airconditioner and provides
 * device specific attributes as well a the functionality for the Air Conditioner
 *
 * @author John Cunha - Initial contribution
 */

public class GreeDevice {
    private final static Charset UTF8_CHARSET = Charset.forName("UTF-8");
    private final static HashMap<String, HashMap<String,Integer>> tempRanges = createTempRangeMap();
    private Boolean mIsBound = false;
    private InetAddress mAddress;
    private int mPort = 0;
    private String mKey;
    private GreeScanResponse4Gson mScanResponseGson = null;
    private GreeBindResponse4Gson bindResponseGson = null;
    private GreeStatusResponse4Gson statusResponseGson = null;
    private GreeStatusResponsePack4Gson prevStatusResponsePackGson = null;
    private final Logger logger = LoggerFactory.getLogger(GreeAirHandler.class);

    public Boolean getIsBound() {
        return mIsBound;
    }

    public void setIsBound(Boolean isBound) {
        this.mIsBound = isBound;
    }

    public InetAddress getAddress() {
        return mAddress;
    }

    public void setAddress(InetAddress address) {
        this.mAddress = address;
    }

    public int getPort() {
        return mPort;
    }

    public void setPort(int port) {
        this.mPort = port;
    }

    public String getKey() {
        return mKey;
    }

    public String getName() {
        return mScanResponseGson.packJson.name;
    }

    public String getId() {
        return mScanResponseGson.packJson.mac;
    }

    public GreeScanResponse4Gson getScanResponseGson() {
        return mScanResponseGson;
    }

    public void setScanResponseGson(GreeScanResponse4Gson gson) {
        mScanResponseGson = gson;
    }

    public GreeBindResponse4Gson getBindResponseGson() {
        return bindResponseGson;
    }

    public GreeStatusResponse4Gson getGreeStatusResponse4Gson() {
        return statusResponseGson;
    }

    public void BindWithDevice(DatagramSocket clientSocket) throws Exception {
        byte[] sendData = new byte[1024];
        byte[] receiveData = new byte[347];
        Gson gson = new Gson();

        // Prep the Binding Request pack
        GreeBindRequestPack4Gson bindReqPackGson = new GreeBindRequestPack4Gson();
        bindReqPackGson.mac = getId();
        bindReqPackGson.t = "bind";
        bindReqPackGson.uid = 0;
        String bindReqPackStr = gson.toJson(bindReqPackGson);

        // Now Encrypt the Binding Request pack
        String encryptedBindReqPacket = CryptoUtil.encryptPack(CryptoUtil.GetAESGeneralKeyByteArray(), bindReqPackStr);

        // Prep the Binding Request
        GreeBindRequest4Gson bindReqGson = new GreeBindRequest4Gson();
        bindReqGson.cid = "app";
        bindReqGson.i = 1;
        bindReqGson.t = "pack";
        bindReqGson.uid = 0;
        bindReqGson.pack = new String(encryptedBindReqPacket.getBytes(), UTF8_CHARSET);
        String bindReqStr = gson.toJson(bindReqGson);
        sendData = bindReqStr.getBytes();

        // Now Send the request
        DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, getAddress(), getPort());
        clientSocket.send(sendPacket);

        // Recieve a response
        DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
        clientSocket.receive(receivePacket);
        String modifiedSentence = new String(receivePacket.getData());

        // Read the response
        StringReader stringReader = new StringReader(modifiedSentence);
        bindResponseGson = gson.fromJson(new JsonReader(stringReader), GreeBindResponse4Gson.class);
        bindResponseGson.decryptedPack = CryptoUtil.decryptPack(CryptoUtil.GetAESGeneralKeyByteArray(),
                bindResponseGson.pack);

        // Create the JSON to hold the response values
        stringReader = new StringReader(bindResponseGson.decryptedPack);
        bindResponseGson.packJson = gson.fromJson(new JsonReader(stringReader), GreeBindResponsePack4Gson.class);

        // Now set the key and flag to indicate the bind was succesful
        mKey = bindResponseGson.packJson.key;
        setIsBound(Boolean.TRUE);
    }

    public void SetDevicePower(DatagramSocket clientSocket, Integer value) throws Exception {
        // Only allow this to happen if this device has been bound and values are valid
        if ((!Objects.equals(getIsBound(), Boolean.TRUE)) || (value.intValue() < 0 || value.intValue() > 1)) {
            return;
        }

        // Set the values in the HashMap
        HashMap<String, Integer> parameters = new HashMap<>();
        parameters.put("Pow", value);
        ExecuteCommand(clientSocket, parameters);
    }

    public Integer GetDevicePower() {
        return GetIntStatusVal("Pow");
    }

    public void SetDeviceMode(DatagramSocket clientSocket, Integer value) throws Exception {
        // Only allow this to happen if this device has been bound and values are valid
        if ((!Objects.equals(getIsBound(), Boolean.TRUE)) || (value.intValue() < 0 || value.intValue() > 4)) {
            return;
        }

        // Set the values in the HashMap
        HashMap<String, Integer> parameters = new HashMap<>();
        parameters.put("Mod", value);
        ExecuteCommand(clientSocket, parameters);
    }

    public Integer GetDeviceMode() {
        return GetIntStatusVal("Mod");
    }

    public void SetDeviceSwingVertical(DatagramSocket clientSocket, Integer value) throws Exception {
        // Only allow this to happen if this device has been bound and values are valid
        // Only values 0,1,2,3,4,5,6,10,11 allowed
        if ((!Objects.equals(getIsBound(), Boolean.TRUE)) || (value.intValue() < 0 || value.intValue() > 11)
                || (value.intValue() > 6 && value.intValue() < 10)) {
            return;
        }
        // Set the values in the HashMap
        HashMap<String, Integer> parameters = new HashMap<>();
        parameters.put("SwUpDn", value);
        ExecuteCommand(clientSocket, parameters);
    }

    public Integer GetDeviceSwingVertical() {
        return GetIntStatusVal("SwUpDn");
    }

    public void SetDeviceWindspeed(DatagramSocket clientSocket, Integer value) throws Exception {
        // Only allow this to happen if this device has been bound and values are valid
        /*
         * Possible values are :
         * 0 : Auto
         * 1 : Low
         * 2 : Medium Low
         * 3 : Medium
         * 4 : Medium High
         * 5 : High
         */
        if ((!Objects.equals(getIsBound(), Boolean.TRUE)) || (value.intValue() < 0 || value.intValue() > 5)) {
            return;
        }

        // Set the values in the HashMap
        HashMap<String, Integer> parameters = new HashMap<>();
        parameters.put("WdSpd", value);
        parameters.put("Quiet", 0);
        parameters.put("Tur", 0);
        parameters.put("NoiseSet", 0);
        ExecuteCommand(clientSocket, parameters);
    }

    public Integer GetDeviceWindspeed() {
        return GetIntStatusVal("WdSpd");
    }

    public void SetDeviceTurbo(DatagramSocket clientSocket, Integer value) throws Exception {
        // Only allow this to happen if this device has been bound and values are valid
        if ((!Objects.equals(getIsBound(), Boolean.TRUE)) || (value.intValue() < 0 || value.intValue() > 1)) {
            return;
        }

        // Set the values in the HashMap
        HashMap<String, Integer> parameters = new HashMap<>();
        parameters.put("Tur", value);
        ExecuteCommand(clientSocket, parameters);
    }

    public Integer GetDeviceTurbo() {
        return GetIntStatusVal("Tur");
    }

    public void SetDeviceLight(DatagramSocket clientSocket, Integer value) throws Exception {
        // Only allow this to happen if this device has been bound and values are valid
        if ((!Objects.equals(getIsBound(), Boolean.TRUE)) || (value.intValue() < 0 || value.intValue() > 1)) {
            return;
        }

        // Set the values in the HashMap
        HashMap<String, Integer> parameters = new HashMap<>();
        parameters.put("Lig", value);
        ExecuteCommand(clientSocket, parameters);
    }

    public Integer GetDeviceLight() {
        return GetIntStatusVal("Lig");
    }

    private static HashMap<String, HashMap<String,Integer>> createTempRangeMap()
    {   //Create Hash Look Up for C and F Temperature Ranges for gree A/C units
        //f_range = {86,61}, c_range= {16,30}
        HashMap<String, HashMap<String,Integer>> tempRanges = new HashMap<>();
        HashMap<String, Integer> hmf = new HashMap<>();
        HashMap<String, Integer> hmc= new HashMap<>();

        hmf.put("min",new Integer(61)); // F
        hmf.put("max",new Integer(86));
        tempRanges.put("F",hmf);

        hmc.put("min",new Integer(16)); //C
        hmc.put("max",new Integer(30));
        tempRanges.put("C",hmc);

        return tempRanges;
    }

    private Integer[] ValidateTemperatureRangeForTempSet(Integer newVal,Integer CorF) {
        // Checks input ranges for validity and TempUn for validity
        // Uses newVal as priority and tries to validate and determine intent
        // For example if value is 75 and TempUn says Celsius, change TempUn to Fahrenheit
        //
        final String[] minMaxLUT = {"max","min"}; // looks up 0 = C = max, 1 = F = min
        final String [] tempScaleLUT = {"C","F"}; //Look Up Table used to convert TempUn integer 0,1 to "C" to "F" string for hashmap
        HashMap<String, Integer> nullCorFLUT = new HashMap<>(); // simple look up table for logic
            nullCorFLUT.put("C",new Integer(0));
            nullCorFLUT.put("F",new Integer(1));
            nullCorFLUT.put("INVALID",new Integer(0));

        String validRangeCorF; // stores if the input range is a valid C or F temperature

        newVal = Math.max(newVal,Math.min(tempRanges.get("C").get("min"),tempRanges.get("F").get("min"))); // force to global min
        newVal = Math.min(newVal,Math.max(tempRanges.get("C").get("max"),tempRanges.get("F").get("max"))); // force to global max

        if ((newVal >= tempRanges.get("C").get("min") ) && ( newVal <= tempRanges.get("C").get("max")) ) {
            validRangeCorF = "C";
        } else if ((newVal >= tempRanges.get("F").get("min") ) && ( newVal <= tempRanges.get("F").get("max"))) {
            validRangeCorF = "F";
        }else{
            logger.warn("Input Temp request {} is invalid",newVal);
            validRangeCorF = "INVALID";
        }

        if (CorF == null){
            // if CorF wasnt initialized or is null set it from lookup
            CorF = nullCorFLUT.get(validRangeCorF);
        }

        if ((CorF == 1) && (validRangeCorF == "C")){ 
            CorF = 0; // input temp takes priority
        }
        else if ((CorF == 0) && (validRangeCorF == "F")){
            CorF = 1; // input temp takes priority
        }
        else if (validRangeCorF == "INVALID"){
            // force min or max temp based on CorF scale to be used
            newVal = tempRanges.get(tempScaleLUT[CorF]).get(minMaxLUT[CorF]);
        }
        
        return new Integer[]{newVal,CorF}; 
    }
    public void SetDeviceTempSet(DatagramSocket clientSocket, Integer value) throws Exception {
        // **value** :  set temperature in degrees celsius or Fahrenheit        
        // Only allow this to happen if this device has been bound
        if (getIsBound() != Boolean.TRUE) {
            return;
        }
        Integer [] retList;
        Integer newVal = new Integer(value);
        Integer outVal = new Integer(value);
        // Get Celsius or Fahrenheit from status message
        Integer CorF = GetIntStatusVal("TemUn");
        // TODO put a param in openhab to allow setting this from the config

        //If commanding Fahrenheit set halfStep to 1 or 0 to tell the A/C which F integer 
        //    temperature to use as celsius alone is ambigious
        Integer halfStep = new Integer(0); //default to C

        retList = ValidateTemperatureRangeForTempSet(newVal,CorF);
        newVal = retList[0];
        CorF = retList[1];

        if (CorF == 1){ //If Fahrenheit, 
            //value argument is degrees F, convert Fahrenheit to Celsius, 
            //SetTem input to A/C always in Celsius despite passing in 1 to TemUn
            outVal = new Integer((int) Math.round((newVal-32.)*5.0/9.0)); //Integer Truncated
            //******************TempRec TemSet Mapping for setting Fahrenheit****************************
            //Fahren = [68. , 69. , 70. , 71. , 72. , 73. , 74. , 75. , 76. , 77. , 78. , 79. , 80. , 81. , 82. , 83. , 84. , 85. , 86. ]
            //Celsiu = [20.0, 20.5, 21.1, 21.6, 22.2, 22.7, 23.3, 23.8, 24.4, 25.0, 25.5, 26.1, 26.6, 27.2, 27.7, 28.3, 28.8, 29.4, 30.0]
            //TemSet = [20,   21,   21,   22,   22,   23,   23,   24,   25,   25,   26,   26,   27,   27,   28,   28,   29,   29,  30, 30]
            //TemRec = [ 1,    0,    1,    0,    1,    0,    1,    0,   1,    0,    1,    0,    1,    0,    1,    0,    1,    0,   1,  0]
            //******************TempRec TemSet Mapping for setting Fahrenheit****************************
            // subtract the float verison - the int version to get the fractional difference
            // if the difference is positive set halfStep to 1, negative to 0
            halfStep = ((((newVal-32.)*5.0/9.0) - outVal) > 0) ? 1 : 0;
        }

        // Set the values in the HashMap
        HashMap<String, Integer> parameters = new HashMap<>();
        parameters.put("TemUn", CorF);
        parameters.put("SetTem",outVal);
        parameters.put("TemRec",halfStep);

        ExecuteCommand(clientSocket, parameters);
    }

    public Integer GetDeviceTempSet() {
        return GetIntStatusVal("SetTem");
    }

    public void SetDeviceAir(DatagramSocket clientSocket, Integer value) throws Exception {
        // Only allow this to happen if this device has been bound
        if (getIsBound() != Boolean.TRUE) {
            return;
        }

        // Set the values in the HashMap
        HashMap<String, Integer> parameters = new HashMap<>();
        parameters.put("Air", value);

        ExecuteCommand(clientSocket, parameters);
    }

    public Integer GetDeviceAir() {
        return GetIntStatusVal("Air");
    }

    public void SetDeviceDry(DatagramSocket clientSocket, Integer value) throws Exception {
        // Only allow this to happen if this device has been bound
        if (getIsBound() != Boolean.TRUE) {
            return;
        }

        // Set the values in the HashMap
        HashMap<String, Integer> parameters = new HashMap<>();
        parameters.put("Blo", value);

        ExecuteCommand(clientSocket, parameters);
    }

    public Integer GetDeviceDry() {
        return GetIntStatusVal("Blo");
    }

    public void SetDeviceHealth(DatagramSocket clientSocket, Integer value) throws Exception {
        // Only allow this to happen if this device has been bound
        if (getIsBound() != Boolean.TRUE) {
            return;
        }

        // Set the values in the HashMap
        HashMap<String, Integer> parameters = new HashMap<>();
        parameters.put("Health", value);

        ExecuteCommand(clientSocket, parameters);
    }

    public Integer GetDeviceHealth() {
        return GetIntStatusVal("Health");
    }

    public void SetDevicePwrSaving(DatagramSocket clientSocket, Integer value) throws Exception {
        // Only allow this to happen if this device has been bound
        if (getIsBound() != Boolean.TRUE) {
            return;
        }

        // Set the values in the HashMap
        HashMap<String, Integer> parameters = new HashMap<>();
        parameters.put("SvSt", value);
        parameters.put("WdSpd", new Integer(0));
        parameters.put("Quiet", new Integer(0));
        parameters.put("Tur", new Integer(0));
        parameters.put("SwhSlp", new Integer(0));
        parameters.put("SlpMod", new Integer(0));

        ExecuteCommand(clientSocket, parameters);
    }

    public Integer GetDevicePwrSaving() {
        return GetIntStatusVal("SvSt");
    }

    public Integer GetIntStatusVal(String valueName) {
        /*
         * Note : Values can be:
         * "Pow": Power (0 or 1)
         * "Mod": Mode: Auto: 0, Cool: 1, Dry: 2, Fan: 3, Heat: 4
         * "SetTem": Requested Temperature
         * "WdSpd": Fan Speed : Low:1, Medium Low:2, Medium :3, Medium High :4, High :5
         * "Air": Air Mode Enabled
         * "Blo": Dry
         * "Health": Health
         * "SwhSlp": Sleep
         * "SlpMod": ???
         * "Lig": Light On
         * "SwingLfRig": Swing Left Right
         * "SwUpDn": Swing Up Down: // Ceiling:0, Upwards : 10, Downwards : 11, Full range : 1
         * "Quiet": Quiet mode
         * "Tur": Turbo
         * "StHt": 0,
         * "TemUn": Temperature unit, 0 for Celsius, 1 for Fahrenheit
         * "HeatCoolType"
         * "TemRec": (0 or 1), Send with SetTem, when TemUn==1, distinguishes between upper and lower integer Fahrenheit temp
         * "SvSt": Power Saving
         */
        // Find the valueName in the Returned Status object
        String columns[] = statusResponseGson.packJson.cols;
        Integer values[] = statusResponseGson.packJson.dat;
        List<String> colList = new ArrayList<>(Arrays.asList(columns));
        List<Integer> valList = new ArrayList<>(Arrays.asList(values));
        int valueArrayposition = colList.indexOf(valueName);
        if (valueArrayposition == -1) {
            return null;
        }

        // Now get the Corresponding value
        Integer value = valList.get(valueArrayposition);
        return value;
    }

    public Boolean HasStatusValChanged(String valueName) {
        if (prevStatusResponsePackGson == null) {
            return Boolean.TRUE;
        }
        // Find the valueName in the Current Status object
        String currcolumns[] = statusResponseGson.packJson.cols;
        Integer currvalues[] = statusResponseGson.packJson.dat;
        List<String> currcolList = new ArrayList<>(Arrays.asList(currcolumns));
        List<Integer> currvalList = new ArrayList<>(Arrays.asList(currvalues));
        int currvalueArrayposition = currcolList.indexOf(valueName);
        if (currvalueArrayposition == -1) {
            return null;
        }
        // Now get the Corresponding value
        Integer currvalue = currvalList.get(currvalueArrayposition);

        // Find the valueName in the Previous Status object
        String prevcolumns[] = prevStatusResponsePackGson.cols;
        Integer prevvalues[] = prevStatusResponsePackGson.dat;
        List<String> prevcolList = new ArrayList<>(Arrays.asList(prevcolumns));
        List<Integer> prevvalList = new ArrayList<>(Arrays.asList(prevvalues));
        int prevvalueArrayposition = prevcolList.indexOf(valueName);
        if (prevvalueArrayposition == -1) {
            return null;
        }
        // Now get the Corresponding value
        Integer prevvalue = prevvalList.get(prevvalueArrayposition);

        // Finally Compare the values
        return new Boolean(currvalue.intValue() != prevvalue.intValue());
    }

    protected void ExecuteCommand(DatagramSocket clientSocket, HashMap<String, Integer> parameters) throws Exception {
        byte[] sendData = new byte[1024];
        byte[] receiveData = new byte[1024];
        Gson gson = new Gson();

        // Convert the parameter map values to arrays
        String[] keyArray = parameters.keySet().toArray(new String[0]);
        Integer[] valueArray = parameters.values().toArray(new Integer[0]);

        // Prep the Command Request pack
        GreeExecuteCommandPack4Gson execCmdPackGson = new GreeExecuteCommandPack4Gson();
        execCmdPackGson.opt = keyArray;
        execCmdPackGson.p = valueArray;
        execCmdPackGson.t = "cmd";
        String execCmdPackStr = gson.toJson(execCmdPackGson);

        // Now Encrypt the Binding Request pack
        String encryptedCommandReqPacket = CryptoUtil.encryptPack(getKey().getBytes(), execCmdPackStr);
        // String unencryptedCommandReqPacket = CryptoUtil.decryptPack(device.getKey().getBytes(),
        // encryptedCommandReqPacket);

        // Prep the Command Request
        GreeExecCommand4Gson execCmdGson = new GreeExecCommand4Gson();
        execCmdGson.cid = "app";
        execCmdGson.i = 0;
        execCmdGson.t = "pack";
        execCmdGson.uid = 0;
        execCmdGson.pack = new String(encryptedCommandReqPacket.getBytes(), UTF8_CHARSET);
        String execCmdStr = gson.toJson(execCmdGson);
        sendData = execCmdStr.getBytes();
        DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, getAddress(), getPort());
        clientSocket.send(sendPacket);

        // Recieve a response
        DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
        clientSocket.receive(receivePacket);
        String modifiedSentence = new String(receivePacket.getData());
        // System.out.println("FROM SERVER:" + modifiedSentence);
        // byte[] modifiedSentenceArray = receivePacket.getData();

        // Read the response
        StringReader stringReader = new StringReader(modifiedSentence);
        GreeExecResponse4Gson execResponseGson = gson.fromJson(new JsonReader(stringReader),
                GreeExecResponse4Gson.class);
        execResponseGson.decryptedPack = CryptoUtil.decryptPack(this.getKey().getBytes(), execResponseGson.pack);

        // Create the JSON to hold the response values
        stringReader = new StringReader(execResponseGson.decryptedPack);
        execResponseGson.packJson = gson.fromJson(new JsonReader(stringReader), GreeExecResponsePack4Gson.class);

    }

    public void getDeviceStatus(DatagramSocket clientSocket) throws Exception {
        Gson gson = new Gson();
        byte[] sendData = new byte[1024];
        byte[] receiveData = new byte[1024];

        // Set the values in the HashMap
        ArrayList<String> columns = new ArrayList<>();
        columns.add("Pow");
        columns.add("Mod");
        columns.add("SetTem");
        columns.add("WdSpd");
        columns.add("Air");
        columns.add("Blo");
        columns.add("Health");
        columns.add("SwhSlp");
        columns.add("Lig");
        columns.add("SwingLfRig");
        columns.add("SwUpDn");
        columns.add("Quiet");
        columns.add("Tur");
        columns.add("StHt");
        columns.add("TemUn");
        columns.add("HeatCoolType");
        columns.add("TemRec");
        columns.add("SvSt");
        columns.add("NoiseSet");

        // Convert the parameter map values to arrays
        String[] colArray = columns.toArray(new String[0]);

        // Prep the Command Request pack
        GreeReqStatusPack4Gson reqStatusPackGson = new GreeReqStatusPack4Gson();
        reqStatusPackGson.t = "status";
        reqStatusPackGson.cols = colArray;
        reqStatusPackGson.mac = getId();
        String reqStatusPackStr = gson.toJson(reqStatusPackGson);

        // Now Encrypt the Binding Request pack
        String encryptedStatusReqPacket = CryptoUtil.encryptPack(getKey().getBytes(), reqStatusPackStr);

        // Prep the Status Request
        GreeReqStatus4Gson reqStatusGson = new GreeReqStatus4Gson();
        reqStatusGson.cid = "app";
        reqStatusGson.i = 0;
        reqStatusGson.t = "pack";
        reqStatusGson.uid = 0;
        reqStatusGson.pack = new String(encryptedStatusReqPacket.getBytes(), UTF8_CHARSET);
        String execCmdStr = gson.toJson(reqStatusGson);
        sendData = execCmdStr.getBytes();
        DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, getAddress(), getPort());
        clientSocket.send(sendPacket);

        logger.trace("Sending Status request packet to device");

        // Recieve a response
        DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
        clientSocket.receive(receivePacket);
        logger.trace("Status request packet received from device");
        String modifiedSentence = new String(receivePacket.getData());

        // Keep a copy of the old response to be used to check if values have changed
        // If first time running, there will not be a previous GreeStatusResponsePack4Gson
        if (statusResponseGson != null && statusResponseGson.packJson != null) {
            prevStatusResponsePackGson = new GreeStatusResponsePack4Gson(statusResponseGson.packJson);
        }

        // Read the response
        StringReader stringReader = new StringReader(modifiedSentence);
        statusResponseGson = gson.fromJson(new JsonReader(stringReader), GreeStatusResponse4Gson.class);
        statusResponseGson.decryptedPack = CryptoUtil.decryptPack(this.getKey().getBytes(), statusResponseGson.pack);

        logger.trace("Response from device: {}", statusResponseGson.decryptedPack);

        // Create the JSON to hold the response values
        stringReader = new StringReader(statusResponseGson.decryptedPack);

        statusResponseGson.packJson = gson.fromJson(new JsonReader(stringReader), GreeStatusResponsePack4Gson.class);
        UpdateTempFtoC();
    }

    private void UpdateTempFtoC(){
        // Status message back from A/C always reports degrees C
        //    If using Fahrenheit, us SetTem, TemUn and TemRec to 
        //    reconstruct the Fahrenheit temperature
        // Get Celsius or Fahrenheit from status message
        Integer CorF = GetIntStatusVal("TemUn");
        Integer newVal = GetIntStatusVal("SetTem");
        Integer halfStep = GetIntStatusVal("TemRec");

        if (CorF == null  || newVal == null || halfStep == null){
            logger.warn("SetTem,TemUn or TemRec is invalid, not performing conversion");
        }
        else if (CorF == 1){    //convert SetTem to Fahrenheit
            // Find the valueName in the Returned Status object
            String columns[] = statusResponseGson.packJson.cols;
            Integer values[] = statusResponseGson.packJson.dat;
            List<String> colList = new ArrayList<>(Arrays.asList(columns));
            int valueArrayposition = colList.indexOf("SetTem");
            if (valueArrayposition != -1) {
                //convert Celsius to Fahrenheit,
                //SetTem status returns degrees C regardless of TempUn setting
                
                // Perform the float Celsius to Fahrenheit conversion
                //     add or subtract 0.5 based on the value of TemRec
                //     (0 = -0.5, 1 = +0.5)
                //     Pass into a rounding function, this yeild the correct Fahrenheit 
                //     Temperature to match A/C display
                newVal = new Integer((int) Math.round(((newVal*9.0/5.0)+32.0)+ halfStep-0.5));
                
                //Update the status array with F temp , 
                //    assume this is updating the array in situ
                values[valueArrayposition] = newVal;
            }
        }
    }
}