using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Xml;
using System.IO;
using System.IO.Ports;
using System.Drawing.Printing;
using HouseHeatingTerminal.Classes;

public enum SCIDevAddress : byte { NA = 0x00, Terminal, GUI, MATLAB };
public enum SCITask : byte { NA = 0x00, RING, ACK, NACK, RTX, ERR, GET, SET, ANS, RST, LOCK, DISCONN, FORBIDDEN, SERGET, SERANS, SERSET };
public enum SCIRegAddress : byte { NA = 0x00, TERM = 0x01, PC, MAT, LM75_1, LM75_2, MCP9808_1, MCP9808_2, MCP9808_3, HTU21D_TEMP, HTU21D_HUM, ROOM_TEMP, WATER_OUT_TEMP, WATER_RETURN_TEMP, AVG_TEMP, OUTDOOR_TEMP, OUTDOOR_HUM, REGULATE_DAY_TEMP, REGULATE_NIGHT_TEMP, REGULATE_TEMP_HYST, REGULATE_WATER_MAX_TEMP, REGULATE_WATER_DIFF, ON_AVG_TEMP, OFF_AVG_TEMP, SYST_TYPE, HEATER, PUMP, AUTOSW, DAY_START_TIME, DAY_END_TIME, PUMP_DELAY_TIME, CLOCK, SERIALIZED };
public enum ConnectionStatus : int { Connecting, Connected, Disconnecting, Disconnected, Error, ConErr };

namespace HouseHeatingTerminal.Classes
{
    static class serialComm
    {
        /*public serialComm()
        {

        }*/

        private static byte timeoutCount = 0;
        private struct SerialStatusRegister
        {
            public static bool CONN;
            public static bool ACK;
            public static bool ACKRQ;
            public static byte ACKAddress;
            public static SCIRegAddress ACKRegAddress;
            public static bool active;
            public static bool TimeOut;
            public static bool waitingForAnswer;
            public static byte connectedAddress = 0x00;
        }
        private static byte[] lastMessage;


        public static SerialPort _serialPort = new SerialPort();



        public static void RingTerminal()
        {
            SendData(Convert.ToByte(Convert.ToInt32(Properties.Settings.Default.terminalAddress, 16)), SCITask.RING, 0, 0, null);
            AckRQ(Convert.ToByte(Convert.ToInt32(Properties.Settings.Default.terminalAddress, 16)), 0);
        }

        public static void DisconnectTerminal()
        {
            SendData(Convert.ToByte(Convert.ToInt32(Properties.Settings.Default.terminalAddress, 16)), SCITask.LOCK, 0, 0, null);
            SerialStatusRegister.CONN = false;
            AckRQ(Convert.ToByte(Convert.ToInt32(Properties.Settings.Default.terminalAddress, 16)), 0);
        }

        private static System.Timers.Timer timeoutTimer = new System.Timers.Timer();

        public static ConnectionStatus OpenConn()
        {
            if (!_serialPort.IsOpen)
            {
                try
                {
                    _serialPort.PortName = Properties.Settings.Default.SerialPort;
                    _serialPort.BaudRate = System.Convert.ToInt32(Properties.Settings.Default.BaudRate);
                    _serialPort.Parity = Parity.None;
                    _serialPort.StopBits = StopBits.One;
                    _serialPort.DataBits = 8;
                    _serialPort.Handshake = Handshake.None;
                    _serialPort.DtrEnable = true;
                    _serialPort.RtsEnable = true;
                    _serialPort.Open();
                    _serialPort.DataReceived += new SerialDataReceivedEventHandler(serialPort_DataReceived);

                    timeoutTimer.Interval = Convert.ToInt32(Properties.Settings.Default.timeoutInterval);
                    timeoutTimer.Elapsed += timeoutTimer_Elapsed;


                    if (_serialPort.IsOpen)
                    {
                        RingTerminal();
                        System.Threading.Thread.Sleep(500);
                        if (serialComm.IsTerminalConnected())
                        {
                            return ConnectionStatus.Connected;
                        }
                        else
                        {
                            return ConnectionStatus.Error;
                        }
                    }
                    else
                    {
                        return ConnectionStatus.ConErr;
                    }

                }
                catch (UnauthorizedAccessException)
                {
                    throw new UnauthorizedAccessException();
                }
                catch (IOException)
                {
                    throw new IOException();
                }
                catch (InvalidOperationException)
                {
                    throw new InvalidOperationException();
                }
            }
            return ConnectionStatus.Error;

        }

        static void timeoutTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            if (SerialStatusRegister.ACKRQ)
            {

                if (timeoutCount == 0)
                {
                    MainForm.NoAnswerTimeout();
                    Retransmit();
                }
                else if (timeoutCount >= 1)
                {
                    SerialStatusRegister.TimeOut = true;
                    timeoutTimer.Stop();
                    timeoutCount = 0;
                    MainForm.TimeoutOccured();
                    MainForm.refreshConnState();
                    SerialStatusRegister.ACKRQ = false;

                }
            }
            timeoutCount++;

        }



        public static bool IsSerialConnected()
        {
            return _serialPort.IsOpen;
        }
        public static bool IsTerminalConnected()
        {
            return SerialStatusRegister.CONN;
        }

        public static bool IsCommActive()
        {
            return SerialStatusRegister.active;
        }

        public static bool IsCommWaitingForAnswer()
        {
            return SerialStatusRegister.waitingForAnswer;
        }

        public static ConnectionStatus CloseConn()
        {
            if (SerialStatusRegister.CONN) DisconnectTerminal();
            while (SerialStatusRegister.CONN && !SerialStatusRegister.TimeOut) ;
            try
            {

                //while (SerialStatusRegister.active) ;
                timeoutTimer.Stop();
                timeoutTimer.Elapsed -= timeoutTimer_Elapsed;
                _serialPort.DataReceived -= serialPort_DataReceived;
                _serialPort.Close();
                if (!_serialPort.IsOpen)
                {
                    return ConnectionStatus.Disconnected;
                }
                else
                {
                    return ConnectionStatus.Error;
                }

            }
            catch (UnauthorizedAccessException)
            {
                return ConnectionStatus.Error;
            }


        }

        public static bool disconnectSerialPort()
        {
            try
            {
                if (!IsTerminalConnected())
                {
                    timeoutTimer.Stop();
                    timeoutTimer.Elapsed -= timeoutTimer_Elapsed;
                    _serialPort.DataReceived -= serialPort_DataReceived;
                    _serialPort.Close();
                    if (!_serialPort.IsOpen)
                    {
                        return true;
                    }
                    else
                    {
                        return false;
                    }
                }

            }
            catch (UnauthorizedAccessException)
            {
                throw new UnauthorizedAccessException();
            }
            return false;
        }

        public static void SendData(byte address, SCITask task, SCIRegAddress register, byte bytesToSend, byte[] dataToSend)
        {
            byte[] data = new byte[6 + bytesToSend];
            data[0] = address;
            data[1] = Convert.ToByte(Convert.ToInt32(Properties.Settings.Default.localAddress, 16));
            data[2] = (byte)task;
            data[3] = (byte)register;
            data[4] = (byte)bytesToSend;
            byte[] header = new List<byte>(data).GetRange(0, 5).ToArray();
            data[5] = createCRC(header);
            if (bytesToSend - 1 > 0)
            {
                int i;
                for (i = 0; i < bytesToSend; i++)
                {
                    data[6 + i] = dataToSend[i];
                }
                Array.Resize(ref data, 7 + bytesToSend);
                data[6 + i] = createCRC(dataToSend);
            }
            lastMessage = data;
            SendToSerial(data);
        }

        public static void GetData(SCIRegAddress register)
        {
            SendData(Convert.ToByte(Convert.ToInt32(Properties.Settings.Default.terminalAddress, 16)), SCITask.GET, register, 0, null);
            AckRQ(Convert.ToByte(Convert.ToInt32(Properties.Settings.Default.terminalAddress, 16)), register);
        }

        public static void ResetTerminal()
        {
            ResetTerm(Convert.ToByte(Convert.ToInt32(Properties.Settings.Default.terminalAddress, 16)));
        }

        private static void ResetTerm(byte dstAddress)
        {
            SendData(Convert.ToByte(Convert.ToInt32(Properties.Settings.Default.terminalAddress, 16)), SCITask.RST, 0, 0, null);
            AckRQ(Convert.ToByte(Convert.ToInt32(Properties.Settings.Default.terminalAddress, 16)), 0);
        }

        private static void SendToSerial(byte[] dataToSend)
        {

            if (_serialPort.IsOpen)
            {
                _serialPort.Write(dataToSend, 0, dataToSend.Length);

            }
            else
            { }
        }

        private static void serialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {

            byte[] headerBuffer, dataBuffer, addressBuffer;
            addressBuffer = new byte[1];
            SerialStatusRegister.active = true;
            bool useData = false;
            System.Threading.Thread.Sleep(100);
            while (_serialPort.IsOpen && _serialPort.BytesToRead != 0)
            {
                _serialPort.Read(addressBuffer, 0, 1);
                if (addressBuffer[0] == Convert.ToByte(Convert.ToInt32(Properties.Settings.Default.localAddress, 16)))
                {
                    headerBuffer = new byte[6];
                    headerBuffer[0] = addressBuffer[0];
                    _serialPort.Read(headerBuffer, 1, 5);
                    if (checkCRC(headerBuffer))
                    {
                        byte srcAddress = headerBuffer[1];
                        SCITask task = (SCITask)headerBuffer[2];
                        SCIRegAddress registerAddress = (SCIRegAddress)headerBuffer[3];
                        byte dataBytes = headerBuffer[4];
                        switch (task)
                        {
                            case SCITask.NA: break;
                            case SCITask.RING: RingAck(srcAddress); break;
                            case SCITask.ACK: AckCheck(srcAddress, registerAddress); break;
                            case SCITask.NACK: Retransmit(); break;
                            case SCITask.RTX: Retransmit(); break;
                            case SCITask.ERR: break;
                            case SCITask.GET: SendERR(srcAddress, registerAddress); break;
                            case SCITask.SET: useData = true; break;
                            case SCITask.ANS: useData = true; break;
                            case SCITask.RST: ResetSyst(srcAddress); break;
                            case SCITask.LOCK: Disconnect(srcAddress); break;
                            case SCITask.DISCONN: SerialStatusRegister.CONN = false; break;
                            case SCITask.SERGET: useData = true; break;
                            case SCITask.SERANS: useData = true; break;
                            case SCITask.SERSET: useData = true; break;
                            default: SendERR(srcAddress, registerAddress); break;
                        }


                        /* task and register code here */

                        byte register = Convert.ToByte(headerBuffer[3]);
                        if (dataBytes != 0 && useData)
                        {
                            dataBuffer = new byte[dataBytes + 1];
                            _serialPort.Read(dataBuffer, 0, dataBytes + 1);
                            if (checkCRC(dataBuffer))
                            {
                                switch (task)
                                {
                                    case SCITask.SET: Set(srcAddress, registerAddress, dataBytes, dataBuffer); break;
                                    case SCITask.ANS: Ans(srcAddress, registerAddress, dataBuffer); break;
                                    case SCITask.SERGET: SerGet(srcAddress, registerAddress, dataBytes, dataBuffer); break;
                                    case SCITask.SERANS: SerAns(srcAddress, registerAddress, dataBytes, dataBuffer); break;
                                    case SCITask.SERSET: SendERR(srcAddress, registerAddress); break;
                                    default: break;
                                }
                            }
                            else
                            {

                            }

                        }
                    }

                }
            }
            SerialStatusRegister.active = false;

        }

        public static void SerSet(byte dataBytes, byte[] dataBuffer)
        {
            SendData(Convert.ToByte(Convert.ToInt32(Properties.Settings.Default.terminalAddress, 16)), SCITask.SERSET, SCIRegAddress.SERIALIZED, dataBytes, dataBuffer);
            AckRQ(Convert.ToByte(Convert.ToInt32(Properties.Settings.Default.terminalAddress, 16)), SCIRegAddress.SERIALIZED);
        }

        private static void SerAns(byte srcAddress, SCIRegAddress registerAddress, byte dataBytes, byte[] dataBuffer)
        {
            if (ConnCheck(srcAddress))
            {
                AckCheck(srcAddress, registerAddress);
                int pos = 0;
                while (pos < dataBytes)
                {

                    if (((int)dataBuffer[pos] >= (int)SCIRegAddress.LM75_1 && (int)dataBuffer[pos] <= (int)SCIRegAddress.OFF_AVG_TEMP))
                    {

                        Program.GetWindow<MainForm>().measurementComplete(dataBuffer.Skip(pos + 1).Take(2).ToArray(), (SCIRegAddress)dataBuffer[pos]);
                        pos += 3;
                    }
                    else if ((int)dataBuffer[pos] >= (int)SCIRegAddress.SYST_TYPE && (int)dataBuffer[pos] <= (int)SCIRegAddress.AUTOSW)
                    {

                        Program.GetWindow<MainForm>().statusGetComplete(dataBuffer.Skip(pos + 1).Take(1).ToArray(), (SCIRegAddress)dataBuffer[pos]);
                        pos += 2;
                    }
                    else if ((int)dataBuffer[pos] >= (int)SCIRegAddress.DAY_START_TIME && (int)dataBuffer[pos] <= (int)SCIRegAddress.PUMP_DELAY_TIME)
                    {

                        Program.GetWindow<MainForm>().measurementComplete(dataBuffer.Skip(pos + 1).Take(1).ToArray(), (SCIRegAddress)dataBuffer[pos]);
                        pos += 2;
                    }
                    else
                    {
                        SendERR(srcAddress, SCIRegAddress.SERIALIZED);
                    }
                }
                Ack(srcAddress, SCIRegAddress.SERIALIZED);
            }
            else
            {
                SendForbidden(srcAddress, SCIRegAddress.SERIALIZED);
            }


        }

        private static void SerGet(byte srcAddress, SCIRegAddress registerAddress, byte dataBytes, byte[] dataBuffer)
        {
            throw new NotImplementedException();
        }

        public static void GetSerData(SCIRegAddress[] registerAddresses)
        {
            byte[] message = registerAddresses.Select(n => (byte)n).ToArray();
            SendData(Convert.ToByte(Convert.ToInt32(Properties.Settings.Default.terminalAddress, 16)), SCITask.SERGET, SCIRegAddress.SERIALIZED, (byte)message.Length, message);
            AckRQ(Convert.ToByte(Convert.ToInt32(Properties.Settings.Default.terminalAddress, 16)), SCIRegAddress.SERIALIZED);
        }

        private static void RingAck(byte srcAddress)
        {
            SerialStatusRegister.ACKRQ = false;
            SendData(srcAddress, SCITask.ACK, 0, 0, null);

            if (SerialStatusRegister.connectedAddress != srcAddress)
            {
                SerialStatusRegister.connectedAddress = srcAddress;
            }
            SerialStatusRegister.CONN = true;
            SerialStatusRegister.ACK = true;
            SerialStatusRegister.waitingForAnswer = false;
        }

        private static void Ack(byte srcAddress, SCIRegAddress reg)
        {
            SendData(srcAddress, SCITask.ACK, reg, 0, null);
            SerialStatusRegister.ACK = true;
            SerialStatusRegister.ACKRQ = false;
            SerialStatusRegister.waitingForAnswer = false;
            timeoutTimer.Stop();
        }

        private static void SendERR(byte dstAddress, SCIRegAddress device)
        {
            SendData(dstAddress, SCITask.ERR, device, 0, null);
            AckRQ(dstAddress, device);
        }

        private static void AckRQ(byte address, SCIRegAddress reg)
        {
            SerialStatusRegister.ACKAddress = address;
            SerialStatusRegister.ACKRegAddress = reg;
            SerialStatusRegister.ACKRQ = true;
            SerialStatusRegister.waitingForAnswer = true;
            timeoutTimer.Interval = Convert.ToInt32(Properties.Settings.Default.timeoutInterval);
            timeoutTimer.Start();
        }

        private static void SendForbidden(byte dstAddress, SCIRegAddress device)
        {
            SendData(dstAddress, SCITask.ERR, device, 0, null);
            SerialStatusRegister.waitingForAnswer = false;
        }

        private static void Retransmit()
        {
            SendToSerial(lastMessage);
            AckRQ(lastMessage[0], (SCIRegAddress)lastMessage[3]);
        }

        private static void RTX(byte dstAddress, SCIRegAddress reg)
        {
            SendData(dstAddress, SCITask.RTX, reg, 0, null);

        }

        private static void Disconnect(byte srcAddress)
        {
            if (SerialStatusRegister.connectedAddress != srcAddress)
            {
                SendData(srcAddress, SCITask.FORBIDDEN, 0, 0, null);
            }
            else
            {
                if (SerialStatusRegister.connectedAddress == srcAddress) SerialStatusRegister.connectedAddress = 0x00;
                SendData(srcAddress, SCITask.DISCONN, 0, 0, null);

                SerialStatusRegister.CONN = false;
            }
            SerialStatusRegister.waitingForAnswer = false;
        }

        private static void ResetSyst(byte srcAddress)
        {
            if (ConnCheck(srcAddress))
            {
                Disconnect(srcAddress);
                if (!SerialStatusRegister.CONN)
                {
                    /* resetting code here */
                }
            }
        }

        private static bool ConnCheck(byte srcAddress)
        {
            if (SerialStatusRegister.connectedAddress == srcAddress) return true;
            else return false;
        }

        private static bool AckCheck(byte srcAddress, SCIRegAddress reg)
        {
            if (SerialStatusRegister.ACKAddress == srcAddress && reg == SerialStatusRegister.ACKRegAddress && SerialStatusRegister.ACKRQ)
            {
                SerialStatusRegister.ACKRQ = false;
                SerialStatusRegister.CONN = true;
                SerialStatusRegister.connectedAddress = srcAddress;
                SerialStatusRegister.waitingForAnswer = false;
                timeoutTimer.Stop();
                return true;
            }
            else return false;
        }

        public static byte GetACKAddress()
        {
            return SerialStatusRegister.ACKAddress;
        }

        private static void Set(byte srcAddress, SCIRegAddress reg, byte bytes, byte[] data)
        {

            SendForbidden(srcAddress, reg);

        }

        private static void Ans(byte srcAddress, SCIRegAddress reg, byte[] data)
        {

            if (ConnCheck(srcAddress))
            {
                AckCheck(srcAddress, reg);
                if (((int)reg >= (int)SCIRegAddress.LM75_1 && (int)reg <= (int)SCIRegAddress.HTU21D_HUM)
                        || ((int)reg >= (int)SCIRegAddress.ROOM_TEMP && (int)reg <= (int)SCIRegAddress.OFF_AVG_TEMP))
                {
                    Ack(srcAddress, reg);
                    Program.GetWindow<MainForm>().measurementComplete(data, reg);
                }
                else if ((int)reg >= (int)SCIRegAddress.SYST_TYPE && (int)reg <= (int)SCIRegAddress.AUTOSW)
                {
                    Ack(srcAddress, reg);
                    Program.GetWindow<MainForm>().statusGetComplete(data, reg);
                }
                else if ((int)reg >= (int)SCIRegAddress.DAY_START_TIME && (int)reg <= (int)SCIRegAddress.PUMP_DELAY_TIME)
                {
                    Ack(srcAddress, reg);
                    Program.GetWindow<MainForm>().measurementComplete(data, reg);
                }
                else
                {
                    SendERR(srcAddress, reg);
                }
            }
            else
            {
                SendForbidden(srcAddress, reg);
            }
        }

        public static float ConvertDataToValue(byte[] data)
        {
            return (data[0] < 128 ? ((float)data[0]) : ((float)(data[0] - 256))) + (data[1] < 128 ? ((float)data[1] / 100) : ((float)(data[1] - 256) / 100));
        }

        public static byte[] ConvertValueToMessage(float value)
        {
            byte[] res = new byte[2];
            res[0] = value > -1 ? (byte)((int)value) : (byte)(256 - Math.Abs((int)value));
            res[1] = value >= 0 ? (byte)(Math.Abs((value - (int)value) * 100)) : (byte)(256 - (Math.Abs(value - (int)value) * 100));
            return res;
        }

        public static bool ConvertDataToStatus(byte[] data)
        {
            return Convert.ToBoolean(data[0]);
        }

        public static byte[] ConvertStatusToMessage(bool value)
        {
            byte[] res = new byte[1];
            res[0] = (byte)(Convert.ToByte(value));
            return res;
        }


        private static byte createCRC(byte[] message)
        {
            byte ans = message[0];
            int length = message.Length;
            for (int i = 1; i < length; i++)
            {
                ans ^= message[i];
            }
            return ans;

        }

        private static bool checkCRC(byte[] message)
        {
            byte ans = 0;
            int length = message.Length;
            ans = message[0];
            for (int i = 1; i < length - 1; i++)
            {
                ans ^= message[i];
            }
            if (ans == message[length - 1])
            {
                return true;
            }
            else
            {
                return false;
            }
        }  // same as above

        private static void SendUpdateTime()
        {
            byte[] SCIMessage = new byte[7];
            SCIMessage[0] = Convert.ToByte(DateTime.Now.Year - 2000);
            SCIMessage[1] = Convert.ToByte(DateTime.Now.Month);
            SCIMessage[2] = Convert.ToByte(DateTime.Now.Day);
            SCIMessage[3] = Convert.ToByte(((DateTime.Now.DayOfWeek == System.DayOfWeek.Sunday) ? 7 : Convert.ToByte(DateTime.Now.DayOfWeek)));
            SCIMessage[4] = Convert.ToByte(DateTime.Now.Hour);
            SCIMessage[5] = Convert.ToByte(DateTime.Now.Minute);
            SCIMessage[6] = Convert.ToByte(DateTime.Now.Second);

            SendData(Convert.ToByte(Convert.ToInt32(Properties.Settings.Default.terminalAddress, 16)), SCITask.SET, SCIRegAddress.CLOCK, 7, SCIMessage);
            SerialStatusRegister.ACKRQ = true;
            AckRQ(Convert.ToByte(Convert.ToInt32(Properties.Settings.Default.terminalAddress, 16)), SCIRegAddress.CLOCK);

        }

        public static void UpdateTime()
        {
            SendUpdateTime();
        }
    }
}
