using Edge.Core.Processor;
using Edge.Core.IndustryStandardInterface.Pump;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using Wayne_Pump_Dart.MessageEntity;
using Wayne_Pump_Dart.MessageEntity.Outgoing;
using Edge.Core.Processor.Dispatcher.Attributes;
using Edge.Core.Processor.Communicator;
using System.Xml.Serialization;
using System.IO;

namespace Wayne_Pump_Dart
{
    [MetaPartsRequired(typeof(HalfDuplexActivePollingDeviceProcessor<,>))]
    [MetaPartsRequired(typeof(ComPortCommunicator<>))]
    [MetaPartsRequired(typeof(TcpClientCommunicator<>))]
    [MetaPartsRequired(typeof(TcpServerCommunicator<>))]
    [MetaPartsDescriptor(
        "lang-zh-cn:稳Dart加油机lang-en-us:WayneDartPump",
        "lang-zh-cn:用于驱动 Wayne Dart 协议的加油机lang-en-us:Used for driven Wayne Pump which use Dart Protocol",
        new[] { "lang-zh-cn:加油机lang-en-us:Pump" })]
    public class PumpGroupHandler : TestableActivePollingDeviceHandler<byte[], MessageBase>, IEnumerable<IFdcPumpController>, IDisposable
    {
        //protected static ILog logger = log4net.LogManager.GetLogger("PumpHandler");
        static NLog.Logger logger = NLog.LogManager.LoadConfiguration("nlog.config").GetLogger("PumpHandler");

        protected IContext<byte[], MessageBase> context;
        protected int pollingInterval;
        protected List<PumpHandler> pumpHandlers = new List<PumpHandler>();

        /// <summary>
        /// if the underlying communicator connected to remote device.
        /// </summary>
        private bool isCommConnected = false;

        private int amountDecimalDigits;
        private int volumeDecimalDigits;
        private int priceDecimalDigits;
        private int volumeTotalizerDecimalDigits;

        #region Ctor parameters

        public class PumpGroupConfiguration
        {
            public byte AmountDecimalDigits { get; set; }
            public byte VolumeDecimalDigits { get; set; }
            public byte PriceDecimalDigits { get; set; }
            public byte VolumeTotalizerDecimalDigits { get; set; }

            public List<PumpConfiguration> PumpConfigurations { get; set; }
        }

        public class PumpConfiguration
        {
            public byte PumpId { get; set; }
            public byte PhysicalId { get; set; }

            public List<NozzleConfiguration> NozzleConfigurations { get; set; }
        }

        public class NozzleConfiguration
        {
            public byte LogicalId { get; set; }
            public byte PhysicalId { get; set; }
            public int DefaultNoDecimalPointPriceIfNoHistoryPriceReadFromDb { get; set; }
        }

        #endregion

        [ParamsJsonSchemas("PumpGroupHandlerCtorParamsJsonSchemas")]
        public PumpGroupHandler(PumpGroupConfiguration pumpGroupConfiguration, IServiceProvider services)
        {
            this.amountDecimalDigits = pumpGroupConfiguration.AmountDecimalDigits;
            this.volumeDecimalDigits = pumpGroupConfiguration.VolumeDecimalDigits;
            this.priceDecimalDigits = pumpGroupConfiguration.PriceDecimalDigits;
            this.volumeTotalizerDecimalDigits = pumpGroupConfiguration.VolumeTotalizerDecimalDigits;
            logger.Info("Wayne Dart pump group, Will create " + pumpGroupConfiguration.PumpConfigurations.Count + " pump handlers for this Wayne Dart Group from config");
            this.CreatePumpHandlers(pumpGroupConfiguration.PumpConfigurations.Select(pc =>
            {
                XmlDocument doc = new XmlDocument();
                var xml =
                "<Pump pumpId='" + pc.PumpId + "' physicalId='" + pc.PhysicalId + "'>" +
                    "<Nozzles>" +
                pc.NozzleConfigurations.Select(nc =>
                        "<Nozzle logicalId='" + nc.LogicalId + "' physicalId='" + nc.PhysicalId +
                            "' defaultNoDecimalPointPriceIfNoHistoryPriceReadFromDb='" +
                                nc.DefaultNoDecimalPointPriceIfNoHistoryPriceReadFromDb + "' />").Aggregate((acc, n) => acc + n) +
                    "</Nozzles>" +
                "</Pump>";
                doc.LoadXml(xml);
                return doc.FirstChild;
            }));
        }

        //[MetaApiParamsJsonSchemaDescriptor("PumpGroupHandlerCtorSchema")]
        public PumpGroupHandler(int amountDecimalDigits, int volumeDecimalDigits,
            int priceDecimalDigits, int volumeTotalizerDecimalDigits,
            string pumpGroupXmlConfiguration)
        {
            this.amountDecimalDigits = amountDecimalDigits;
            this.volumeDecimalDigits = volumeDecimalDigits;
            this.priceDecimalDigits = priceDecimalDigits;
            this.volumeTotalizerDecimalDigits = volumeTotalizerDecimalDigits;

            //sample of pumpGroupXmlConfiguration, the address is a config value in physical wayne dart 
            //pump mother board, 1 - 32 is the acceptable values.
            //the reason introduce PumpGroupHandler, because wayne dart pump may run multiple pumps in single
            //rs485 com port.
            //<PumpGroup>
            //	<Pump pumpId='1' physicalId='1'>
            //		<Nozzles>
            //			<Nozzle logicalId='1' physicalId='1' />
            //			<Nozzle logicalId='2' physicalId='2' />
            //          <Nozzle logicalId='3' physicalId='3' />
            //		</Nozzles>
            //	</Pump>
            //	<Pump pumpId='2' physicalId='2'>
            //		<Nozzles>
            //			<Nozzle logicalId='1' physicalId='1' />
            //			<Nozzle logicalId='2' physicalId='2' />
            //		</Nozzles>
            //	</Pump>
            //</PumpGroup>
            XmlDocument xmlDocument = new XmlDocument();
            xmlDocument.LoadXml(pumpGroupXmlConfiguration);
            //var rootElement = xmlDocument.GetElementsByTagName("PumpGroup").Cast<XmlElement>().First();

            var pumpElements = xmlDocument.GetElementsByTagName("Pump").Cast<XmlNode>();
            logger.Info("Wayne Dart pump group, Will create " + pumpElements.Count() + " pump handlers for this Wayne Dart Group from local config");
            this.CreatePumpHandlers(pumpElements);
        }

        protected virtual void CreatePumpHandlers(IEnumerable<XmlNode> pumpElements)
        {
            foreach (XmlNode pumpElement in pumpElements)
            {
                var pumpId =
                    int.Parse(pumpElement.Attributes["pumpId"].Value);
                var pumpHandler = new PumpHandler(this, pumpId,
                    amountDecimalDigits, volumeDecimalDigits,
                    priceDecimalDigits, volumeTotalizerDecimalDigits,
                    pumpElement.OuterXml);
                this.pumpHandlers.Add(pumpHandler);
            }

            this.rotateMsgTokens.AddRange(Enumerable.Repeat(0, this.pumpHandlers.Select(s => s.PumpId).Max()));
        }

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

        /// <summary>
        /// key:value = pumpId:MsgToken
        /// </summary>
        private List<int> rotateMsgTokens = new List<int>();

        /// <summary>
        /// get a valid token which from >=1 and lessOrEqual F.
        /// NOTE, if ResetMessageTokenToAlign(pumpId) was called, the next token generated here will be 0, which is
        /// a special value used to align the comm with real pump side, and then if the comm eastablished, we
        /// should use 1 to F.
        /// </summary>
        /// <param name="pumpId"></param>
        /// <returns></returns>
        public byte GetNewMessageToken(int pumpId)
        {
            lock (rotateMsgTokens)
            {
                //return 0;
                if (rotateMsgTokens[pumpId - 1] > 0x0E)
                    this.rotateMsgTokens[pumpId - 1] = 0;
                //var returnValue = this.rotateMsgTokens[pumpId - 1];
                //this.rotateMsgTokens[pumpId - 1] += 1;
                return (byte)(++this.rotateMsgTokens[pumpId - 1]);
            }
        }

        /// <summary>
        /// typically called after a data request was NAKed by pump side, 0 token used for 
        /// re-align the comm from FC to real pump.
        /// </summary>
        /// <param name="pumpId"></param>
        public void ResetMessageTokenToAlign(int pumpId)
        {
            lock (rotateMsgTokens)
                this.rotateMsgTokens[pumpId - 1] = -1;
        }

        public override void Init(IContext<byte[], MessageBase> context)
        {
            base.Init(context);
            this.context = context;
            this.context.Communicator.OnConnected += (object sender, EventArgs e) => { this.isCommConnected = true; };
            this.context.Communicator.OnDisconnected += (object sender, EventArgs e) => { this.isCommConnected = false; };
            this.pumpHandlers.ForEach(p => p.Init(this.context));
            var timeWindowWithActivePollingOutgoing =
                this.context.Outgoing as TimeWindowWithActivePollingOutgoing<byte[], MessageBase>;
            this.pollingInterval = timeWindowWithActivePollingOutgoing.PollingInterval;
            int previousPolledHandlerIndex = 0;
            timeWindowWithActivePollingOutgoing.PollingMsgProducer =
                () =>
                {
                    try
                    {
                        //if (!this.isCommConnected) return null;
                        if (this.pumpHandlers.Count <= previousPolledHandlerIndex)
                            previousPolledHandlerIndex = 0;
                        var target = this.pumpHandlers[previousPolledHandlerIndex++];
                        return new Poll((byte)(target.PumpPhysicalId), 0);// this.GetNewMessageToken(target.PumpId));
                    }
                    catch (Exception exxx)
                    {
                        logger.Error("Exceptioned in WayneDartPumpGroupHandler(previousPolledHandlerIndex: " + previousPolledHandlerIndex + "): " + exxx);
                        return null;
                    }
                };
        }

        public override Task Process(IContext<byte[], MessageBase> context)
        {
            this.context = context;
            var ph = this.pumpHandlers.FirstOrDefault(p => (byte)(p.PumpPhysicalId) == context.Incoming.Message.Adrs);
            if (ph == null)
            {
                logger.Error("PumpGroupHandler does not contain pumpHandler with physcialId: "
                    + context.Incoming.Message.Adrs.ToString("X2"));
                return Task.CompletedTask;
            }

            return ph.Process(context);
            //return Task.CompletedTask;
        }

        public void Dispose()
        {
            this.pumpHandlers.ForEach(ph => ph.Dispose());
        }
    }
}