using Edge.Core.IndustryStandardInterface.Pump.Fdc;
using Edge.Core.IndustryStandardInterface.Pump;
using HengshanPaymentTerminal;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using HengshanPaymentTerminal.MessageEntity.Outgoing;

namespace HengshanPaymentTerminal
{

    /// <summary>
    /// A neutral pump handler that represents a Hengshan pump regardless of its protocol (Hengshan or TQC).
    /// Actuall this is a fake pump handler since it doesn't have direct control over the pumps.
    /// </summary>
    public class HengshanPumpHandler : IFdcPumpController
    {
        #region Fields

        //The parent of the pump handler.
        private HengshanPayTermHandler terminalHandler;

        private LogicalDeviceState state = LogicalDeviceState.FDC_READY;

        //private SpsManager spsManager;

        private byte frameSqNo;

        private object syncObj = new object();

        private bool totalizerRequested = false;
        private byte totalizerRequestedNozzleLogicId = 0;

        private bool lockPumpRequested = false;

        private bool unlockPumpRequested = false;

        private LogicalNozzle nozzle;
        private List<LogicalNozzle> nozzleList;
        private List<int> siteNozzleNoList;

        #endregion

        #region Logger

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

        #endregion

        #region Constructor

        public HengshanPumpHandler(HengshanPayTermHandler terminalHandler, string name, int pumpId, List<int> nozzles, List<int> siteNozzleNoList)
        {
            this.terminalHandler = terminalHandler;

            this.terminalHandler.OnCheckCommandReceived += TerminalHandler_OnCheckCommandReceived;

            Name = name;
            PumpId = pumpId;
            nozzle = new LogicalNozzle(pumpId, Convert.ToByte(pumpId), 1, null);
            //spsManager = new SpsManager();

            this.siteNozzleNoList = siteNozzleNoList;

            nozzleList = new List<LogicalNozzle>();
            foreach (var nozzleNo in nozzles)
            {
                var logicalNozzle = new LogicalNozzle(PumpId, Convert.ToByte(nozzleNo), Convert.ToByte(nozzleNo), null);
                nozzleList.Add(logicalNozzle);
            }
        }

        #endregion

        #region Properties

        public string Name { get; }

        public int PumpId { get; }

        public int PumpPhysicalId => 1;

        public IEnumerable<LogicalNozzle> Nozzles
        {
            get
            {
                //For Hengshan pumps, there is only one logical nozzle per pump, no real price set.
                return nozzleList;//return new List<LogicalNozzle> { nozzle };
            }
        }

        //Decimal digit settings, China domestic standard
        //小数点后位数设定，中国国内标准，一般为2位

        public int AmountDecimalDigits => 2;

        public int VolumeDecimalDigits => 2;

        public int PriceDecimalDigits => 2;

        public int VolumeTotalizerDecimalDigits => 2;

        public byte FrameSqNo
        {
            get
            {
                lock (syncObj)
                {
                    return frameSqNo;
                }
            }

            set
            {
                lock (syncObj)
                {
                    frameSqNo = value;
                }
            }
        }

        #endregion

        #region Events

        public event EventHandler<FdcPumpControllerOnStateChangeEventArg> OnStateChange;

        public event EventHandler<FdcTransactionDoneEventArg> OnCurrentFuellingStatusChange;

        public event EventHandler<FuelPriceChangeRequestEventArgs> OnFuelPriceChangeRequested;

        #endregion

        #region Event handlers

        private byte GetSiteNozzleNoByLogicId(byte logicId)
        {
            foreach (var nozzleNo in siteNozzleNoList)
            {
                if (terminalHandler.NozzleLogicIdDict[nozzleNo] == logicId)
                    return Convert.ToByte(nozzleNo);
            }

            return Convert.ToByte(PumpId);
        }

        private void TerminalHandler_OnCheckCommandReceived(object sender, CheckCommandEventArgs e)
        {
            if (totalizerRequested
                && e.CheckCommandRequest.SourceAddress == terminalHandler.GetSubAddressForPump(PumpId))
            {
                var request = new ReadVolumeTotal
                {
                    Prefix = 0xFA,
                    SourceAddress = Convert.ToByte(terminalHandler.GetSubAddressForPump(PumpId)),
                    DestinationAddress = Convert.ToByte(terminalHandler.GetSubAddressForPump(PumpId)),
                    FrameSqNoByte = e.CheckCommandRequest.FrameSqNoByte,
                    Handle = (byte)MessageEntity.Command.ReadVolumeTotalizer,
                    NozzleNo = GetSiteNozzleNoByLogicId(totalizerRequestedNozzleLogicId)//Convert.ToByte(PumpId)
                };

                terminalHandler.PendMessage(request);

                Log("Request to terminal to get totalizer, pending!");
            }
            else if ((lockPumpRequested || unlockPumpRequested)
                && e.CheckCommandRequest.SourceAddress == terminalHandler.GetSubAddressForPump(PumpId))
            {
                LockOrUnlockPumpRequest request = new LockOrUnlockPumpRequest
                {
                    Prefix = 0xFA,
                    SourceAddress = Convert.ToByte(terminalHandler.GetSubAddressForPump(PumpId)),
                    DestinationAddress = Convert.ToByte(terminalHandler.GetSubAddressForPump(PumpId)),
                    Handle = Convert.ToByte(MessageEntity.Command.LockOrUnlockPump),
                    FrameSqNoByte = e.CheckCommandRequest.FrameSqNoByte,
                    FPCode = EncodeFPCodeString(PumpId, PumpId),
                    OperationType = lockPumpRequested ? Support.LockUnlockOperation.Lock : Support.LockUnlockOperation.Unlock
                };

                terminalHandler.PendMessage(request);
                var currentPump = terminalHandler.PumpStatusDict.GetValueOrDefault(PumpId);

                if (currentPump == null)
                    Log("Could not get the pump state holder");
                else
                    Log($"Current pump state holder, pump no: {currentPump.PumpNo}");

                currentPump.OperationType = request.OperationType;
                Log($"Current pump state holder, operation type set to: {request.OperationType}");
                //terminalHandler.Write(request);

                if (lockPumpRequested)
                    Log($"PumpNo: {PumpId}, Request to lock pump, pending!");

                if (unlockPumpRequested)
                    Log($"PumpNo: {PumpId}, Request to unlock pump, pending!");
            }
        }
        public void FirePumpStateChange(LogicalDeviceState state, byte nozzeLogicId)
        {
            var logicalNozzle = new LogicalNozzle(PumpId, Convert.ToByte(PumpPhysicalId), nozzeLogicId, null);
            this.state = state;
            if (state == LogicalDeviceState.FDC_READY)
                OnStateChange?.Invoke(this, new FdcPumpControllerOnStateChangeEventArg(LogicalDeviceState.FDC_READY));
            else
                OnStateChange?.Invoke(this, new FdcPumpControllerOnStateChangeEventArg(state, logicalNozzle));
        }

        public void FireNozzleStateChange(LogicalDeviceState? state)
        {
            nozzle = new LogicalNozzle(PumpId, Convert.ToByte(PumpPhysicalId), 1, null);
            if (state.HasValue)
                nozzle.LogicalState = state;
            else
                nozzle.LogicalState = null;

            OnStateChange?.Invoke(this, new FdcPumpControllerOnStateChangeEventArg(LogicalDeviceState.FDC_READY, nozzle));
        }

        //2023-08-22 状态变更
        public void FireFuelingStatusChange(FdcTransaction fuelingTransaction)
        {
            OnCurrentFuellingStatusChange?.Invoke(this, new FdcTransactionDoneEventArg(fuelingTransaction));
        }

        #endregion

        #region Pump interactions

        public LogicalDeviceState QueryStatus()
        {
            var stateHolder = terminalHandler.PumpStatusDict.GetValueOrDefault(PumpId);
            if (stateHolder != null)
            {
                if (stateHolder.State.DispenserState == 3)
                {
                    return LogicalDeviceState.FDC_READY;
                }
                else if (stateHolder.State.DispenserState == 2)
                {
                    return LogicalDeviceState.FDC_LOCKED;
                }
            }

            return LogicalDeviceState.FDC_READY;
        }

        /// <summary>
        /// locks a nozzle 
        /// </summary>
        /// <param name="logicalNozzleId"></param>
        /// <returns></returns>
        public Task<bool> LockNozzleAsync(byte logicalNozzleId)
        {
            var stateHolder = terminalHandler.PumpStatusDict.GetValueOrDefault(PumpId);
            if (stateHolder != null)
            {
                if (stateHolder.State.DispenserState == 2)
                {
                    // If the pump is already locked, reply with success immediately.
                    return Task.FromResult(true);
                }
                else if (stateHolder.State.DispenserState != 3)
                {
                    // Avoid handling locking when pump is fueling or in other states. 
                    return Task.FromResult(false);
                }
            }

            TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
            EventHandler<LockUnlockEventArgs> eventHandler = null;

            eventHandler += (o, e) =>
            {
                Log($"Locking pump, result received, success? {e.Result}");

                lockPumpRequested = false;

                tcs.SetResult(e.Result);

                if (e.Result)
                {
                    FireNozzleStateChange(LogicalDeviceState.FDC_LOCKED);
                }

                terminalHandler.OnLockUnlockCompleted -= eventHandler;
                var currentPump = terminalHandler.PumpStatusDict.GetValueOrDefault(PumpId);
                if (currentPump != null)
                {
                    currentPump.OperationType = Support.LockUnlockOperation.None;
                }
            };

            terminalHandler.OnLockUnlockCompleted += eventHandler;

            lockPumpRequested = true;

            return tcs.Task;
        }

        /// <summary>
        /// unlock a locked nozzle 
        /// </summary>
        /// <param name="logicalNozzleId"></param>
        /// <returns></returns>
        public Task<bool> UnlockNozzleAsync(byte logicalNozzleId)
        {
            var stateHolder = terminalHandler.PumpStatusDict.GetValueOrDefault(PumpId);
            if (stateHolder != null)
            {
                if (stateHolder.State.DispenserState == 3)
                {
                    // Need to do nothing when pump is not locked.
                    return Task.FromResult(true);
                }
            }

            TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
            EventHandler<LockUnlockEventArgs> eventHandler = null;

            eventHandler += (o, e) =>
            {
                Log($"Unlocking pump, result received, success? {e.Result}");

                unlockPumpRequested = false;

                tcs.SetResult(e.Result);

                if (e.Result)
                {
                    FireNozzleStateChange(null);
                }

                terminalHandler.OnLockUnlockCompleted -= eventHandler;
                var currentPump = terminalHandler.PumpStatusDict.GetValueOrDefault(PumpId);
                if (currentPump != null)
                {
                    currentPump.OperationType = Support.LockUnlockOperation.None;
                }
            };

            terminalHandler.OnLockUnlockCompleted += eventHandler;

            unlockPumpRequested = true;

            return tcs.Task;
        }

        #endregion

        #region FDC Server Init

        public void OnFdcServerInit(Dictionary<string, object> parameters)
        {
        }

        public Task<LogicalDeviceState> QueryStatusAsync()
        {
            return Task.FromResult(state);
        }

        //public void QueryTotalizerAsync(int pumpId, byte logicalNozzleId)
        //{
        //    Log($"Query totalizer internally...");

        //    var request = new ReadVolumeTotal
        //    {
        //        Prefix = 0xFA,
        //        SourceAddress = Convert.ToByte(pumpId),
        //        DestinationAddress = Convert.ToByte(pumpId),
        //        FrameSqNoByte = frameSqNo,
        //        Handle = (byte)MessageEntity.Command.ReadVolumeTotalizer,
        //        NozzleNo = logicalNozzleId
        //    };

        //    terminalHandler.Write(request);
        //}

        public Task<Tuple<long, long>> QueryTotalizerAsync(byte logicalNozzleId)
        {
            Log($"LogicalNozzle {logicalNozzleId} querying totalizer...");

            TaskCompletionSource<Tuple<long, long>> tcs = new TaskCompletionSource<Tuple<long, long>>();

            EventHandler<TotalizerDataEventArgs> eventHandler = null;

            eventHandler += (o, e) =>
            {
                Log($"Totalizer data for nozzle: {e.NozzleNo}, money acc: {e.Totalizer.Item1}, volume acc: {e.Totalizer.Item2}");
                totalizerRequested = false; //Reset the value so that no more request will be sent.

                if (e.NozzleNo == GetSiteNozzleNoByLogicId(logicalNozzleId))
                {
                    Log("Totalizer data ready");
                    tcs.SetResult(e.Totalizer);
                    terminalHandler.OnTotalizerReceived -= eventHandler;
                }
                totalizerRequestedNozzleLogicId = 0;
            };

            terminalHandler.OnTotalizerReceived += eventHandler;
            totalizerRequestedNozzleLogicId = logicalNozzleId;
            totalizerRequested = true;

            return tcs.Task;
        }

        public Task<bool> ChangeFuelPriceAsync(int newPriceWithoutDecimalPoint, byte logicalNozzleId)
        {
            logger.Info($"Change price received, Pump {PumpId}, LogicalNozzle {logicalNozzleId}, Price {newPriceWithoutDecimalPoint}");

            //Let the iPOS App handle the fuel price change.
            OnFuelPriceChangeRequested?.Invoke(this, new FuelPriceChangeRequestEventArgs
            {
                NozzleId = logicalNozzleId,
                PumpId = (byte)PumpId,
                Price = newPriceWithoutDecimalPoint
            });

            Log($"application handled fuel price change");

            return Task.FromResult(true);

            //TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();

            //EventHandler<FuelPriceDownloadRequestedEventArgs> eventHandler = null;

            //eventHandler += (o, e) =>
            //{
            //    Log("Terminal requested fuel price");
            //    tcs.SetResult(e.Requested);
            //    terminalHandler.OnTerminalFuelPriceDownloadRequested -= eventHandler;
            //};
            //terminalHandler.OnTerminalFuelPriceDownloadRequested += eventHandler;

            //return tcs.Task;
        }

        public Task<bool> AuthorizeAsync(byte logicalNozzleId)
        {
            return Task.FromResult(false);
        }

        public Task<bool> UnAuthorizeAsync(byte logicalNozzleId)
        {
            return Task.FromResult(false);
        }

        public Task<bool> AuthorizeWithAmountAsync(int moneyAmountWithoutDecimalPoint, byte logicalNozzleId)
        {
            return Task.FromResult(false);
        }

        public Task<bool> AuthorizeWithVolumeAsync(int volumnWithoutDecimalPoint, byte logicalNozzleId)
        {
            return Task.FromResult(false);
        }

        public Task<bool> FuelingRoundUpByAmountAsync(int amount)
        {
            return Task.FromResult(false);
        }

        public Task<bool> FuelingRoundUpByVolumeAsync(int volume)
        {
            return Task.FromResult(false);
        }

        public Task<bool> SuspendFuellingAsync()
        {
            return Task.FromResult(false);
        }

        public Task<bool> ResumeFuellingAsync()
        {
            return Task.FromResult(false);
        }

        #endregion

        private string EncodeFPCodeString(int nozzleId, int pumpId)
        {
            return nozzleId.ToString("X").PadLeft(2, '0') + pumpId.ToString("X").PadLeft(2, '0');
        }

        #region Log

        private void Log(string message)
        {
            logger.Info($"Pump Handler: {PumpId}, {message}");
        }

        #endregion
    }
}
