using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Threading;
using System.Threading.Tasks;
using System.CodeDom;

namespace arDev
{
    public class BMSCellInformation
    {
        public float[] Voltage = new float[8];
        public float Average
        {
            get
            {
                if (Voltage.Any() == false) return 0f;
                return Voltage.Average();
            }
        }
        public float Delta
        {
            get
            {
                if (Voltage.Any() == false) return 0f;
                var value = Math.Abs(Voltage.Max() - Voltage.Min());
                return value * 1000f;
            }
        }
    }
    public class BMSBasicInformation
    {
        public float packVoltage { get; set; }
        public float current { get; set; }
        public float remainingCapacity { get; set; }
        public float fullCapacity { get; set; }
        public UInt16 cycleCount { get; set; }
        public DateTime productionDate { get; set; }
        public UInt32 fullBalance { get; set; }
        public BMSProtectionStatus protectionStatus { get; set; }
        public byte version { get; set; }
        public byte rsoc { get; set; }
        public BMSMosfetStatus mosfetStatus { get; set; }
        public byte ntcCount { get; set; }
        public float[] ntcTemp { get; set; }

        public ushort raw_protection { get; set; }
        public float watt
        {
            get
            {
                return (this.current * packVoltage);
            }
        }

        /// <summary>
        /// Remain / Full * 100
        /// </summary>
        public float RawLevel
        {
            get
            {
                if (fullCapacity < 1) return 0f;
                return (remainingCapacity / fullCapacity) * 100f;
            }
        }

        public BMSBasicInformation()
        {
            protectionStatus = new BMSProtectionStatus();
            mosfetStatus = new BMSMosfetStatus();
            productionDate = new DateTime();
            ntcTemp = new float[] { 0f, 0f };

        }
        public void Clear()
        {
            packVoltage = 0;
            current = 0;
            remainingCapacity = 0;
            fullBalance = 0;
            fullCapacity = 0;
            cycleCount = 0;
            productionDate = new DateTime();
            protectionStatus = new BMSProtectionStatus();

        }

        public override string ToString()
        {
            return $"packVoltage:{packVoltage}\n" +
                $"current:{current}\n" +
                $"remainingCapacity:{remainingCapacity}\n" +
                $"fullCapacity:{fullCapacity}\n" +
                $"cycleCount:{cycleCount}\n" +
                $"productionDate:{productionDate}\n" +
                $"fullBalance:{fullBalance}\n" +
                $"protectionStatus:{protectionStatus}\n" +
                $"version:{version}\n" +
                $"rsoc:{rsoc}\n" +
                $"mosfetStatus:{mosfetStatus}\n" +
                $"ntcCount:{ntcCount}\n" +
                $"ntcTemp:{ntcTemp}\n" +
                $"watt:{watt}\n" +
                $"RawLevel:{RawLevel}";
        }
    }
    public class BMSProtectionStatus
    {
        public bool covp { get; set; }// Cell Over Voltage Protection
        public bool cuvp { get; set; } // Cell Under Voltage Protection
        public bool povp { get; set; } // Pack Over Voltage Protection
        public bool puvp { get; set; } // Pack Under Voltage Protection
        public bool chgot { get; set; }// Charge Over Temp
        public bool chgut { get; set; } // Charge Under Temp
        public bool dsgot { get; set; } // Discharge Over Temp
        public bool dsgut { get; set; } // Discharge Under Temp
        public bool chgoc { get; set; } // Charge Over Current
        public bool dsgoc { get; set; } // Discharge Over Current
        public bool sc { get; set; } // Short Circuit
        public bool afe { get; set; } // AFE Error
        public override string ToString()
        {
            return "BMSProtectionStatus\n" +
            $"covp={covp}\n" +// { get; set; }// Cell Over Voltage Protection
            $"cuvp={cuvp}\n" +// { get; set; } // Cell Under Voltage Protection
            $"povp={povp}\n" +// { get; set; } // Pack Over Voltage Protection
            $"puvp={puvp}\n" +// { get; set; } // Pack Under Voltage Protection
            $"chgot={chgot}\n" +// { get; set; }// Charge Over Temp
            $"chgut={chgut}\n" +// { get; set; } // Charge Under Temp
            $"dsgot={dsgot}\n" +// { get; set; } // Discharge Over Temp
            $"dsgut={dsgut}\n" +// { get; set; } // Discharge Under Temp
            $"chgoc={chgoc}\n" +// { get; set; } // Charge Over Current
            $"dsgoc={dsgoc}\n" +// { get; set; } // Discharge Over Current
            $"sc={sc}\n" +// { get; set; } // Short Circuit
            $"afe={afe}";// +// { get; set; } // AFE Error
        }
    }
    public class BMSMosfetStatus
    {
        public bool charge { get; set; }
        public bool discharge { get; set; }
        public override string ToString()
        {
            return $"charge:{charge}\n" +
                $"discharge:{discharge}";
        }

    }

    public class BMS : BMSSerialComm
    {
        public BMS()
        {


        }

        /// <summary>
        /// 시리얼포트의 핀 상태값이 변경될 때 발생합니다.
        /// </summary>
        public event EventHandler<BMSInformationEventArgs> BMSDataReceive;
        public event EventHandler<BMSCelvoltageEventArgs> BMSCellDataReceive;

        protected override bool CustomParser(byte[] buf, out byte[] remainBuffer)
        {
            List<byte> remain = new List<byte>();

            //데이터는 총 34byt 이며 , DD~77로 구성됨
            Boolean bComplete = false;
            for (int i = 0; i < buf.Length; i++)
            {
                var incomByte = buf[i];
                if (bComplete == true)
                {
                    remain.Add(incomByte);
                }
                else
                {
                    if (findSTX == false)
                    {
                        if (incomByte != 0xDD)
                        {
                            //버리는데이터
                        }
                        else
                        {
                            findSTX = true;
                            tempBuffer.Clear();
                            tempBuffer.Add(incomByte);
                        }
                    }
                    else
                    {
                        tempBuffer.Add(incomByte);

                        if (tempBuffer.Count > 7)
                        {
                            byte len = tempBuffer[3];
                            if (tempBuffer.Count >= 4 + len + 3) // Start+Reg+Status+Len + Data + Chk(2) + End
                            {
                                if (tempBuffer.Last() == 0x77)
                                {
                                    //데이터가 맞게 수신됨
                                    LastReceiveBuffer = tempBuffer.ToArray();
                                    bComplete = true;
                                }
                                else
                                {
                                    //종단기호가 맞지 않다. 이자료는 폐기한다.
                                    var hexstr = string.Join(" ", tempBuffer.Select(t => t.ToString("X2")));
                                    RaiseMessage(MessageType.Error, $"discard : {hexstr}");
                                    tempBuffer.Clear();
                                }
                                findSTX = false;
                            }
                        }

                        //                        [22 - 12 - 27 14:32:49] open: True
                        //[22 - 12 - 27 14:32:49] Send: DD A5 03 00 FF FD 77 0D
                        //[22 - 12 - 27 14:32:50] Send: DD A5 03 00 FF FD 77 0D
                        //[22 - 12 - 27 14:32:50] Recv: 26.61v,81.4 %
                        //[22 - 12 - 27 14:32:50] Recv: DD 03 00 1B 0A 65 00 00 21 63 29 04 00 00 2C 92 00 00 00 00 00 00 28 51 03 08 02 0B 69 0B 66 FC 9C 77
                        //[22 - 12 - 27 14:32:50] Send: DD A5 03 00 FF FD 77 0D
                        //[22 - 12 - 27 14:32:51] Recv: 26.61v,81.4 %
                        //[22 - 12 - 27 14:32:51] Recv: DD 03 00 1B 0A 65 00 00 21 63 29 04 00 00 2C 92 00 00 00 00 00 00 28 51 03 08 02 0B 69 0B 66 FC 9C 77


                        //var queylen = QueryIndex == 0 ? 34 : 23;
                        //if (tempBuffer.Count == queylen)
                        //{
                        //    if (incomByte != 0x77)
                        //    {
                        //        //종단기호가 맞지 않다. 이자료는 폐기한다.
                        //        var hexstr = string.Join(" ", tempBuffer.Select(t => t.ToString("X2")));
                        //        RaiseMessage(MessageType.Error, $"discard : {hexstr}");
                        //        tempBuffer.Clear();
                        //    }
                        //    else
                        //    {
                        //        //데이터가 맞게 수신됨
                        //        LastReceiveBuffer = tempBuffer.ToArray();
                        //        bComplete = true;
                        //    }
                        //    findSTX = false;
                        //}
                        //else
                        //{
                        //    //아직 모자르므로 대기한다
                        //}
                    }
                }
            }

            remainBuffer = remain.ToArray();
            return bComplete;
        }

        bool Recv0 = false;
        bool Recv1 = false;
        public event EventHandler<ChargetDetectArgs> ChargeDetect;
        public override bool ProcessRecvData(byte[] data)
        {
            //LastReceiveBuffer
            if (data == null)
            {
                RaiseMessage(MessageType.Error, "수신 데이터가 없습니다");
                return false;
            }

            var datalne = QueryIndex == 0 ? 34 : 23;
            if (data.Length != datalne)
            {
                RaiseMessage(MessageType.Error, $"데이터의 길이가 {MinRecvLength}가 아닙니다 길이={data.Length}");
                return false;
            }
            else
            {
                var rxstr = string.Join(" ", data.Select(t => t.ToString("X2")));
                RaiseMessage(MessageType.Recv, rxstr);
            }

            if (QueryIndex == 0)
            {
                return ParseBMSInfo();
            }
            else
            {
                return ParseBMSCellVoltage();
            }


        }
        bool ParseBMSCellVoltage()
        {
            //var i = 0;
            var BatteryCell_Checksum = 0xffff;// - (LastReceiveBuffer[i + 3] + LastReceiveBuffer[i + 4] + LastReceiveBuffer[i + 5] + LastReceiveBuffer[i + 6] + LastReceiveBuffer[i + 7] + LastReceiveBuffer[i + 8] + LastReceiveBuffer[i + 9] + LastReceiveBuffer[i + 10] + LastReceiveBuffer[i + 11] + LastReceiveBuffer[i + 12] + LastReceiveBuffer[i + 13] + LastReceiveBuffer[i + 14] + LastReceiveBuffer[i + 15] + LastReceiveBuffer[i + 16] + LastReceiveBuffer[i + 17] + LastReceiveBuffer[i + 18] + LastReceiveBuffer[i + 19])) + 1;
            for (int i = 3; i < 20; i++)
            {
                BatteryCell_Checksum -= LastReceiveBuffer[i];// + LastReceiveBuffer[i + 4] + LastReceiveBuffer[i + 5] + LastReceiveBuffer[i + 6] + LastReceiveBuffer[i + 7] + LastReceiveBuffer[i + 8] + LastReceiveBuffer[i + 9] + LastReceiveBuffer[i + 10] + LastReceiveBuffer[i + 11] + LastReceiveBuffer[i + 12] + LastReceiveBuffer[i + 13] + LastReceiveBuffer[i + 14] + LastReceiveBuffer[i + 15] + LastReceiveBuffer[i + 16] + LastReceiveBuffer[i + 17] + LastReceiveBuffer[i + 18] + LastReceiveBuffer[i + 19])) + 1;
            }

            BatteryCell_Checksum += 1;
            var recvchecksum = BitConverter.ToUInt16(LastReceiveBuffer.Skip(20).Take(2).Reverse().ToArray(), 0);
            if (recvchecksum == BatteryCell_Checksum)
            {
                var v1 = BitConverter.ToUInt16(LastReceiveBuffer.Skip(4).Take(2).Reverse().ToArray(), 0) / 1000f;
                var v2 = BitConverter.ToUInt16(LastReceiveBuffer.Skip(6).Take(2).Reverse().ToArray(), 0) / 1000f;
                var v3 = BitConverter.ToUInt16(LastReceiveBuffer.Skip(8).Take(2).Reverse().ToArray(), 0) / 1000f;
                var v4 = BitConverter.ToUInt16(LastReceiveBuffer.Skip(10).Take(2).Reverse().ToArray(), 0) / 1000f;
                var v5 = BitConverter.ToUInt16(LastReceiveBuffer.Skip(12).Take(2).Reverse().ToArray(), 0) / 1000f;
                var v6 = BitConverter.ToUInt16(LastReceiveBuffer.Skip(14).Take(2).Reverse().ToArray(), 0) / 1000f;
                var v7 = BitConverter.ToUInt16(LastReceiveBuffer.Skip(16).Take(2).Reverse().ToArray(), 0) / 1000f;
                var v8 = BitConverter.ToUInt16(LastReceiveBuffer.Skip(18).Take(2).Reverse().ToArray(), 0) / 1000f;

                var idx = 0;
                BMSCellVoltage.Voltage[idx++] = v1;
                BMSCellVoltage.Voltage[idx++] = v2;
                BMSCellVoltage.Voltage[idx++] = v3;
                BMSCellVoltage.Voltage[idx++] = v4;
                BMSCellVoltage.Voltage[idx++] = v5;
                BMSCellVoltage.Voltage[idx++] = v6;
                BMSCellVoltage.Voltage[idx++] = v7;
                BMSCellVoltage.Voltage[idx++] = v8;

                Recv1 = true;

                try
                {
                    BMSCellDataReceive?.Invoke(this, new BMSCelvoltageEventArgs(v1, v2, v3, v4, v5, v6, v7, v8));
                    Current_CellTime = DateTime.Now;
                    return true;
                }
                catch (Exception ex)
                {
                    RaiseMessage(MessageType.Error, ex.Message);
                    return false;
                }
            }
            else return false;
        }

        bool ParseBMSInfo()
        {
            var newinfo = new BMSBasicInformation();

            //전압확인
            var offset = 4;
            UInt16 batH = (UInt16)LastReceiveBuffer[offset + 0];
            UInt16 batL = (UInt16)LastReceiveBuffer[offset + 1];
            batH = (UInt16)(batH << 8);
            batH = (UInt16)(batH | batL);
            newinfo.packVoltage = (float)(batH / 100.0);

            //충방전전류
            Int16 batHi = (Int16)LastReceiveBuffer[offset + 2];
            Int16 batLi = (Int16)LastReceiveBuffer[offset + 3];
            batHi = (Int16)(batHi << 8);
            batHi = (Int16)(batHi | batLi);
            newinfo.current = (float)(batHi / 100.0);

            //잔량확인
            batH = (UInt16)LastReceiveBuffer[offset + 4];
            batL = (UInt16)LastReceiveBuffer[offset + 5];
            batH = (UInt16)(batH << 8);
            batH = (UInt16)(batH | batL);
            newinfo.remainingCapacity = (float)(batH / 100.0);

            //총량확인
            batH = (UInt16)LastReceiveBuffer[offset + 6];
            batL = (UInt16)LastReceiveBuffer[offset + 7];
            batH = (UInt16)(batH << 8);
            batH = (UInt16)(batH | batL);
            newinfo.fullCapacity = (float)(batH / 100.0);

            //cycle
            batH = (UInt16)LastReceiveBuffer[offset + 8];
            batL = (UInt16)LastReceiveBuffer[offset + 9];
            batH = (UInt16)(batH << 8);
            batH = (UInt16)(batH | batL);
            newinfo.cycleCount = batH;

            //productiondate
            batH = (UInt16)LastReceiveBuffer[offset + 10];
            batL = (UInt16)LastReceiveBuffer[offset + 11];
            batH = (UInt16)(batH << 8);
            batH = (UInt16)(batH | batL);
            var info_productiondateint = batH;
            var date_year = (info_productiondateint >> 9) + 2000;
            var date_month = (info_productiondateint >> 5) & 0x0F;
            var date_day = info_productiondateint & 0x1F;
            newinfo.productionDate = new DateTime(date_year, date_month, date_day);

            //balnace status
            batH = (UInt16)LastReceiveBuffer[offset + 12];
            batL = (UInt16)LastReceiveBuffer[offset + 13];
            batH = (UInt16)(batH << 8);
            var balanceStatus = (UInt16)(batH | batL);

            //balnace status(HIGH)
            batH = (UInt16)LastReceiveBuffer[offset + 14];
            batL = (UInt16)LastReceiveBuffer[offset + 15];
            batH = (UInt16)(batH << 8);
            var balanceStatusHigh = (UInt16)(batH | batL);
            newinfo.fullBalance = (UInt32)(balanceStatus | (balanceStatusHigh << 16));

            //protectionStatusRaw
            batH = (UInt16)LastReceiveBuffer[offset + 16];
            batL = (UInt16)LastReceiveBuffer[offset + 17];
            batH = (UInt16)(batH << 8);
            newinfo.raw_protection = batH;// view.getUint16(16, false);
            var protectionStatusRaw = newinfo.raw_protection;
            newinfo.protectionStatus.covp = (protectionStatusRaw & 1) > 0;
            newinfo.protectionStatus.cuvp = ((protectionStatusRaw >> 1) & 1) > 0;
            newinfo.protectionStatus.povp = ((protectionStatusRaw >> 2) & 1) > 0;
            newinfo.protectionStatus.puvp = ((protectionStatusRaw >> 3) & 1) > 0;
            newinfo.protectionStatus.chgot = ((protectionStatusRaw >> 4) & 1) > 0;
            newinfo.protectionStatus.chgut = ((protectionStatusRaw >> 5) & 1) > 0;
            newinfo.protectionStatus.dsgot = ((protectionStatusRaw >> 6) & 1) > 0;
            newinfo.protectionStatus.dsgut = ((protectionStatusRaw >> 7) & 1) > 0;
            newinfo.protectionStatus.chgoc = ((protectionStatusRaw >> 8) & 1) > 0;
            newinfo.protectionStatus.dsgoc = ((protectionStatusRaw >> 9) & 1) > 0;
            newinfo.protectionStatus.sc = ((protectionStatusRaw >> 10) & 1) > 0;
            newinfo.protectionStatus.afe = ((protectionStatusRaw >> 11) & 1) > 0;


            //version
            newinfo.version = LastReceiveBuffer[offset + 18];
            newinfo.rsoc = LastReceiveBuffer[offset + 19];
            var mosfetRaw = LastReceiveBuffer[offset + 20];
            newinfo.mosfetStatus.charge = (mosfetRaw & 1) == 1;
            newinfo.mosfetStatus.discharge = ((mosfetRaw >> 1) & 1) == 1;


            //250620 jwlee 추가
            newinfo.ntcCount = LastReceiveBuffer[offset + 22]; //센서갯수
            int temp1 = (LastReceiveBuffer[offset + 23] << 8) | LastReceiveBuffer[offset + 24];
            int temp2 = (LastReceiveBuffer[offset + 25] << 8) | LastReceiveBuffer[offset + 26];
            var Current_temp1 = (temp1 - 2731) / 10f;
            var Current_temp2 = (temp2 - 2731) / 10f;
            newinfo.ntcTemp = new float[] { (temp1 - 2731) / 10f, (temp2 - 2731) / 10f };

            CheckManualCharge(newinfo);

            Recv0 = true;

            try
            {
                this.BMSInformation = newinfo;
                BMSDataReceive?.Invoke(this, new BMSInformationEventArgs(BMSInformation));
                Current_DataTime = DateTime.Now;
                return true;
            }
            catch (Exception ex)
            {
                RaiseMessage(MessageType.Error, ex.Message);
                return false;
            }
        }

        public BMSBasicInformation BMSInformation = new BMSBasicInformation();
        public BMSCellInformation BMSCellVoltage = new BMSCellInformation();


        /// <summary>
        /// 현재 충전중인지?
        /// </summary>
        public bool IsCharging { get; private set; }
        DateTime ChargeStart = DateTime.Now;
        DateTime ChargeEnd = DateTime.Now;
        void CheckManualCharge(BMSBasicInformation info)
        {
            //충방전전력이 1보다 크면 충전으로 한다.
            if (this.BMSInformation.current > 0.1)
            {
                //기존에 충전상태가 OFF였다면 충전중으로 알려준다
                if (IsCharging == false)
                {
                    IsCharging = true;
                    ChargeStart = DateTime.Now;
                    ChargeEnd = new DateTime(1982, 11, 23);
                    try
                    {
                        ChargeDetect?.Invoke(this, new ChargetDetectArgs(ChargeStart, true, info.RawLevel));
                    }
                    catch (Exception ex) { RaiseMessage(MessageType.Error, ex.Message); }
                }
                else
                {
                    //충전상태가 유지되고 있다.
                }
            }
            else
            {
                //충전이해제되었다.. 단 바로 해제하지않고 1초정도 텀을 주고 OFF한다.
                if (IsCharging)
                {
                    if (ChargeEnd.Year == 1982)
                    {
                        ChargeEnd = DateTime.Now;
                    }
                    else
                    {
                        var ts = DateTime.Now - ChargeEnd;
                        if (ts.TotalSeconds > 2) //충전종료시그널후 2초후에 충전off를 알린다.
                        {
                            ChargeEnd = DateTime.Now;
                            IsCharging = false;
                            try
                            {
                                ChargeDetect?.Invoke(this, new ChargetDetectArgs(ChargeEnd, false, info.RawLevel));
                            }
                            catch (Exception ex) { RaiseMessage(MessageType.Error, ex.Message); }
                        }
                    }
                }
                else
                {
                    //방전상태가 유지되고 있다.
                }
            }


        }

        public DateTime chk_times { get; set; } = new DateTime(1982, 11, 23);
        public DateTime chk_timee { get; set; } = new DateTime(1982, 11, 23);
        public float chk_values { get; set; } = 0f;
        public float chk_valuee { get; set; } = 0f;
        public DateTime Current_DataTime = DateTime.Parse("1982-11-23");
        public DateTime Current_CellTime = DateTime.Parse("1982-11-23");

        public int QueryIndex { get; set; } = 0;

        /// <summary>
        ///  상태읽기와 전압읽기명령을 반복합니다
        /// </summary>
        /// <returns></returns>
        public Boolean SendQuery()
        {
            if (QueryIndex == 0)
            {
                if (Recv0 == true)
                {
                    QueryIndex = 1;
                    Recv1 = false;
                    return true;
                }
                else
                {
                    return SendQuery_ReadStatue();
                }
            }
            else
            {
                if (Recv1 == true)
                {
                    QueryIndex = 0;
                    Recv0 = false;
                    return true;
                }
                else
                {
                    return SendQuery_ReadCellvoltage();
                }
            }
        }

        public Boolean SendQuery_ReadStatue()
        {
            Recv0 = false;
            var cmd = new List<byte>();
            cmd.Add(0xDD);
            cmd.Add(0xA5);
            cmd.Add(0x03);
            cmd.Add(0x00);
            cmd.Add(0xFF);
            cmd.Add(0xFD);
            cmd.Add(0x77);
            //cmd.Add(0x0D);
            //_device.DiscardInBuffer();
            return WriteData(cmd.ToArray());
        }


        public Boolean SendQuery_ReadCellvoltage()
        {
            Recv1 = false;
            var cmd = new List<byte>();
            cmd.Add(0xDD);
            cmd.Add(0xA5);
            cmd.Add(0x04);
            cmd.Add(0x00);
            cmd.Add(0xFF);
            cmd.Add(0xFC);
            cmd.Add(0x77);
            //cmd.Add(0x0D);
            //_device.DiscardInBuffer();
            return WriteData(cmd.ToArray());
        }

    }
}
