using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.IO.Ports;
using System.Windows.Forms;

namespace SiWiComMSI
{
    #region Public Enumerations
    public enum LogMsgType { Incoming, Outgoing, Normal, Warning, Error };
    public enum PacketType { Error, Normal };
    #endregion

    public partial class SiWiComDevTool_Main : Form
    {
        #region Local Variables

        // Various colors for logging info
        private Color[] LogMsgTypeColor = { Color.Gray, Color.LightGray, Color.Goldenrod, Color.Orange, Color.Red };

        const int WM_NCLBUTTONDOWN = 0xA1;
        const int WM_NCHITTEST = 0x84;
        const int HT_CAPTION = 0x2;

        byte[] pcProtocolInputBuffer = new byte[8];
        int pcProtocolByteCount = 0;
        int terminalCounter = 0;

        #endregion

        #region Constructor
        public SiWiComDevTool_Main()
        {
            InitializeComponent();
            CheckForIllegalCrossThreadCalls = false;
        }
        #endregion

        #region Local Methods

        protected override void WndProc(ref Message m)
        {
            base.WndProc(ref m);
            if (m.Msg == WM_NCHITTEST)
                m.Result = (IntPtr)HT_CAPTION;
        }
        
        /// <summary> Save the user's settings. </summary>
        private void SaveSettings()
        {


            try
            {
                // If the port is open, close it.
                if (comport.IsOpen) comport.Close();
            }
            catch (Exception)
            {

            }
        }

        /// <summary> Populate the form's controls with default settings. </summary>
        private void InitializeControlValues()
        {
            

            lstComPort.Items.Clear();
            lstComPort.Height = 0;
            foreach (string s in SerialPort.GetPortNames())
            {
                lstComPort.Items.Add(s);
                lstComPort.Height += 14;
            }

            if (lstComPort.Items.Count == 0)
            {
                MessageBox.Show(this, "No COM ports detected. \nYou need at least one free COM port for this to work!!!", "No COM ports detected!", MessageBoxButtons.OK, MessageBoxIcon.Error);
                this.Close();
            }
        }

        /// <summary> Open serial port fom communication (9600bps). </summary>
        /// <param name="port"> COM port to open. </param>
        private void openComport(string port)
        {
            try
            {
                // If the port is open, close it.
                if (comport.IsOpen) comport.Close();
                // Set the port's settings
                comport.BaudRate = 9600;
                comport.DataBits = 8;
                comport.StopBits = (StopBits)Enum.Parse(typeof(StopBits), "1");
                comport.Parity = (Parity)Enum.Parse(typeof(Parity), "None");
                comport.PortName = port;

                // Open the port
                comport.Open();

                btnConnect.ImageIndex = 1;
            }
            catch (Exception)
            {
                btnConnect.ImageIndex = 0;
                MessageBox.Show(this, "Error connecting to port! \nPlease check that no other program are connected to the selected COM port.", "Error connecting to port!", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        /// <summary> Adds one byte to the input buffer and validate packet when buffer is full. </summary>
        byte[] pcProtocolGetData()
        {
            pcProtocolByteCount = 0;
            return pcProtocolInputBuffer;
        }

        /// <summary> Adds one byte to the input buffer and validate packet when buffer is full. </summary>
        /// <param name="data"> data byte to be added </param>
		/// <param name="mode"> mode = MASTER or SLAVE </param>
		/// <param name="address"> specific address for the slave device </param>
        bool pcProtocolAddByteToBuffer(byte data)
        {
            bool gotPacket = false;

            if (pcProtocolByteCount == 0)
            {
                if (data == 0x0A)
                {
                    pcProtocolInputBuffer[pcProtocolByteCount] = data;
                    pcProtocolByteCount++;
                }
            }
            else if (pcProtocolByteCount < pcProtocolInputBuffer.Length - 1)
            {
                pcProtocolInputBuffer[pcProtocolByteCount] = data;
                pcProtocolByteCount++;
            }
            else if (pcProtocolByteCount == pcProtocolInputBuffer.Length - 1)
            {
                pcProtocolInputBuffer[pcProtocolByteCount] = data;

                if (calculateChecksum(pcProtocolInputBuffer) == data)
                {
                    gotPacket = true;
                }
                else
                {
                    pcProtocolByteCount = 0;
                }
            }

            if (gotPacket)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        /// <summary> Calculate checksum for roboMSP packet. </summary>
        /// <param name="stringToConvert"> The packet to calculate on. </param>
        /// <returns> Returns the checksum. </returns>
        private byte calculateChecksum(byte[] data)
        {
            int checksum = 0;

            for (int i = 0; i < (data.Length - 1); i++)
            {
                checksum += data[i];
            }

            checksum &= 0x00FF;

            return (byte)checksum;
        }
        /// <summary> Extract command discription from roboMSP packet. </summary>
        /// <param name="stringToConvert"> The packet to extract from. </param>
        /// <returns> Returns the command discription. </returns>
        private string getCommand(string stringToConvert)
        {
            switch (stringToConvert.Substring(3, 2))
            {
                case "11": return "GET_DATA";
                case "22": return "DATA";
                case "33": return "DATA";
                case "44": return "UPDATE";
                case "55": return "UPDATE";
                case "FE": return "ACK";
            }

            return "Error";
        }

        /// <summary> Extract id from roboMSP packet. </summary>
        /// <param name="stringToConvert"> The packet to extract from. </param>
        /// <returns> Returns id. </returns>
        private string getId(string stringToConvert)
        {
            return stringToConvert.Substring(4, 2);
        }

        /// <summary> Send the user's data currently entered in the 'send' box.</summary>
        private void SendCommand(byte commandToSend, byte[] inputData)
        {

            byte[] data = { 0x0A, commandToSend, inputData[0], inputData[1], inputData[2], inputData[3], inputData[4], 0x55 };

            data[data.Length - 1] = calculateChecksum(data);
            
            try
            {
                // Send the binary data out the port
                comport.Write(data, 0, data.Length);
                // Show the hex digits on in the terminal window
                Log(LogMsgType.Outgoing, ByteArrayToHexString(data), PacketType.Normal);
            }
            catch (Exception)
            {
                // Show the hex digits on in the terminal window
                //Log(LogMsgType.Outgoing, ByteArrayToHexString(data), PacketType.Error);
            }
        }
        /// <summary> Send ACK command to robot. </summary>
        /// <param name="id"> Id to robot. </param>
        private void SendACK()
        {
            byte[] data = { 0x0A, 0xFE, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55 };

            data[data.Length - 1] = calculateChecksum(data);

            try
            {   
                comport.Write(data, 0, data.Length);
                // Show the hex digits on in the terminal window
                Log(LogMsgType.Outgoing, ByteArrayToHexString(data), PacketType.Normal);
            }
            catch (FormatException)
            {
                // Show the hex digits on in the terminal window
                //Log(LogMsgType.Outgoing, ByteArrayToHexString(data), PacketType.Error);
            }
        }

        /// <summary> Log data to the terminal window. </summary>
        /// <param name="msgtype"> The type of message to be written. </param>
        /// <param name="msg"> The string containing the message to be shown. </param>
        private void Log(LogMsgType msgtype, string msg, PacketType packetType)
        {
            if (packetType == PacketType.Error)
            {
                rtfTerminal.SelectedText = string.Empty;
                rtfTerminal.SelectionFont = new Font(rtfTerminal.SelectionFont, FontStyle.Regular);
                rtfTerminal.SelectionColor = LogMsgTypeColor[4];
                rtfTerminal.AppendText("Error\n");
                terminalCounter++;
                rtfTerminal.ScrollToCaret();
            }
            else if (msgtype == LogMsgType.Incoming)
            {
                rtfTerminal.Invoke(new EventHandler(delegate
                {
                    rtfTerminal.SelectedText = string.Empty;
                    rtfTerminal.SelectionFont = new Font(rtfTerminal.SelectionFont, FontStyle.Regular);
                    rtfTerminal.SelectionColor = LogMsgTypeColor[0];

                    rtfTerminal.AppendText(System.DateTime.Now.ToString("[HH:mm:ss]") + " >> ");
                    rtfTerminal.SelectionFont = new Font(rtfTerminal.SelectionFont, FontStyle.Bold);
                    rtfTerminal.SelectionColor = LogMsgTypeColor[0];
                    rtfTerminal.AppendText((msg) + " ");

                    rtfTerminal.SelectedText = string.Empty;
                    rtfTerminal.SelectionFont = new Font(rtfTerminal.SelectionFont, FontStyle.Regular);
                    rtfTerminal.SelectionColor = LogMsgTypeColor[0];
                    rtfTerminal.AppendText((char)0x22 + getCommand(msg) + (char)0x22 + "\n");
                    terminalCounter++;

                    rtfTerminal.ScrollToCaret();
                }));
            }
            else if (msgtype == LogMsgType.Outgoing) 
            {
                rtfTerminal.Invoke(new EventHandler(delegate
                {
                    rtfTerminal.SelectedText = string.Empty;
                    rtfTerminal.SelectionFont = new Font(rtfTerminal.SelectionFont, FontStyle.Regular);
                    rtfTerminal.SelectionColor = LogMsgTypeColor[2];
                    
                    rtfTerminal.AppendText(System.DateTime.Now.ToString("[HH:mm:ss]") + " << ");
                    rtfTerminal.SelectionFont = new Font(rtfTerminal.SelectionFont, FontStyle.Bold);
                    rtfTerminal.SelectionColor = LogMsgTypeColor[2];
                    rtfTerminal.AppendText((msg) + " ");

                    rtfTerminal.SelectedText = string.Empty;
                    rtfTerminal.SelectionFont = new Font(rtfTerminal.SelectionFont, FontStyle.Regular);
                    rtfTerminal.SelectionColor = LogMsgTypeColor[2];
                    rtfTerminal.AppendText((char)0x22 + getCommand(msg) + (char)0x22 + "\n");
                    terminalCounter++;

                    rtfTerminal.ScrollToCaret();
                }));
            }

            if (terminalCounter == 300)
            {
                rtfTerminal.Clear();
                terminalCounter = 0;
            }

        }

        /// <summary> Convert a string of hex digits (ex: E4 CA B2) to a byte array. </summary>
        /// <param name="s"> The string containing the hex digits (with or without spaces). </param>
        /// <returns> Returns an array of bytes. </returns>
        private void showDataOnPanel (byte[] data)
        {
            if ((data[1] == 0x22) || (data[1] == 0x33))
            {
                decimal batTemp = Convert.ToDecimal((float)data[4] * 0.0146);
                decimal tempTemp = Convert.ToDecimal((float)data[3] / 9.8);
                decimal rssiTemp = Convert.ToDecimal((float)((sbyte)data[6]) / 2);

                lblAddr.Text = data[2].ToString();
                lblBat.Text = Decimal.Round(batTemp, 1) + " V";
                lblRSSI.Text = Decimal.Round(rssiTemp, 1) + " dB";
                lblTemp.Text = Decimal.Round(tempTemp, 1) + " C";
            }
        }

        /// <summary> Convert a string of hex digits (ex: E4 CA B2) to a byte array. </summary>
        /// <param name="s"> The string containing the hex digits (with or without spaces). </param>
        /// <returns> Returns an array of bytes. </returns>
        private byte[] HexStringToByteArray(string s)
        {
            s = s.Replace(" ", "");
            byte[] buffer = new byte[s.Length / 2];
            for (int i = 0; i < s.Length; i += 2)
                buffer[i / 2] = (byte)Convert.ToByte(s.Substring(i, 2), 16);
            return buffer;
        }

        /// <summary> Converts an array of bytes into a formatted string of hex digits (ex: E4 CA B2)</summary>
        /// <param name="data"> The array of bytes to be translated into a string of hex digits. </param>
        /// <returns> Returns a well formatted string of hex digits with spacing. </returns>
        private string ByteArrayToHexString(byte[] data)
        {
            StringBuilder sb = new StringBuilder(data.Length * 3);
            foreach (byte b in data)
                sb.Append(Convert.ToString(b, 16).PadLeft(2, '0').PadRight(3, ' '));
            return sb.ToString().ToUpper();
        }

        /// <summary> Converts an array of bytes into a formatted string of hex digits (ex: E4 CA B2)</summary>
        /// <param name="data"> The array of bytes to be translated into a string of hex digits. </param>
        /// <returns> Returns a well formatted string of hex digits with spacing. </returns>
        private string ByteArrayToCharString(byte[] data)
        {
            StringBuilder sb = new StringBuilder(data.Length);
            foreach (byte b in data)
                sb.Append(Convert.ToChar(b));
            return sb.ToString().ToUpper();
        }
        #endregion

        #region Event Handlers
        private void Form1_Load(object sender, EventArgs e)
        {
            InitializeControlValues();
        }
        #endregion

        private void comport_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            // Obtain the number of bytes waiting in the port's buffer
            int bytes = comport.BytesToRead;
            byte[] buffer = new byte[bytes];

            // Read the data from the port and store it in our buffer
            comport.Read(buffer, 0, bytes);

            //comport.DiscardInBuffer();

            for (int i = 0; i < bytes; i++)
            {
                if (pcProtocolAddByteToBuffer(buffer[i]))
                {
                    byte[] completePacket = pcProtocolGetData();

                    showDataOnPanel(completePacket);
                    // Show the hex digits on in the terminal window
                    Log(LogMsgType.Incoming, ByteArrayToHexString(completePacket), PacketType.Normal);
                }
            }

        }

        private void lstComPort_SelectedIndexChanged(object sender, EventArgs e)
        {
            openComport(lstComPort.Text);
            lstComPort.Visible = false;
            lblSelectPort.Visible = false;
        }

        private void SiWiComMSI_Main_FormClosing(object sender, FormClosingEventArgs e)
        {
            SaveSettings();
        }

        private void btnClose_MouseEnter(object sender, EventArgs e)
        {
            btnClose.ImageIndex = 1;
        }

        private void btnClose_MouseLeave(object sender, EventArgs e)
        {
            btnClose.ImageIndex = 0;
        }

        private void btnClose_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        private void btnConnect_Click_1(object sender, EventArgs e)
        {
            lstComPort.Visible = true;
            lblSelectPort.Visible = true;
        }

        private void lblLocalDeviceAddr_Click(object sender, EventArgs e)
        {
            listLocalDeviceAddr.Visible = true;
        }

        private void lblLocalMasterAddr_Click(object sender, EventArgs e)
        {
            listLocalMasterAddr.Visible = true;
        }

        private void lblRemoteOldDeviceAddr_Click(object sender, EventArgs e)
        {
            listRemoteOldDeviceAddr.Visible = true;
        }

        private void lblRemoteNewDeviceAddr_Click(object sender, EventArgs e)
        {
            listRemoteNewDeviceAddr.Visible = true;
        }

        private void lblRemoteMasterAddr_Click(object sender, EventArgs e)
        {
            listRemoteMasterAddr.Visible = true;
        }

        private void listLocalDeviceAddr_SelectedIndexChanged(object sender, EventArgs e)
        {
            listLocalDeviceAddr.Visible = false;
            lblLocalDeviceAddr.Text = listLocalDeviceAddr.Text;
        }

        private void listLocalMasterAddr_SelectedIndexChanged(object sender, EventArgs e)
        {
            listLocalMasterAddr.Visible = false;
            lblLocalMasterAddr.Text = listLocalMasterAddr.Text;
        }

        private void listRemoteMasterAddr_SelectedIndexChanged(object sender, EventArgs e)
        {
            listRemoteMasterAddr.Visible = false;
            lblRemoteMasterAddr.Text = listRemoteMasterAddr.Text;
        }

        private void listRemoteNewDeviceAddr_SelectedIndexChanged(object sender, EventArgs e)
        {
            listRemoteNewDeviceAddr.Visible = false;
            lblRemoteNewDeviceAddr.Text = listRemoteNewDeviceAddr.Text;
        }

        private void listRemoteOldDeviceAddr_SelectedIndexChanged(object sender, EventArgs e)
        {
            listRemoteOldDeviceAddr.Visible = false;
            lblRemoteOldDeviceAddr.Text = listRemoteOldDeviceAddr.Text;
        }

        private void btnSendLocal_Click(object sender, EventArgs e)
        {
            byte[] tmpPacket = { (byte)Convert.ToByte(lblLocalMasterAddr.Text), (byte)Convert.ToByte(lblLocalDeviceAddr.Text), (byte)Convert.ToByte(txtLocalWakeUpTime.Text), 0x00, 0x55 };
            SendCommand(0x44, tmpPacket);
            lblLocalMasterAddr.Text = "0";
            lblLocalDeviceAddr.Text = "0";
            txtLocalWakeUpTime.Text = "0";
        }

        private void btnSendRemote_Click(object sender, EventArgs e)
        {
            byte[] tmpPacket = { (byte)Convert.ToByte(lblRemoteOldDeviceAddr.Text), (byte)Convert.ToByte(lblRemoteMasterAddr.Text), (byte)Convert.ToByte(lblRemoteNewDeviceAddr.Text), (byte)Convert.ToByte(txtRemoteWakeUpTime.Text), 0x55 };
            SendCommand(0x55, tmpPacket);
            lblRemoteOldDeviceAddr.Text = "0";
            lblRemoteMasterAddr.Text = "0";
            lblRemoteNewDeviceAddr.Text = "0";
            txtRemoteWakeUpTime.Text = "0";
        }

        private void btnIDLE_Click(object sender, EventArgs e)
        {
            byte[] tmpPacket = { 0x00, 0x00, 0x00, 0x01, 0x55 };
            SendCommand(0x44, tmpPacket);
        }

        private void btnMASTER_Click(object sender, EventArgs e)
        {
            byte[] tmpPacket = { 0x00, 0x00, 0x00, 0x02, 0x55 };
            SendCommand(0x44, tmpPacket);
        }

        private void btnSLAVE_Click(object sender, EventArgs e)
        {
            byte[] tmpPacket = { 0x00, 0x00, 0x00, 0x03, 0x55 };
            SendCommand(0x44, tmpPacket);
        }

        private void btnNOICEMAKER_Click(object sender, EventArgs e)
        {
            byte[] tmpPacket = { 0x00, 0x00, 0x00, 0x04, 0x55 };
            SendCommand(0x44, tmpPacket);
        }

        private void btnGetLocalData_Click(object sender, EventArgs e)
        {
            byte[] tmpPacket = { 0x55, 0x55, 0x55, 0x55, 0x55 };
            SendCommand(0x11, tmpPacket);
        }


    }
}