using Edge.Core.Processor;
using Edge.Core.IndustryStandardInterface.Pump;
using Dfs.WayneChina.HengshanPayTerminal.MessageEntity;
using Dfs.WayneChina.HengshanPayTerminal.MessageEntity.Incoming;
using Dfs.WayneChina.HengshanPayTerminal.Support;
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Wayne.FDCPOSLibrary;
using Edge.Core.Processor.Dispatcher.Attributes;
using System.Text.RegularExpressions;
using System.Text.Json;

namespace Dfs.WayneChina.HengshanPayTerminal
{
    /// <summary>
    /// Handler that communicates directly with the Hengshan Payment Terminal for card handling and pump handling via serial port.
    /// </summary>
    [MetaPartsDescriptor(
        "lang-zh-cn:恒山IC卡终端(UI板) Applang-en-us:Hengshan IC card terminal (UI Board)",
        "lang-zh-cn:用于与UI板通讯控制加油机" +
        "lang-en-us:Used for terminal communication to control pumps",
        new[] 
        { 
            "lang-zh-cn:恒山IC卡终端lang-en-us:HengshanICTerminal"
        })]
    public class HengshanPayTermHandler : IEnumerable<IFdcPumpController>, IDeviceHandler<byte[], CardMessageBase>
    {
        #region Fields

        private string pumpIds;
        private string pumpSubAddresses;
        private string pumpNozzles;
        private string pumpSiteNozzleNos;
        private string nozzleLogicIds;

        private IContext<byte[], CardMessageBase> _context;
        private List<HengshanPumpHandler> pumpHandlers = new List<HengshanPumpHandler>();

        public Queue<CardMessageBase> queue = new Queue<CardMessageBase>();
        private object syncObj = new object();

        private ConcurrentDictionary<int, PumpStateHolder> statusDict = new ConcurrentDictionary<int, PumpStateHolder>();

        public ConcurrentDictionary<int, PumpStateHolder> PumpStatusDict => statusDict;

        private Dictionary<int, int> pumpIdSubAddressDict;

        public Dictionary<int, List<int>> PumpNozzlesDict { get; private set; }

        public Dictionary<int, int> NozzleLogicIdDict { get; private set; }

        public Dictionary<int, List<int>> PumpSiteNozzleNoDict { get; private set; }

        #endregion

        #region Logger

        private static NLog.Logger logger = NLog.LogManager.LoadConfiguration("NLog.config").GetLogger("IPosPlusApp");

        #endregion

        #region Constructor

        //private static List<object> ResolveCtorMetaPartsConfigCompatibility(string incompatibleCtorParamsJsonStr)
        //{
        //    var jsonParams = JsonDocument.Parse(incompatibleCtorParamsJsonStr).RootElement.EnumerateArray().ToArray();

        //    //sample:   "UITemplateVersion":"1.0"
        //    string uiTemplateVersionRegex = @"(?<=""UITemplateVersion""\:\"").+?(?="")";
        //    var match = Regex.Match(jsonParams.First().GetRawText(), uiTemplateVersionRegex, RegexOptions.IgnoreCase | RegexOptions.Multiline);

        //    if (match.Success)
        //    {
        //        var curVersion = match.Value;
        //        if (curVersion == "1.0")
        //        {
        //            var existsAppConfigV1 = JsonSerializer.Deserialize(jsonParams.First().GetRawText(), typeof(HengshanPayTerminalHanlderGroupConfigV1));
        //        }
        //        else
        //        { 
        //        }
        //    }

        //    return null;
        //}

        [ParamsJsonSchemas("TermHandlerGroupCtorParamsJsonSchemas")]
        public HengshanPayTermHandler(HengshanPayTerminalHanlderGroupConfigV2 config)
            : this(config.PumpIds,
                  string.Join(";", config.PumpSubAddresses.Select(m => $"{m.PumpId}={m.SubAddress}")),
                  string.Join(";", config.PumpNozzleLogicIds.Select(m => $"{m.PumpId}={m.LogicIds}")),
                  string.Join(";", config.PumpSiteNozzleNos.Select(m => $"{m.PumpId}={m.SiteNozzleNos}")),
                  string.Join(";", config.NozzleLogicIds.Select(m => $"{m.NozzleNo}={m.LogicId}")))
        {

        }

        public HengshanPayTermHandler(
            string pumpIds,
            string pumpSubAddresses,
            string pumpNozzles, 
            string pumpSiteNozzleNos, 
            string nozzleLogicIds)
        {
            this.pumpIds = pumpIds;
            this.pumpSubAddresses = pumpSubAddresses;
            this.pumpNozzles = pumpNozzles;
            this.pumpSiteNozzleNos = pumpSiteNozzleNos;
            this.nozzleLogicIds = nozzleLogicIds;

            AssociatedPumpIds = GetPumpIdList(pumpIds);
            pumpIdSubAddressDict = InitializePumpSubAddressMapping();
            PumpNozzlesDict = ParsePumpNozzlesList(pumpNozzles);
            PumpSiteNozzleNoDict = ParsePumpSiteNozzleNoList(pumpSiteNozzleNos);
            NozzleLogicIdDict = InitializeNozzleLogicIdMapping(nozzleLogicIds);

            InitializePumpHandlers();
        }

        #endregion

        public void OnFdcServerInit(Dictionary<string, object> parameters)
        {
            logger.Info("OnFdcServerInit called");

            if (parameters.ContainsKey("LastPriceChange"))
            {
                // nozzle logical id:rawPrice
                var lastPriceChanges = parameters["LastPriceChange"] as Dictionary<byte, int>;
                foreach (var priceChange in lastPriceChanges)
                {
                }
            }
        }

        #region Event handler

        public event EventHandler<TerminalMessageEventArgs> OnTerminalMessageReceived;

        public event EventHandler<TotalizerDataEventArgs> OnTotalizerReceived;

        public event EventHandler<FuelPriceChangeRequestEventArgs> OnFuelPriceChangeRequested;

        public event EventHandler<FuelPriceDownloadRequestedEventArgs> OnTerminalFuelPriceDownloadRequested;

        public event EventHandler<CheckCommandEventArgs> OnCheckCommandReceived;

        public event EventHandler<LockUnlockEventArgs> OnLockUnlockCompleted;

        #endregion

        #region Properties

        public List<int> AssociatedPumpIds { get; private set; }

        public IContext<byte[], CardMessageBase> Context
        {
            get { return _context; }
        }

        public string PumpIdList => pumpIds;

        //public LockUnlockOperation LockUnlockOperationType { get; set; } = LockUnlockOperation.Undefined;

        #endregion

        #region Methods

        public int GetSubAddressForPump(int pumpId)
        {
            return pumpIdSubAddressDict.First(d => d.Key == pumpId).Value;
        }

        private List<int> GetPumpIdList(string pumpIds)
        {
            var pumpIdList = new List<int>();
            if (!string.IsNullOrEmpty(pumpIds) && pumpIds.Contains(',')) //multiple pumps per serial port, Hengshan TQC pump
            {
                var arr = pumpIds.Split(',');
                foreach (var item in arr)
                {
                    pumpIdList.Add(int.Parse(item));
                }
                return pumpIdList;
            }
            else if (!string.IsNullOrEmpty(pumpIds) && pumpIds.Length == 1 || pumpIds.Length == 2) //only 1 pump per serial port, Hengshan pump
            {
                return new List<int> { int.Parse(pumpIds) };
            }
            else
            {
                throw new ArgumentException("Pump id list not specified!");
            }
        }

        private Dictionary<int, int> InitializePumpSubAddressMapping()
        {
            var dict = new Dictionary<int, int>();
            if (!string.IsNullOrEmpty(pumpSubAddresses))
            {
                var sequence = pumpSubAddresses.Split(';')
                    .Select(s => s.Split('='))
                    .Select(a => new { PumpId = int.Parse(a[0]), SubAddress = int.Parse(a[1]) });

                foreach (var pair in sequence)
                {
                    if (!dict.ContainsKey(pair.PumpId))
                    {
                        dict.Add(pair.PumpId, pair.SubAddress);
                    }
                }

                return dict;
            }
            else
            {
                throw new ArgumentException("Pump id and sub address mapping does not exist");
            }
        }

        private Dictionary<int, List<int>> ParsePumpNozzlesList(string pumpNozzles)
        {
            Dictionary<int, List<int>> pumpNozzlesDict = new Dictionary<int, List<int>>();

            if (!string.IsNullOrEmpty(pumpNozzles) && pumpNozzles.Contains(';'))
            {
                var arr = pumpNozzles.Split(';');
                foreach (var subMapping in arr)
                {
                    var pair = new KeyValuePair<int, int>(int.Parse(subMapping.Split('=')[0]), int.Parse(subMapping.Split('=')[1]));
                    Console.WriteLine($"{pair.Key}, {pair.Value}");

                    if (!pumpNozzlesDict.ContainsKey(pair.Key))
                    {
                        pumpNozzlesDict.Add(pair.Key, new List<int> { pair.Value });
                    }
                    else
                    {
                        List<int> nozzlesForThisPump;
                        pumpNozzlesDict.TryGetValue(pair.Key, out nozzlesForThisPump);
                        if (nozzlesForThisPump != null && !nozzlesForThisPump.Contains(pair.Value))
                        {
                            nozzlesForThisPump.Add(pair.Value);
                        }
                    }
                }
            }
            else if (!string.IsNullOrEmpty(pumpNozzles) && pumpNozzles.Count(c => c == '=') == 1) // only one pump per serial port
            {
                try
                {
                    pumpNozzlesDict.Add(
                        int.Parse(pumpNozzles.Split('=')[0]),
                        new List<int> { int.Parse(pumpNozzles.Split('=')[1]) });
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex);
                }
            }
            else
            {
                throw new ArgumentException("Wrong mapping between pump and its associated nozzles!");
            }

            return pumpNozzlesDict;
        }

        static Dictionary<int, List<int>> ParsePumpSiteNozzleNoList(string pumpSiteNozzleNos)
        {
            Dictionary<int, List<int>> pumpSiteNozzleNoDict = new Dictionary<int, List<int>>();

            if (!string.IsNullOrEmpty(pumpSiteNozzleNos) && pumpSiteNozzleNos.Contains(';'))
            {
                var arr = pumpSiteNozzleNos.Split(';');
                foreach (var subMapping in arr)
                {
                    var pair = new KeyValuePair<int, List<int>>(
                        int.Parse(subMapping.Split('=')[0]), subMapping.Split('=')[1].Split(',').Select(a => int.Parse(a)).ToList());

                    Console.WriteLine($"{pair.Key}, {pair.Value}");

                    if (!pumpSiteNozzleNoDict.ContainsKey(pair.Key))
                    {
                        pumpSiteNozzleNoDict.Add(pair.Key, pair.Value);
                    }
                }
            }
            else if (!string.IsNullOrEmpty(pumpSiteNozzleNos) && pumpSiteNozzleNos.Count(c => c == '=') == 1)
            {

                try
                {
                    string[] strArr = pumpSiteNozzleNos.Split('=');
                    pumpSiteNozzleNoDict.Add(
                        int.Parse(strArr[0]), new List<int> { int.Parse(strArr[1]) });
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex);
                }
            }
            else
            {
                throw new ArgumentException("Wrong mapping between pump and its associated nozzles!");
            }

            return pumpSiteNozzleNoDict;
        }

        private Dictionary<int, int> InitializeNozzleLogicIdMapping(string nozzleLogicIds)
        {
            var dict = new Dictionary<int, int>();
            if (!string.IsNullOrEmpty(nozzleLogicIds))
            {
                var sequence = nozzleLogicIds.Split(';')
                    .Select(s => s.Split('='))
                    .Select(a => new { NozzleNo = int.Parse(a[0]), LogicId = int.Parse(a[1]) });

                foreach (var pair in sequence)
                {
                    if (!dict.ContainsKey(pair.NozzleNo))
                    {
                        Console.WriteLine($"nozzle, logic id: {pair.NozzleNo} - {pair.LogicId}");
                        dict.Add(pair.NozzleNo, pair.LogicId);
                    }
                }

                return dict;
            }
            else if (!string.IsNullOrEmpty(nozzleLogicIds) && nozzleLogicIds.Count(c => c == '=') == 1)
            {
                try
                {
                    string[] sequence = nozzleLogicIds.Split('=');
                    dict.Add(int.Parse(sequence[0]), int.Parse(sequence[1]));
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex);
                }
                return dict;
            }
            else
            {
                throw new ArgumentException("Pump id and sub address mapping does not exist");
            }
        }

        private void InitializePumpHandlers()
        {
            var pumpIdList = GetPumpIdList(pumpIds);

            foreach (var item in pumpIdList)
            {
                var nozzleList = GetNozzleListForPump(item);
                var siteNozzleNoList = PumpSiteNozzleNoDict[item];
                HengshanPumpHandler pumpHandler = new HengshanPumpHandler(this, $"Pump_{item}", item, nozzleList, siteNozzleNoList);
                pumpHandler.OnFuelPriceChangeRequested += PumpHandler_OnFuelPriceChangeRequested;
                pumpHandlers.Add(pumpHandler);
            }
        }

        private List<int> GetNozzleListForPump(int pumpId)
        {
            List<int> nozzles;
            PumpNozzlesDict.TryGetValue(pumpId, out nozzles);
            return nozzles;
        }

        private void PumpHandler_OnFuelPriceChangeRequested(object sender, FuelPriceChangeRequestEventArgs e)
        {
            InfoLog($"Change price, Pump {e.PumpId}, Nozzle {e.NozzleId}, Price {e.Price}");

            OnFuelPriceChangeRequested?.Invoke(sender, e);
        }

        IEnumerator<IFdcPumpController> IEnumerable<IFdcPumpController>.GetEnumerator()
        {
            return pumpHandlers.GetEnumerator();
        }

        #endregion

        #region IHandler implementation

        public void Init(IContext<byte[], CardMessageBase> context)
        {
            CommIdentity = context.Processor.Communicator.Identity;
            _context = context;
        }

        public string CommIdentity { get; private set; }

        public async Task Process(IContext<byte[], CardMessageBase> context)
        {
            if (context.Incoming.Message != null && context.Incoming.Message.Handle == (byte)Command.VolumeTotalizerResult)
            {
                var volumeTotalResult = context.Incoming.Message as VolumeTotal;

                if (volumeTotalResult != null)
                {
                    byte nozzleNo = volumeTotalResult.VolumeTotalizers.First().NozzleNo;
                    var volumeTotal = volumeTotalResult.VolumeTotalizers.
                        First(n => AssociatedPumpIds.Contains(n.NozzleNo)).VolumeTotal.ToUInt32();
                    var result = new Tuple<int, int>(-1, Convert.ToInt32(volumeTotal));

                    OnTotalizerReceived?.Invoke(this, new TotalizerDataEventArgs(nozzleNo, result));
                }
            }
            else if (context.Incoming.Message != null && context.Incoming.Message.Handle == (byte)Command.ReturnTotalizer)
            {
                var totalizerResult = context.Incoming.Message as Totalizer;

                if (totalizerResult != null)
                {
                    foreach (var item in totalizerResult.TotalizerData)
                    {
                        //var nozzleNo = totalizerResult.TotalizerData.First().NozzleNo;
                        var nozzleNo = item.NozzleNo;
                        //var volumeTotal = totalizerResult.TotalizerData.First(n => AssociatedPumpIds.Contains(n.NozzleNo)).VolumeTotal.ToUInt32();
                        //var amountTotal = totalizerResult.TotalizerData.First(n => AssociatedPumpIds.Contains(n.NozzleNo)).AmountTotal.ToUInt32();
                        var volumeTotal = item.VolumeTotal.ToUInt32();
                        var amountTotal = item.AmountTotal.ToUInt32();
                        var result = new Tuple<int, int>(Convert.ToInt32(amountTotal), Convert.ToInt32(volumeTotal));

                        OnTotalizerReceived?.Invoke(this, new TotalizerDataEventArgs(nozzleNo, result));
                    }
                }
            }
            else if (context.Incoming.Message != null && context.Incoming.Message.Handle == (byte)Command.LockOrUnlockPumpAck)
            {
                var lockUnlockResult = context.Incoming.Message as LockOrUnlockPumpAck;

                if (lockUnlockResult != null)
                {
                    //PumpStateHolder lastState = null;
                    //statusDict.TryGetValue(lockUnlockResult.FPCode.PumpNo, out lastState);

                    //if (LockUnlockOperationType == LockUnlockOperation.Lock)
                    //{
                    //    OnLockUnlockCompleted?.Invoke(this,
                    //       new LockUnlockEventArgs(LockUnlockOperationType, lockUnlockResult.DispenserState == DispenserState.Closed));
                    //}
                    //else if (LockUnlockOperationType == LockUnlockOperation.Unlock)
                    //{
                    //    OnLockUnlockCompleted?.Invoke(this,
                    //        new LockUnlockEventArgs(LockUnlockOperationType, lockUnlockResult.DispenserState == DispenserState.Idle));
                    //}
                }
            }
            else
            {
                if (context.Incoming.Message != null && context.Incoming.Message.Handle == (byte)Command.DataRequest)
                {
                    var request = context.Incoming.Message as DataDownloadRequest;
                    if (request != null && request.DataContentType == DataContentType.FuelPriceList)
                    {
                        InfoLog($"Fuel Price length download request from terminal address: {request.SourceAddress}");

                        OnTerminalFuelPriceDownloadRequested?.Invoke(this, new FuelPriceDownloadRequestedEventArgs(true));
                    }
                }
                else if (context.Incoming.Message != null && context.Incoming.Message.Handle == (byte)Command.Check_cmd)
                {
                    CheckStatus((CheckCmdRequest)context.Incoming.Message);

                    OnCheckCommandReceived?.Invoke(this, new CheckCommandEventArgs((CheckCmdRequest)context.Incoming.Message));
                }

                OnTerminalMessageReceived?.Invoke(this, new TerminalMessageEventArgs(pumpIds, context.Incoming.Message));
            }

            await Task.CompletedTask;
        }

        private void CheckStatus(CheckCmdRequest request)
        {
            if(!statusDict.ContainsKey(request.FuelingPoint.PumpNo))
            {
                var result = statusDict.TryAdd(request.FuelingPoint.PumpNo, 
                    new PumpStateHolder
                    {
                        PumpNo = request.FuelingPoint.PumpNo,
                        NozzleNo = 1,
                        State = request,
                        OperationType = LockUnlockOperation.None
                    });

                logger.Info($"Adding FuelingPoint {request.FuelingPoint.PumpNo} to dict");

                if (!result)
                {
                    statusDict.TryAdd(request.FuelingPoint.PumpNo, null);
                }
            }
            else
            {
                PumpStateHolder stateHolder = null;
                statusDict.TryGetValue(request.FuelingPoint.PumpNo, out stateHolder);

                if (stateHolder != null)
                {
                    logger.Debug($"State holder, PumpNo: {stateHolder.PumpNo}, dispenser state: {stateHolder.State.DispenserState}, " +
                        $"operation: {stateHolder.OperationType}");
                }

                if (stateHolder != null && stateHolder.OperationType != LockUnlockOperation.None)
                {
                    logger.Debug($"PumpNo: {request.FuelingPoint.PumpNo}, Last Dispenser State: {stateHolder.State.DispenserState}, " +
                        $"Current Dispenser State: {request.DispenserState}");

                    if (stateHolder.State.DispenserState == 3 && request.DispenserState == 2)
                    {
                        //Pump is locked due to lock operation
                        if (stateHolder.OperationType != LockUnlockOperation.None)
                        {
                            logger.Info("Locking done!");
                            stateHolder.State = request; //Update the state
                            OnLockUnlockCompleted?.Invoke(this, new LockUnlockEventArgs(stateHolder.OperationType, true));
                        }
                    }
                    else if (stateHolder.State.DispenserState == 2 && request.DispenserState == 3)
                    {
                        //Pump is unlocked due to unlock operation
                        if (stateHolder.OperationType != LockUnlockOperation.None)
                        {
                            logger.Info($"Unlocking done!");
                            stateHolder.State = request; //Update the state
                            OnLockUnlockCompleted?.Invoke(this, new LockUnlockEventArgs(stateHolder.OperationType, true));
                        }
                    }
                }
                else if (stateHolder != null && stateHolder.OperationType == LockUnlockOperation.None)
                {
                    if (stateHolder.State.DispenserState != request.DispenserState)
                    {
                        logger.Warn($"Observed a pump state change, {stateHolder.State.DispenserState} -> {request.DispenserState}");
                        stateHolder.State = request; //Update the state.
                    }
                }
            }
        }

        public void Write(CardMessageBase cardMessage)
        {
            _context.Outgoing.Write(cardMessage);
        }

        public async Task<CardMessageBase> WriteAsync(CardMessageBase request, Func<CardMessageBase, CardMessageBase, bool> responseCapture, 
            int timeout)
        {
            var resp = await _context.Outgoing.WriteAsync(request, responseCapture, timeout);
            return resp;
        }

        #endregion

        #region IEnumerable<IFdcPumpController> implementation

        public IEnumerator<IFdcPumpController> GetEnumerator()
        {
            return pumpHandlers.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return pumpHandlers.GetEnumerator();
        }

        #endregion

        public void PendMessage(CardMessageBase message)
        {
            lock (syncObj)
            {
                queue.Enqueue(message);
            }
        }

        public bool TrySendNextMessage()
        {
            lock (syncObj)
            {
                if (queue.Count > 0)
                {
                    DebugLog($"queue count: {queue.Count}");
                    var message = queue.Dequeue();
                    Write(message);
                    return true;
                }
            }

            return false;
        }

        public void StoreLatestFrameSqNo(int pumpId, byte frameSqNo)
        {
            var pump = GetPump(pumpId);
            if (pump != null)
            {
                pump.FrameSqNo = frameSqNo;
            }
        }

        public void UpdatePumpState(int pumpId, int logicId, LogicalDeviceState state)
        {
            var currentPump = GetPump(pumpId);
            currentPump?.FirePumpStateChange(state, Convert.ToByte(logicId));
        }

        public void UpdateFuelingStatus(int pumpId, FdcTransaction fuelingTransaction)
        {
            var currentPump = GetPump(pumpId);
            currentPump?.FireFuelingStatusChange(fuelingTransaction);
        }

        private HengshanPumpHandler GetPump(int pumpId)
        {
            return pumpHandlers.FirstOrDefault(p => p.PumpId == pumpId);
        }

        public void SetRealPrice(int pumpId,int price)
        {
            var currentPump = GetPump(pumpId);
            var nozzle = currentPump?.Nozzles.FirstOrDefault();
            if (nozzle != null)
                nozzle.RealPriceOnPhysicalPump = price;
        }

        #region Log methods

        private void InfoLog(string info)
        {
            logger.Info("PayTermHdlr " + info);
        }

        private void DebugLog(string debugMsg)
        {
            logger.Debug("PayTermHdlr " + debugMsg);
        }

        #endregion
    }

    public class HengshanPayTerminalHanlderGroupConfigV1
    {
        public string PumpIds { get; set; }

        public List<PumpSubAddress> PumpSubAddresses { get; set; }
    }

    public class HengshanPayTerminalHanlderGroupConfigV2
    {
        public string PumpIds { get; set; }

        public List<PumpSubAddress> PumpSubAddresses { get; set; }

        public List<PumpNozzleLogicId> PumpNozzleLogicIds { get; set; }

        public List<PumpSiteNozzleNo> PumpSiteNozzleNos { get; set; }

        public List<NozzleLogicId> NozzleLogicIds { get; set; }
    }

    public class PumpSubAddress
    {
        public byte PumpId { get; set; }

        public byte SubAddress { get; set; }
    }

    public class PumpNozzleLogicId
    {
        public byte PumpId { get; set; }

        public string LogicIds { get; set; }
    }

    public class PumpSiteNozzleNo
    {
        public byte PumpId { get; set; }

        public string SiteNozzleNos { get; set; }
    }

    public class NozzleLogicId
    {
        public byte NozzleNo { get; set; }

        public byte LogicId { get; set; }
    }
}