using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Linq;
using System.Windows.Forms;
using DevExpress.XtraEditors;
using System.IO;
using System.Runtime.InteropServices;

using System.Threading;

using LFP_Manager.Function;
using LFP_Manager.Utils;

namespace LFP_Manager
{
    public delegate void AutoTxSetEnvent(bool autoTx);
    public delegate void SendDataUartEvent(ushort dev_addr, byte[] data, bool ReplyFlag, int type);

    public partial class fmxFwUpdate : XtraForm
    {
        #region DEFINES

        const int FW_UPDATE_START_ADDR      = 0x08080000;
        const int FW_UPDATE_END_ADDR        = 0x080E0000 - 1;
        const int FW_UPDATE_SECTOR_SIZE     = 0x20000;
        const int FW_UPDATE_PACKET_SIZE     = 0x800;

        const int APP_TO_BOOT = 0;
        const int BOOT_TO_APP = 1;

        #endregion

        #region VARIABLES

        private UInt32 SystemId = 0;
        private byte[] fwdata;
        private int fwsize;
        private int FwStartAddr = 0;
        private int FwCurrPosition = 0;
        private int FwSentLength = 0;

        private bool StopUpdate = true;

        private DateTime UpdateStartTime;
        private TimeSpan UpdateTime;

        public event AutoTxSetEnvent OnAutoTxSet = null;
        public event SendDataUartEvent OnSendUartData = null;

        #endregion

        #region CONSTRUCTORS

        public fmxFwUpdate(int sID)
        {
            InitializeComponent();

            SystemId = (UInt32)sID;
            edTargetID.Text = sID.ToString();

            fwdata = null;
            fwsize = 0;
            btnFwUpdate.Enabled = false;

            lcItemFlashErase.Visibility = DevExpress.XtraLayout.Utils.LayoutVisibility.Never;
            lcItemFlashWrite.Visibility = DevExpress.XtraLayout.Utils.LayoutVisibility.Never;
            lcItemGotoBoot.Visibility = DevExpress.XtraLayout.Utils.LayoutVisibility.Never;
            lcItemGotoApp.Visibility = DevExpress.XtraLayout.Utils.LayoutVisibility.Never;
        }

        #endregion

        #region BUTTON EVENTS

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

        private void btnFwUpdate_Click(object sender, EventArgs e)
        {
            if (edTargetID.Text == "")
            {
                MessageBox.Show("Please check target id !!");
                return;
            }

            SystemId = Convert.ToUInt32(edTargetID.Text);

            if (StopUpdate == true)
            {
                // Working mode change (Application --> Boot) - It need to be wait more than 1.5sec
                OnAutoTxSet?.Invoke(false);

                DisplayLabelUpdate("Goto Bootloader Mode");
                RequestModeChange(APP_TO_BOOT);
                UpdateStartTime = DateTime.Now;

                btnFwUpdate.Text = "Cancel Update";
                btnFwUpdate.Enabled = false;

                for (int i = 0; i < 2000; i += 100)
                {
                    Thread.Sleep(100);
                    Application.DoEvents();
                }

                btnFwUpdate.Enabled = true;
                // Flash Erase
                RequestFlashErase();
            }
            else
            {
                StopFwUpdate();
            }
        }

        private void StopFwUpdate()
        {
            StopUpdate = true;
            btnFwUpdate.Text = "Firmware Update";
            pgUpdate.Position = 0;
            //pgUpdate.Properties.Maximum = 0;
            //lbUpdateProgress.Text = "-/-";
            lbUpdateStatus.Text = "Stop Update";
        }

        public static byte[] ToByteArray(String HexString)
        {
            int NumberChars = HexString.Length;
            byte[] bytes = new byte[NumberChars / 2];
            for (int i = 0; i < NumberChars; i += 2)
            {
                bytes[i / 2] = Convert.ToByte(HexString.Substring(i, 2), 16);
            }
            return bytes;
        }
        private void btnFind_Click(object sender, EventArgs e)
        {
            OpenFileDialog oDialog;

            oDialog = new OpenFileDialog();
            oDialog.DefaultExt = "*.*";
            oDialog.Filter = "hex files (*.hex)|*.hex|All files (*.*)|*.*";

            if (oDialog.ShowDialog() == DialogResult.OK)
            {
                edFilename.Text = oDialog.FileName;

                int addr1 = 0;
                int addr2 = 0;
                string[] aline = File.ReadLines(edFilename.Text).ToArray();
                for (int i = 0; i < aline.Length; i++)
                {
                    byte[] a = ToByteArray(aline[i].Substring(1, aline[i].Length - 1));
                    switch (a[3])
                    {
                        case 0:     // data record
                            addr2 = (int)((a[1] << 8) | a[2]) + a[0];
                            if (FwStartAddr == 0)
                                FwStartAddr = (int)((a[1] << 8) | a[2]);
                            break;
                        case 1:     // End of file record. Usually, it is 00000001FF

                            break;
                        case 2:     // Extended Segment address record. This indicates segment base address when 16 bits is not enough for addressing memory;
                            break;
                        case 3:     // Start segment address record. Indicates initial segment base address.
                            break;
                        case 4:     // Extended Linear Address Record – allows 32 bit addressing.
                            addr1 = (int)((a[4] << 8) | a[5]);
                            break;
                        case 5:     // Start Linear Address Record.
                            break;
                    }
                }
                int FwImageSize = (int)((addr1 << 16) + addr2 - FwStartAddr);
                if ((FwImageSize % 128) > 0)
                {
                    FwImageSize = ((FwImageSize / 128) + 1) * 128;
                }
                else
                {
                    FwImageSize = ((FwImageSize / 128) + 0) * 128;
                }
                addr1 = 0;
                addr2 = 0;
                fwsize = FwImageSize;
                fwdata = new byte[fwsize];
                for (int i = 0; i < fwdata.Length; i++)
                {
                    fwdata[i] = 0xFF;
                }    
                for (int i = 0; i < aline.Length; i++)
                {
                    byte[] a = ToByteArray(aline[i].Substring(1, aline[i].Length - 1));
                    switch (a[3])
                    {
                        case 0:     // data record
                            addr2 = (int)((a[1] << 8) | a[2]);
                            int len = a[0];
                            for (int j = 0; j < len; j++)
                            {
                                fwdata[((addr1 << 16) + addr2) - FwStartAddr + j] = a[4 + j];
                            }
                            break;
                        case 1:     // End of file record. Usually, it is 00000001FF

                            break;
                        case 2:     // Extended Segment address record. This indicates segment base address when 16 bits is not enough for addressing memory;
                            break;
                        case 3:     // Start segment address record. Indicates initial segment base address.
                            break;
                        case 4:     // Extended Linear Address Record – allows 32 bit addressing.
                            addr1 = (int)((a[4] << 8) | a[5]);
                            break;
                        case 5:     // Start Linear Address Record.
                            break;
                    }
                }

                string strName = Path.GetFileName(edFilename.Text);

                lbFileInfo.Text = string.Format("File Information");
                lbFileInfo.Text += string.Format("\r\n  - Filename: {0}", strName);
                lbFileInfo.Text += string.Format("\r\n  - File Version: {0}", strName.Substring(0, 12));
                lbFileInfo.Text += string.Format("\r\n  - File Size: {0:#,##0} bytes", fwsize);
                lbFileInfo.Text += string.Format("\r\n  - Packet Size: {0:#,##0} packets", fwsize / 128);
                lbFileInfo.Text += string.Format("\r\n  - Last packet: {0:#,##0}", fwsize % 128);
                lbFileInfo.Text += string.Format("\r\n  - Start Addr : 0x{0:X8}", FwStartAddr);

                lbUpdateProgress.Text = string.Format("{0:#,##0}/{1:#,##0} bytes", 0, fwsize);
                pgUpdate.Properties.Maximum = (int)fwsize;

                btnFwUpdate.Enabled = true;
            }
        }

        #endregion

        #region FORM EVENT

        private void fmxFwUpdate_Activated(object sender, EventArgs e)
        {
            //OnAutoTxSet?.Invoke(false);
        }

        private void fmxFwUpdate_FormClosed(object sender, FormClosedEventArgs e)
        {
            OnAutoTxSet?.Invoke(true);
        }

        #endregion

        #region PUBLIC FUNCTION

        void DisplayLabelUpdate(string msg)
        {
            if (this.InvokeRequired)
            {
                this.Invoke(new MethodInvoker(delegate ()
                {
                    lbUpdateStatus.Text = string.Format("Update Status : {0}", msg);
                }));
            }
            else
            {
                lbUpdateStatus.Text = string.Format("Update Status : {0}", msg);
            }
        }
        void DisplayUpdateProgress()
        {
            if (this.InvokeRequired)
            {
                _ = this.Invoke(new MethodInvoker(delegate ()
                {
                    lbUpdateProgress.Text = string.Format("{0:#,##0}/{1:#,##0} bytes", FwCurrPosition, fwsize);
                    pgUpdate.Position = FwCurrPosition;
                    UpdateTime = DateTime.Now - UpdateStartTime;
                    lbUpdateTime.Text = string.Format("{0:mm}:{0:ss}", UpdateTime);
                }));
            }
            else
            {
                lbUpdateProgress.Text = string.Format("{0:#,##0}/{1:#,##0} bytes", FwCurrPosition, fwsize);
                pgUpdate.Position = FwCurrPosition;
                UpdateTime = DateTime.Now - UpdateStartTime;
                lbUpdateTime.Text = string.Format("{0:mm}:{0:ss}", UpdateTime);
            }
        }

        public void RecvData(byte[] rdata)
        {
            //lbUpdateStatus.Text = String.Format("Recv Data: 0x{0:X8} {1:X2} {2:X2} {3:X2} {4:X2} {5:X2} {6:X2} {7:X2} {8:X2}"
            //                                    , rhdr, rdata[0], rdata[1], rdata[2], rdata[3], rdata[4], rdata[5], rdata[6], rdata[7]);

            if (StopUpdate == false)
            {
                if (rdata[0] == 0x7F)
                {
                    switch (rdata[1])
                    {
                        case 0x43:          // Erase Flash
                            if (rdata[2] == 0x79)
                            {
                                // Erase Success
                                DisplayLabelUpdate("Flash Erase Success");
                                byte[] sdata = new byte[128];
                                FwCurrPosition = 0;
                                for (int i = 0; i < sdata.Length; i++)
                                {
                                    sdata[i] = fwdata[FwCurrPosition + i];
                                }
                                RequestFlashWrite(FwStartAddr + FwCurrPosition, sdata);
                            }
                            else if (rdata[2] == 0x1F)
                            {
                                // Erase Fail
                                DisplayLabelUpdate("Flash Erase Fail");
                                StopUpdate = true;
                            }
                            break;
                        case 0x31:          // Write Flash
                            if (rdata[2] == 0x79)
                            {
                                // Write Success
                                string str = string.Format("Flash Write Success - {0}/{1}", FwCurrPosition, FwSentLength);
                                DisplayLabelUpdate(str);
                                DisplayUpdateProgress();

                                FwCurrPosition += FwSentLength;

                                int Remain = fwdata.Length - FwCurrPosition;
                                if (Remain > 0)
                                {
                                    int len = (Remain > 128) ? 128 : Remain;
                                    byte[] sdata = new byte[len];
                                    for (int i = 0; i < sdata.Length; i++)
                                    {
                                        sdata[i] = fwdata[FwCurrPosition + i];
                                    }
                                    RequestFlashWrite(FwStartAddr + FwCurrPosition, sdata);
                                }
                                else
                                {
                                    RequestModeChange(BOOT_TO_APP);
                                    // Write Success
                                    str = string.Format("Complete Update - {0}/{1}", FwCurrPosition, FwSentLength);
                                    DisplayLabelUpdate(str);
                                    DisplayUpdateProgress();
                                    //MessageBox.Show("Complete Firmware Update", "Result", MessageBoxButtons.OK, MessageBoxIcon.Information);
                                }
                            }
                            else if (rdata[2] == 0x1F)
                            {
                                // Write Fail
                                DisplayLabelUpdate("Flash Write Fail");
                                StopUpdate = true;
                            }
                            break;
                    }
                }
            }
        }

        #endregion

        #region UART PACKET FUNCTION
        private void RequestModeChange(int mode)
        {
            byte[] sdata = null;
            byte[] crc;

            if (mode == 0)
            {
                // Normal mode --> Bootloader mode
                sdata = new byte[8];
                sdata[0] = (byte)SystemId;         // Dev Address
                sdata[1] = 0x50;         // Function Code
                sdata[2] = 0x80;         // Address H
                sdata[3] = 0x00;         // Address L
                sdata[4] = 0x00;         // Number of Register H
                sdata[5] = 0x00;         // Number of Register L

                crc = csSerialCommFunction.GetCRC(sdata, 6);

                sdata[6] = crc[1];         // CRCH
                sdata[7] = crc[0];         // CRCL

                StopUpdate = false;
            }
            else if (mode == 1)
            {
                // Bootloader mode --> Normal mode
                sdata = new byte[5];
                sdata[0] = 0x7F;         // Dev Address
                sdata[1] = 0x21;         // Function Code,,+
                sdata[2] = 0xDE;         // Sub Code

                crc = csSerialCommFunction.GetCRC(sdata, 3);

                sdata[3] = crc[1];         // CRCH
                sdata[4] = crc[0];         // CRCL

                StopFwUpdate();
            }
            OnSendUartData?.Invoke((ushort)SystemId, sdata, false, 0);
        }

        private void RequestFlashErase()
        {
            byte[] sdata = null;
            byte[] crc;

            // Flash Erase
            sdata = new byte[5];
            sdata[0] = 0x7F;         // Dev Address
            sdata[1] = 0x43;         // Function Code
            sdata[2] = 0xBC;         // Sub Code

            crc = csSerialCommFunction.GetCRC(sdata, 3);

            sdata[3] = crc[1];         // CRCH
            sdata[4] = crc[0];         // CRCL

            OnSendUartData?.Invoke((ushort)SystemId, sdata, true, 0);
        }
        private void RequestFlashWrite(int addr, byte[] data)
        {
            byte[] sdata = null;
            byte[] crc;

            // Flash Write
            sdata = new byte[6 + 128 + 2];
            for (int i = 0; i < sdata.Length; i++)
            {
                sdata[i] = 0xFF;
            }
            sdata[0] = 0x7F;         // Dev Address
            sdata[1] = 0x31;         // Function Code
            sdata[2] = 0xCE;         // Sub Code
            sdata[3] = (byte)(addr >> 16);          // Addr H
            sdata[4] = (byte)(addr >> 8);           // Addr L MSB
            sdata[5] = (byte)(addr & 0xff);         // Addr L LSB

            for (int i = 0; i < data.Length; i++)
            {
                sdata[6 + i] = data[i];
            }

            crc = csSerialCommFunction.GetCRC(sdata, sdata.Length - 2);

            sdata[sdata.Length - 2] = crc[1];         // CRCH
            sdata[sdata.Length - 1] = crc[0];         // CRCL

            FwSentLength = data.Length;
            OnSendUartData?.Invoke((ushort)SystemId, sdata, false, 0);
        }
        #endregion

        #region BUTTON EVENT
        private void btnGotoBoot_Click(object sender, EventArgs e)
        {
            OnAutoTxSet?.Invoke(false);

            RequestModeChange(APP_TO_BOOT);
            UpdateStartTime = DateTime.Now;
        }

        private void btnGotoApp_Click(object sender, EventArgs e)
        {
            RequestModeChange(BOOT_TO_APP);
        }

        private void btnFlashErase_Click(object sender, EventArgs e)
        {
            RequestFlashErase();
        }

        private void btnFlashWrite_Click(object sender, EventArgs e)
        {
            byte[] sdata = new byte[128];
            FwCurrPosition = 0;
            for (int i = 0; i < sdata.Length; i++) 
            {
                sdata[i] = fwdata[FwCurrPosition + i];
            }
            RequestFlashWrite(FwStartAddr + FwCurrPosition, sdata);
        }
        #endregion
    }
}