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.DataStructure;
using LFP_Manager.Function;
using LFP_Manager.Utils;
using static System.Data.Entity.Infrastructure.Design.Executor;

namespace LFP_Manager
{
    public delegate void AutoTxCanSetEnvent(bool autoTx);
    public delegate void SendDataCanEvent(uint header, byte[] data);

    public partial class fmxFwUpdate : DevExpress.XtraEditors.XtraForm
    {
        #region DEFINES

        const int FW_UPDATE_START_ADDR      = 0x8012000;
        const int FW_UPDATE_END_ADDR        = 0x801FFFF;
        const int FW_UPDATE_SECTOR_SIZE     = 0x20000;
        const int FW_UPDATE_PACKET_SIZE     = 0x800;

        #endregion

        #region VARIABLES

        private uint SystemId = 0;

        private byte[] fwdata;
        private byte[] fwver;

        private bool StopUpdate = true;

        private uint SectorEraseAddr = 0;
        private uint SetAddr = 0;
        private uint SetLength = 0;

        private int WR_PACKET_DELAY = 1;

        private DateTime UpdateStartTime;
        private TimeSpan UpdateTime;

        private CommConfig Config;

        public event AutoTxCanSetEnvent OnAutoTxCanSet = null;
        public event SendDataCanEvent OnSendDataCan = null;
        #endregion

        #region CONSTRUCTORS

        public fmxFwUpdate(uint MdID, CommConfig aConfig)
        {
            InitializeComponent();

            SystemId = MdID;
            teModuleNo.Text = string.Format("{0}", SystemId);

            fwdata = null;
            fwver = null;
            btnFwUpdate.Enabled = false;

            Config = aConfig;

            if (csCanConstData.CanDeviceInfo.DeviceIds[Config.CanDevice] == csCanConstData.CanDeviceInfo.VCI_VALUE_CAN4_1)
                WR_PACKET_DELAY = 10;
            else
                WR_PACKET_DELAY = 1;
        }

        #endregion

        #region KEY EVENT FUNCTION
        private void TextBox_KeyPress(object sender, KeyPressEventArgs e)
        {
            csUtils.TypingOnlyNumber(sender, e, true, true);
        }
        #endregion

        #region BUTTON EVENTS

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

        private void btnFwUpdate_Click(object sender, EventArgs e)
        {
            if (teModuleNo.Text == "")
            {
                MessageBox.Show("Please check module no", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }

            uint sId = Convert.ToUInt32(teModuleNo.Text);
            if ((sId == 0) || (sId > 18))
            {
                MessageBox.Show("Please check module no", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }
            SystemId = sId;
            
            if (StopUpdate)
            {
                teModuleNo.Enabled = false;
                if (OnSendDataCan != null)
                {
                    if (OnAutoTxCanSet != null)
                    {
                        OnAutoTxCanSet(false);
                    }

                    if ((fwdata != null) && (fwdata.Length > 0))
                    {
                        UpdateStartTime = DateTime.Now;
                        PACKET_DN packet = csCanFwUpdateFunction.FwUpdateStartPacket(SystemId, fwver, (UInt32)fwdata.Length);
                        OnSendDataCan(packet.hdr, packet.data);
                    }
                    StopUpdate = false;
                    btnFwUpdate.Text = "Cancel Update";
                }
            }
            else
            {
                teModuleNo.Enabled = true;

                StopUpdate = true;
                SectorEraseAddr = 0;
                SetAddr = 0;
                SetLength = 0;
                FirmwareUpdateTimeoutClear();

                pgUpdate.Position = 0;
                lbUpdateStatus.Text = "Update Status : Cancel By User";
                btnFwUpdate.Text = "Firmware Update";

                if (OnAutoTxCanSet != null)
                {
                    OnAutoTxCanSet(true);
                }
            }
        }

        private void btnFind_Click(object sender, EventArgs e)
        {
            OpenFileDialog oDialog;

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

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

                FileStream fwFile = new FileStream(edFilename.Text, FileMode.Open, FileAccess.Read);
                long fsize = fwFile.Length;

                if (fsize > 0)
                {
                    fwdata = new byte[fsize];
                    fwFile.Seek(0, SeekOrigin.Begin);
                    fwFile.Read(fwdata, 0, (int)fsize);
                }
                fwFile.Close();

                byte[] bName = csUtils.StringToByte(Path.GetFileName(edFilename.Text));

                if (bName.Length > 8)
                {
                    fwver = new byte[4];

                    for (int i = 0; i < 4; i++)
                        fwver[i] = (byte)(bName[bName.Length - 1 - 3 - 4 + i] - 0x30);

                    UInt32 nPC = (UInt32)((fwdata[0] << 0) | (fwdata[1] << 8) | (fwdata[2] << 16) | (fwdata[3] << 24));
                    UInt32 nSP = (UInt32)((fwdata[4] << 0) | (fwdata[5] << 8) | (fwdata[6] << 16) | (fwdata[7] << 24));

                    if ((nPC >= 0x20000000) && (nPC <= 0x20100000)
                        && (nSP >= 0x08000000) && (nSP <= 0x08100000))
                    {
                        btnFwUpdate.Enabled = true;
                    }
                    else
                    {
                        nPC = (UInt32)((fwdata[0 + 64] << 0) | (fwdata[1 + 64] << 8) | (fwdata[2 + 64] << 16) | (fwdata[3 + 64] << 24));
                        nSP = (UInt32)((fwdata[4 + 64] << 0) | (fwdata[5 + 64] << 8) | (fwdata[6 + 64] << 16) | (fwdata[7 + 64] << 24));

                        if ((nPC >= 0x20000000) && (nPC <= 0x20100000)
                            && (nSP >= 0x08000000) && (nSP <= 0x08100000))
                        {
                            fsize -= 64;
                            byte[] fwdataB = new byte[fsize];

                            for (int i = 0; i < fsize; i++)
                            {
                                fwdataB[i] = fwdata[i + 64];
                            }
                            fwdata = new byte[fsize];
                            for (int i = 0; i < fsize; i++)
                            {
                                fwdata[i] = fwdataB[i];
                            }
                            btnFwUpdate.Enabled = true;
                        }
                        else
                        {
                            btnFwUpdate.Enabled = false;
                        }
                    }
                    lbFileInfo.Text = String.Format("File Information");
                    lbFileInfo.Text += String.Format("\r\n  - Filename: {0}", Path.GetFileName(edFilename.Text));
                    lbFileInfo.Text += String.Format("\r\n  - File Version: V{0}.{1}.{2}.{3}", fwver[0], fwver[1], fwver[2], fwver[3]);
                    lbFileInfo.Text += String.Format("\r\n  - File Size: {0:#,##0} bytes", fsize);
                    lbFileInfo.Text += String.Format("\r\n  - File Size: {0:#,##0} bytes", fsize);
                    lbFileInfo.Text += String.Format("\r\n  - Packet Size: {0:#,##0} packets", fsize / 2048);
                    lbFileInfo.Text += String.Format("\r\n  - Last packet: {0:#,##0}", fsize % 2048);

                    lbFileInfo.Text += String.Format("\r\n  - PC: 0x{0:X8}", nPC);
                    lbFileInfo.Text += String.Format("\r\n  - SP: 0x{0:X8}", nSP);

                    lbUpdateProgress.Text = String.Format("{0:#,##0}/{1:#,##0} bytes", 0, fsize);
                    pgUpdate.Properties.Maximum = (int)fsize;
                }
            }
        }
        private void btnRestart_Click(object sender, EventArgs e)
        {
            PACKET_DN packet = csCanFwUpdateFunction.UpdateRestartPacket(SystemId, 1);
            OnSendDataCan?.Invoke(packet.hdr, packet.data);
        }

        #endregion

        #region FORM EVENT

        private void fmxFwUpdate_Activated(object sender, EventArgs e)
        {
            if (OnAutoTxCanSet != null)
            {
                OnAutoTxCanSet(false);
            }
        }

        private void fmxFwUpdate_FormClosed(object sender, FormClosedEventArgs e)
        {
            if (OnAutoTxCanSet != null)
            {
                OnAutoTxCanSet(true);
            }
        }

        #endregion

        #region PUBLIC FUNCTION

        public void RecvData(UInt32 rhdr, byte[] rdata)
        {
            PACKET_HEADER_DN dnHdr = csCanFwUpdateFunction.CovertPtoH(rhdr);

            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 (dnHdr.SID == SystemId)
                {
                    FirmwareUpdateTimeoutClear();
                    switch (dnHdr.FC)
                    {
                        case csCanFwUpdateFunction.DL_START_CODE:
                            FwUpdateStartProcess(dnHdr, rdata);
                            break;
                        case csCanFwUpdateFunction.DL_SECTOR_ERASE_CODE:
                            SectorEraseProcess(dnHdr, rdata);
                            break;
                        case csCanFwUpdateFunction.DL_SET_ADDRESS_CODE:
                            SeAddrProcess(dnHdr, rdata);
                            break;
                        case csCanFwUpdateFunction.DL_WRITE_DATA_CSUM_CODE:
                            WritedataChkSumProcess(dnHdr, rdata);
                            break;
                        case csCanFwUpdateFunction.DL_IMAGE_CSUM_CODE:
                            FwImageChkSumProcess(dnHdr, rdata);
                            break;
                        case csCanFwUpdateFunction.DL_RESTART_CODE:
                            FirmwareUpdateStop();
                            break;
                    }
                }
            }
        }

        #endregion

        #region FW UPDATE PROCESS FUNCTIONS
        private void FwUpdateStartProcess(PACKET_HEADER_DN hdr, byte[] data)
        {
            byte rResult = data[7];

            if (rResult == 0)
            {
                if (OnSendDataCan != null)
                {
                    if ((fwdata != null) && (fwdata.Length > 0))
                    {
                        UpdateStartTime = DateTime.Now;

                        SectorEraseAddr = FW_UPDATE_START_ADDR;
                        PACKET_DN packet = csCanFwUpdateFunction.SectorErasePacket(SystemId, FW_UPDATE_START_ADDR);

                        OnSendDataCan?.Invoke(packet.hdr, packet.data);
                    }
                }
            }
            else
            {
                MessageBox.Show(String.Format("FwUpdateStartProcess Fail - {0}", rResult));
            }
        }

        private void SectorEraseProcess(PACKET_HEADER_DN hdr, byte[] data)
        {
            UInt32 rAddr = (UInt32)((data[0] << 24)
                                  | (data[1] << 16)
                                  | (data[2] << 8)
                                  | (data[3] << 0));
            byte rResult = data[7];
            UInt32 eSize = rAddr - FW_UPDATE_START_ADDR + FW_UPDATE_SECTOR_SIZE;

            if (rResult == 0)
            {
                if (eSize < fwdata.Length)
                {
                    SectorEraseAddr += FW_UPDATE_SECTOR_SIZE;
                    PACKET_DN packet = csCanFwUpdateFunction.SectorErasePacket(SystemId, SectorEraseAddr);

                    OnSendDataCan?.Invoke(packet.hdr, packet.data);
                }
                else
                {
                    SetAddr = FW_UPDATE_START_ADDR;
                    SetLength = 2048 / 8;
                    PACKET_DN packet = csCanFwUpdateFunction.SetAddrPacket(SystemId, SetAddr, SetLength);

                    OnSendDataCan?.Invoke(packet.hdr, packet.data);
                }
            }
            else
            {
                MessageBox.Show(String.Format("SectorEraseProcess Fail - {0}", rResult));
            }
        }

        private void SeAddrProcess(PACKET_HEADER_DN hdr, byte[] data)
        {
            byte rResult = data[7];

            if (rResult == 0)
            {
                UInt32 csum32 = 0;
                UInt32 CurrentAddr = SetAddr - FW_UPDATE_START_ADDR;
                UInt32 CurrentLen = SetLength;
                byte[][] pData;
                PACKET_DN packet;

                pData = new byte[CurrentLen][];

                for (int i = 0; i < CurrentLen; i++)
                {
                    pData[i] = new byte[8];
                    for (int j = 0; j < 8; j++)
                    {
                        if ((CurrentAddr + (i * 8) + j) < fwdata.Length)
                            pData[i][j] = fwdata[CurrentAddr + (i * 8) + j];
                        else
                            pData[i][j] = 0x00;
                        csum32 += (UInt32)pData[i][j];
                    }
                    packet = csCanFwUpdateFunction.WritePacket(SystemId, (byte)i, pData[i]);
                    OnSendDataCan?.Invoke(packet.hdr, packet.data);

                    pgUpdate.Position += 8;
                    UpdateTime = DateTime.Now - UpdateStartTime;
                    lbUpdateTime.Text = String.Format("{0:mm}:{1:ss}", UpdateTime, UpdateTime);
                    lbUpdateProgress.Text = String.Format("{0:#,##0}/{1:#,##0} bytes", pgUpdate.Position, fwdata.Length);
                    Thread.Sleep(WR_PACKET_DELAY);

                    Application.DoEvents();
                    if (StopUpdate) { return; }
                }
                packet = csCanFwUpdateFunction.WriteChkSumPacket(SystemId, SetAddr, csum32);

                OnSendDataCan?.Invoke(packet.hdr, packet.data);
            }
            else
            {
                MessageBox.Show(String.Format("SeAddrProcess Fail - {0}", rResult));
            }
        }

        //private void WritedataChkSumProcess(PACKET_HEADER_DN hdr, byte[] data)
        //{
        //    byte rResult = data[7];

        //    if (rResult == 0)
        //    {
        //        UInt32 csum32 = 0;
        //        CurrentAddr += 1;
        //        byte[][] pData;
        //        PACKET_DN packet;
        //        int plen = 256;

        //        int blen = (int)(fwdata.Length - (CurrentAddr * 2048));

        //        if (blen < 2048)
        //            plen = ((blen % 8) > 0) ? (blen / 8) + 1 : blen / 8;

        //        pData = new byte[plen][];

        //        for (int i = 0; i < plen; i++)
        //        {
        //            pData[i] = new byte[8];
        //            for (int j = 0; j < 8; j++)
        //            {
        //                pData[i][j] = fwdata[(CurrentAddr * 2048) + (i * 8) + j];
        //                csum32 += (UInt32)pData[i][j];
        //            }
        //            packet = csCanFwUpdateFunction.WritePacket(1, (byte)i, pData[i]);
        //            OnSendDataCan?.Invoke(packet.hdr, packet.data);

        //            Thread.Sleep(1);
        //        }
        //        packet = csCanFwUpdateFunction.WriteChkSumPacket(1, csum32);

        //        OnSendDataCan?.Invoke(packet.hdr, packet.data);
        //    }
        //}

        private void WritedataChkSumProcess(PACKET_HEADER_DN hdr, byte[] data)
        {
            byte rResult = data[7];

            if (rResult == 0)
            {
                UInt32 rAddr;
                UInt32 rlen, tlen;

                rAddr = (UInt32)((data[0] << 24)
                              | (data[1] << 16)
                              | (data[2] << 8)
                              | (data[3] << 0)
                              );
                rlen = rAddr - SetAddr;
                tlen = rAddr - FW_UPDATE_START_ADDR;

                UpdateTime = DateTime.Now - UpdateStartTime;
                lbUpdateTime.Text = String.Format("{0:mm}:{0:ss}", UpdateTime);
                lbUpdateProgress.Text = String.Format("{0:#,##0}/{1:#,##0} bytes", tlen, fwdata.Length);
                pgUpdate.Position = (int)tlen;

                if (tlen < fwdata.Length)
                {
                    // remain write data
                    if (rlen == FW_UPDATE_PACKET_SIZE)
                    {
                        SetAddr = rAddr;
                        UInt32 alen = (UInt32)(fwdata.Length - tlen);
                        if (alen > FW_UPDATE_PACKET_SIZE)
                        {
                            SetLength = FW_UPDATE_PACKET_SIZE / 8;
                        }
                        else
                        {
                            if ((alen % 8) > 0)
                                SetLength = alen / 8 + 1;
                            else
                                SetLength = alen / 8;
                        }
                        PACKET_DN packet = csCanFwUpdateFunction.SetAddrPacket(SystemId, SetAddr, SetLength);

                        OnSendDataCan?.Invoke(packet.hdr, packet.data);
                    }
                }
                else
                {
                    // finished write data
                    //string text = String.Format("Finished Write data - {0};{1}", tlen, fwdata.Length);
                    //MessageBox.Show(text);
                    UInt32 fwChkSum = 0;

                    for (int i = 0; i < fwdata.Length; i++)
                    {
                        fwChkSum += (UInt32)(fwdata[i]);
                    }
                    PACKET_DN packet = csCanFwUpdateFunction.FwImageChkSumPacket(SystemId, (UInt32)fwdata.Length, fwChkSum);

                    OnSendDataCan?.Invoke(packet.hdr, packet.data);
                }
            }
            else
            {
                MessageBox.Show(String.Format("WritedataChkSumProcess Fail - {0}", rResult));
            }
        }

        private void FwImageChkSumProcess(PACKET_HEADER_DN hdr, byte[] data)
        {
            byte rResult = data[7];

            if (rResult == 0)
            {
                PACKET_DN packet = csCanFwUpdateFunction.UpdateRestartPacket(SystemId, 3);

                OnSendDataCan?.Invoke(packet.hdr, packet.data);

                MessageBox.Show("Firmware Update Complete !!", "FwUpdate", MessageBoxButtons.OK, MessageBoxIcon.Information);
                lbUpdateStatus.Text = "Update Status : Complete";
            }
            else
            {
                MessageBox.Show(String.Format("FwImageChkSumProcess Fail - {0}", rResult));
                lbUpdateStatus.Text = "Update Status : Update failure (Image Checksum fail)";
            }
            btnFwUpdate.Text = "Firmware Update";
        }
        #endregion

        #region UPDATE FUNCTIONS
        private const int FirmwareUpdateTimeoutMax = 30;        // 3 Sec
        private int FirmwareUpdateTimeout = 0;                  // 10 ms

        private void FirmwareUpdateStop()
        {
            StopUpdate = true;
            FirmwareUpdateTimeoutClear();
            teModuleNo.Enabled = true;

            lbUpdateStatus.Text = "Update Status : Restart";
            if (OnAutoTxCanSet != null)
            {
                OnAutoTxCanSet(true);
            }
        }

        private void FirmwareUpdateTimeoutClear()
        {
            FirmwareUpdateTimeout = 0;
        }
        #endregion

        #region TIMER EVENT
        private void tmrFirmwareUpdate_Tick(object sender, EventArgs e)
        {
            if (StopUpdate == false)
            {
                FirmwareUpdateTimeout++;
                if (FirmwareUpdateTimeout > FirmwareUpdateTimeoutMax)
                {
                    FirmwareUpdateStop();
                }
            }
        }
        #endregion
    }
}