using Edge.Core.Database;
using Edge.Core.Processor;
using Edge.Core.IndustryStandardInterface.Pump;
using Edge.Core.Processor.Communicator;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.DependencyInjection;

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

        public event EventHandler OnMessageCut;
        public event EventHandler<MessageCutterInvalidMessageReadEventArg> OnInvalidMessageRead;

        static ILogger innerLogger = NullLogger.Instance;

        private string loggerAppendix = "Wayne_VaporRecoveryDataCollectorBoard msgCutter ";
        private readonly SizableWindow<byte> window;
        private State nextState = State.Uninitialized;
        public StateMachineMessageCutter() : this(null)
        {
        }

        /// <summary>
        /// 包起始标志(1B)+采集器地址Addr(1B)+命令代码(1B)+数据域长度(1B)+数据域(由数据域长度指定)+校验CS(1B)+包结束标志(1B)。
        ///包起始标志:68;
        ///包结束标志:0x16;
        ///校验:从采集器地址开始(包括采集器地址)到校验字节前所有数据累加求和取低八位数据。
        /// </summary>
        public StateMachineMessageCutter(IServiceProvider services)
        {
            var loggerFactory = services?.GetRequiredService<ILoggerFactory>();
            innerLogger = loggerFactory?.CreateLogger("StateMachineMessageCutter") ?? NullLogger.Instance;
            //this.initWindowSize = initWindowSize;
            this.window = new SizableWindow<byte>();
            this.window.OnWindowFull += (data) =>
            {
                switch (nextState)
                {
                    case State.Uninitialized:
                        if (data.First() == 0x68)
                        {
                            this.window.NewSize = 4;
                            this.nextState = State.LengthReady;
                            if (innerLogger.IsEnabled(LogLevel.Trace))
                                innerLogger.LogTrace(this.loggerAppendix + " in State.Uninitialized read header 0x68, switch to LengthReady");
                        }
                        else
                            this.window.Clear();
                        break;
                    case State.LengthReady:
                        if (this.window.Skip(3).First() >= 40)
                        {
                            innerLogger.LogInformation($"{this.loggerAppendix } MsgBodyLen value: 0x{this.window.Skip(3).First().ToString("X").PadLeft(2, '0')} is too large, treat as error msg.");
                            this.OnInvalidMessageRead?.Invoke(this, new MessageCutterInvalidMessageReadEventArg()
                            {
                                Message = $"{this.loggerAppendix } MsgBodyLen value: 0x{this.window.Skip(3).First().ToString("X").PadLeft(2, '0')} is too large, treat as error msg."
                            });
                            this.nextState = State.Uninitialized;
                            this.window.Clear();
                            return;
                        }

                        if (this.window.Skip(2).First() == 0 && this.window.Skip(3).First() == 0)
                        {
                            /* handle a special case of 'device is in busy' command, it is like: 0x68 06 00 00 16*/
                            this.window.NewSize = 5;
                        }
                        else
                            this.window.NewSize = this.window.Skip(3).First() + 4 + 1 + 1;
                        this.nextState = State.BodyReady;
                        if (innerLogger.IsEnabled(LogLevel.Trace))
                            innerLogger.LogTrace(this.loggerAppendix + " MsgBodyLen caculated with: " + this.window.NewSize);
                        break;
                    case State.BodyReady:
                        if (this.window.Last() != 0x16)
                        {
                            innerLogger.LogInformation($"{this.loggerAppendix } last byte must be 0x16 but now is 0x{this.window.Last().ToString("X").PadLeft(2, '0')}, treat as error msg.");
                            this.OnInvalidMessageRead?.Invoke(this, new MessageCutterInvalidMessageReadEventArg()
                            {
                                Message = $"{this.loggerAppendix } last byte must be 0x16 but now is 0x{this.window.Last().ToString("X").PadLeft(2, '0')}, treat as error msg."
                            });
                            this.nextState = State.Uninitialized;
                            this.window.Clear();
                            return;
                        }

                        this.Message = this.window.ToArray();

                        /* handle a special case of 'device is in busy' command, it is like: 0x68 06 00 00 16*/
                        //make a hack for `device is in busy command` as the parser lib must have a concreate byte data to fill all properties.
                        if (this.Message.Length == 5)
                            this.Message = this.Message.Concat(new byte[] { 0x00 }).ToArray();
                        var safe = this.OnMessageCut;
                        safe?.Invoke(this, null);
                        this.nextState = State.Uninitialized;
                        this.window.Clear();
                        //this.window.NewSize = this.initWindowSize;
                        break;
                    default:
                        throw new ArgumentOutOfRangeException();
                }
            };
        }

        private enum State
        {
            Uninitialized,
            //HeaderSeeking,
            HeaderReady,
            LengthReady,
            BodyReady,
        }

        public void Feed(byte[] next)
        {
            //innerLogger.Debug(this.loggerAppendix + " " + next.ToHexLogString() + " is feed in Window in state: " + nextState);
            for (int i = 0; i < next.Length; i++)
                this.window.Add(next[i]);
        }
    }
}