using Edge.Core.Processor;using Edge.Core.IndustryStandardInterface.Pump;
using Edge.Core.Parser.BinaryParser.Util;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Edge.Core.Processor.Communicator;

namespace Wayne_Pump_Dart
{
    public class StateMachineMessageCutter : IMessageCutter<byte[]>
    {
        public byte[] Message { get; private set; }

        public event EventHandler OnMessageCut;
        public event EventHandler<MessageCutterInvalidMessageReadEventArg> OnInvalidMessageRead;
        //static ILog innerLogger = log4net.LogManager.GetLogger("StateMachine");
        static NLog.Logger innerLogger = NLog.LogManager.LoadConfiguration("nlog.config").GetLogger("Communicator");

        private string loggerAppendix = "Wayne_Pump_Dart msgCutter ";
        private readonly List<byte> buffer = new List<byte>();

        /// <summary>
        /// if the 0xFA count is odd, will use round up for count/2.
        /// e.g.: 0xFA appeared 2 times, return 1.
        /// 0xFA appeared 3 times, return 2, this is considered as window is not big enough to include further 0xFA since they're always even.
        /// </summary>
        /// <returns></returns>
        private int Get0xFAPairCountInWindow(IList<byte> target)
        {
            return (int)Math.Round(((double)(target.Count(w => w == 0xFA)) / 2), MidpointRounding.AwayFromZero);
        }

        /// <summary>
        /// found all pair(one besides one) of 0xFA in a list, and reduce the pair to a single 0xFA.
        /// </summary>
        /// <param name="target"></param>
        private int Reduce0xFAPair(IList<byte> target, int from)
        {
            var faAppearedPositions = new List<int>();
            for (int i = from; i < target.Count - 1; i++)
            {
                if (target[i] == 0x10 && target[i + 1] == 0xFA)
                {
                    faAppearedPositions.Add(i);
                    i++;
                }
            }

            for (int i = 0; i < faAppearedPositions.Count; i++)
            {
                target.RemoveAt(faAppearedPositions[i] - i);
            }

            return faAppearedPositions.Count;
        }
        /// <summary>
        /// Code Transparency        
        /// Code transparency for 8 bit data is achieved by Data Link escape(DLE) inser-tion.
        /// DLE insertion is needed when any data byte or CRC has the value SF(stop flag). 
        /// Transmitting device transmit DLE before SF is transmitted in the data field.
        /// Receiving device checks when receiving SF, 
        /// if previous received character was DLE.If so DLE is over written by the received SF 
        /// in the line buffer.Inserted DLE:s are not included in the CRC-calculation.
        /// 
        /// ETX	03H End of text
        /// DLE	10H Data link escape
        /// SF FAH Stop flag

        /// 
        /// Message format:
        /// ADR     CTRL    trans_Number    trans_Length    trans_data      CRC-1	CRC-2	ETX(0x03)   SF(0xFA)
        /// </summary>
        public StateMachineMessageCutter()
        {
        }
        public void Feed(byte[] next)
        {
            try
            {
                //innerLogger.Debug(this.loggerAppendix + " " + next.ToHexLogString() + " is feed in Window in state: " + nextState);
                for (int i = 0; i < next.Length; i++)
                {
                    this.buffer.Add(next[i]);
                    if (this.buffer[0] < 0x50 || this.buffer[0] > 0x6F)
                    {
                        this.buffer.Clear();
                    }

                    //0x30 is data
                    if (this.buffer.Count >= 2 &&
                        ((this.buffer[1] & 0xF0) != 0x30)
                        //ack
                        && ((this.buffer[1] & 0xF0) != 0xC0)
                        //nak
                        && ((this.buffer[1] & 0xF0) != 0x50)
                        //eot
                        && ((this.buffer[1] & 0xF0) != 0x70)
                        //ackpoll
                        && ((this.buffer[1] & 0xF0) != 0xe0))
                    {
                        this.OnInvalidMessageRead?.Invoke(this, new MessageCutterInvalidMessageReadEventArg()
                        {
                            Message = "invalid byte[1]: 0x" + this.buffer[1].ToString("X2") + ", clear buf(valid byte[0]: 0x" + this.buffer[0].ToString("X2") + ") anyway"
                        });
                        this.buffer.Clear();
                    }

                    if (this.buffer.Count >= 3
                            && this.buffer[this.buffer.Count - 2] != 0x10
                            && this.buffer[this.buffer.Count - 1] == 0xFA)
                    {
                        Reduce0xFAPair(this.buffer, 0);
                        if (this.buffer.Count >= 44)
                            innerLogger.Info("Long length(len: " + this.buffer.Count + ") message was cut from MsgCutter: 0x" + this.buffer.ToHexLogString());
                        this.Message = this.buffer.ToArray();
                        var safe = this.OnMessageCut;
                        safe?.Invoke(this, null);
                        this.buffer.Clear();
                    }

                    if (this.buffer.Count >= 45)
                        innerLogger.Info("Long length(len: " + this.buffer.Count + ") message is still constructing in MsgCutter: 0x" + this.buffer.ToHexLogString());
                }
            }
            catch (Exception exx)
            {
                innerLogger.Error($"Wayne_Pump_Dart.StateMachineMessageCutter, " +
                    $"next: 0x{next?.ToHexLogString() ?? "null"}, " +
                    $"this.buffer: 0x{this.buffer?.ToHexLogString() ?? "null"}" +
                    $"{Environment.NewLine}exception detail: {exx}");
                throw;
            }
        }
    }
}