using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;

using System.Threading;
using System.Runtime.InteropServices;
using System.Net;
using System.Net.Sockets;

using Wayne.FDCPOSLibrary;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.DependencyInjection;

namespace Wayne.FDCPOSInterface
{
    public class FuellingInfo
    {
        public DateTime dateTime;
        public int pumpNumber;
        public int nozzle;
        public int productNo;
        public string productName;
        public string productUM;
        public decimal amount;
        public decimal volume;
        public decimal price;
        public int transactionId;

        public FuellingInfo(int pumpNumber, int nozzle, int productNo, string productName, string productUM, decimal amount, decimal volume, decimal price, DateTime dateTime, int transactionId)
        {
            this.pumpNumber = pumpNumber;
            this.nozzle = nozzle;
            this.productNo = productNo;
            this.productName = productName;
            this.productUM = productUM;
            this.amount = amount;
            this.volume = volume;
            this.price = price;
            this.dateTime = dateTime;
            this.transactionId = transactionId;
        }
    }


    public class FDCPOSClientIX : FDCPOSClient
    {
        public string currencySymbol;
        public bool currencySymbolAfterValue = true;
        public bool spaceBetweenCurrencySymbolAndValue = true;
        public int controllerCrc = 0;
        public int printerWidth = 24;

        public FDCPOSClientIX(FDCPOSManager _fdcposManager, Messages.HeartbeatCallback _heartbeatCallback, string posInfo) :
            base(_fdcposManager, _heartbeatCallback, null)
        {
            currencySymbol = System.Globalization.RegionInfo.CurrentRegion.CurrencySymbol;
            readPOSInfo(posInfo);
        }

        void readPOSInfo(string posInfo)
        {
            try
            {
                posInfo = posInfo.Substring(posInfo.IndexOf('(', 0) + 1, posInfo.Length - 1 - posInfo.IndexOf('(', 0) - 1);
                //FDCPOSManager.tracer.WriteLine(string.Format("posInfo={0}", posInfo));
                Dictionary<string, string> posInfoParamDict;
                posInfoParamDict = ParsePOSInfoString(posInfo);
                string key = "currencySymbol";
                if (posInfoParamDict.ContainsKey(key) && posInfoParamDict[key] != null && posInfoParamDict[key] != "")
                    this.currencySymbol = posInfoParamDict[key];
                key = "currencySymbolAfterValue";
                if (posInfoParamDict.ContainsKey(key) && posInfoParamDict[key] != null && posInfoParamDict[key] != "")
                {
                    if (posInfoParamDict[key] == "0" || posInfoParamDict[key].ToUpper() == "FALSE")
                        this.currencySymbolAfterValue = false;
                }
                key = "spaceBetweenCurrencySymbolAndValue";
                if (posInfoParamDict.ContainsKey(key) && posInfoParamDict[key] != null && posInfoParamDict[key] != "")
                {
                    if (posInfoParamDict[key] == "0" || posInfoParamDict[key].ToUpper() == "FALSE")
                        this.spaceBetweenCurrencySymbolAndValue = false;
                }
                key = "controllerCrc";
                if (posInfoParamDict.ContainsKey(key) && posInfoParamDict[key] != null && posInfoParamDict[key] != "")
                {
                    try
                    {
                        this.controllerCrc = Convert.ToInt32(posInfoParamDict[key]);
                    }
                    catch (Exception ex)
                    {
                    }
                }
                key = "printerWidth";
                if (posInfoParamDict.ContainsKey(key) && posInfoParamDict[key] != null && posInfoParamDict[key] != "")
                {
                    try
                    {
                        this.printerWidth = Convert.ToInt32(posInfoParamDict[key]);
                    }
                    catch (Exception ex)
                    {
                    }
                }
            }
            catch (Exception ex)
            {
                //FDCPOSManager.tracer.WriteLine("Exception! " + ex.Message);
            }
        }

        public override byte[] getDSPFields(FuellingInfo fi)
        {
            byte[] temp;
            string sDSPFilelds = GenerateSignedPumpInfoLine(fi.dateTime, fi.pumpNumber, fi.productName, fi.amount, fi.volume, fi.price,
                currencySymbol, currencySymbolAfterValue, spaceBetweenCurrencySymbolAndValue, fi.productUM, controllerCrc, printerWidth);
            temp = System.Text.UTF8Encoding.UTF8.GetBytes(sDSPFilelds);
            return temp;
        }

        public override byte[] getCRC(FuellingInfo fi)
        {
            byte[] temp;
            string sCRC = "";
            temp = System.Text.ASCIIEncoding.ASCII.GetBytes(sCRC);
            return temp;
        }

        public static string GenerateSignedPumpInfoLine(
                    DateTime dateTime,
                    int pumpNumber,
                    string productName,
                    decimal totalAmount,
                    decimal volume,
                    decimal price,
                    string currencySymbol,
                    bool currencySymbolAfterValue,
                    bool spaceBetweenCurrencySymbolAndValue,
                    string volumeUnit,
                    int controllerCrc,
                    int printerWidth)
        {
            SignedFuellingInfo signedFillingInfo = new SignedFuellingInfo();
            SignedFuellingInfo.Data data = new SignedFuellingInfo.Data();
            data.ControllerModuleCrc = controllerCrc;
            data.CurrencySymbol = currencySymbol;
            data.CurrencySymbolAfterValue = currencySymbolAfterValue;
            data.DateTime = dateTime;
            data.Price = price;
            data.PrinterWidth = printerWidth;
            data.ProductName = productName;
            data.PumpNumber = pumpNumber;
            data.SpaceBetweenCurrencySymbolAndValue = spaceBetweenCurrencySymbolAndValue;
            data.TotalAmount = totalAmount;
            data.Volume = volume;
            data.VolumeUnit = volumeUnit;
            return signedFillingInfo.EncodeReceiptText(data);
        }
    }

    public class FDCPOSClient : IDisposable
    {
        //static NLog.Logger fdcSocketLogger = NLog.LogManager.LoadConfiguration("nlog.config").GetLogger("FdcServerSocket");
        private ILogger fdcSocketLogger;// = ServiceBuilder.Provider
                                        //.GetRequiredService<ILoggerFactory>().CreateLogger("FdcServerSocket");
        public string sID;
        public string workstationID;
        public string applicationSender;

        public FdcClientTcpHandler FdcClientTcpHandler;
        public string sIPAddress;

        private TcpClient _socketChannelResponse = null;
        [ComVisible(false)]
        public TcpClient socketChannelResponse
        {
            get { return _socketChannelResponse; }
            set { _socketChannelResponse = value; }
        }

        private bool _connected = false;
        [ComVisible(false)]
        public bool connected
        {
            get { return _connected; }
            set { _connected = value; }
        }

        private bool _logon = false;
        [ComVisible(false)]
        public bool logon
        {
            get { return _logon; }
            set { _logon = value; }
        }

        private TcpClient _socketChannelUnsolicited = null;
        [ComVisible(false)]
        public TcpClient socketChannelUnsolicited
        {
            get { return _socketChannelUnsolicited; }
            set { _socketChannelUnsolicited = value; }
        }

        FDCPOSManager fdcposManager;
        public Heartbeat heartbeat;
        Messages.HeartbeatCallback heartbeatCallback;


        static public POSType GetType(string posInfo)
        {
            if (posInfo.Contains(POSType.IXTerminal.ToString()))
                return POSType.IXTerminal;
            else
                return POSType.Generic;
        }

        public static Dictionary<string, string> ParsePOSInfoString(string posInfoString)
        {
            Dictionary<string, string> posInfoStringParamDict = new Dictionary<string, string>();
            string[] listItems = posInfoString.Split(';');
            foreach (string listItem in listItems)
            {
                string trimmedListItem = listItem.Trim();
                if (trimmedListItem.Length > 0)
                {
                    int equalSignIndex = trimmedListItem.IndexOf('=');
                    string paramName = trimmedListItem.Substring(0, equalSignIndex);
                    string paramValue = trimmedListItem.Substring(equalSignIndex + 1, trimmedListItem.Length - equalSignIndex - 1);
                    posInfoStringParamDict[paramName] = paramValue;
                }
            }
            return posInfoStringParamDict;
        }

        public FDCPOSClient(FDCPOSManager _fdcposManager,
            Messages.HeartbeatCallback _heartbeatCallback, ILogger logger)
        {
            this.fdcSocketLogger = logger;
            heartbeat = new Heartbeat();
            fdcposManager = _fdcposManager;
            heartbeat.OnHeartbeatInterval += new EventHandler(heartbeat_OnHeartbeatInterval);
            heartbeat.OnClientHeartbeatTimeout += new EventHandler(heartbeat_OnHeartbeatTimeout);
            heartbeatCallback = _heartbeatCallback;
        }

        ~FDCPOSClient()
        {
            if (heartbeat != null)
                heartbeat.Dispose();
            heartbeat = null;
        }

        public void Dispose()
        {
            try
            {
                if (this.fdcSocketLogger != null)
                    this.fdcSocketLogger.LogInformation("FDCPOSClient: " + this.workstationID + "-" + this.applicationSender + " is on disposing");
                if (heartbeat != null)
                    heartbeat.Dispose();
                heartbeat = null;
                this.FdcClientTcpHandler.Dispose();
            }
            catch { }
        }

        private void heartbeat_OnHeartbeatTimeout(object sender, EventArgs e)
        {
            try
            {
                string clientId = FDCPOSClient.getClientID(workstationID, applicationSender);
                //FDCPOSManager.tracer.WriteLine(string.Format("disconnect client '{0}'", clientId));
                if (this.fdcSocketLogger != null)
                    this.fdcSocketLogger.LogInformation(string.Format("disconnecting client '{0}' due to OnHeartbeatTimeout", clientId));

                this.fdcposManager.DisconnectClient(this);
            }
            catch (Exception ex)
            {
                //FDCPOSManager.tracer.WriteLine(string.Format("Exception disconnect client '{0}', error: {1}", clientId, ex.Message));
            }
        }

        private void heartbeat_OnHeartbeatInterval(object sender, EventArgs e)
        {
            if (heartbeatCallback != null)
                heartbeatCallback.Invoke(workstationID, applicationSender);
        }

        public static string getClientID(string workstationID, string applicationSender)
        {
            return (workstationID ?? "") + "ยงยงยง" + (applicationSender ?? "");
        }

        public static void Remove(string clientID)
        {

        }

        public virtual byte[] getDSPFields(FuellingInfo fi)
        {
            byte[] temp;
            string sDSPFields = "";
            temp = System.Text.UTF8Encoding.UTF8.GetBytes(sDSPFields);
            return temp;
        }
        public virtual byte[] getCRC(FuellingInfo fi)
        {
            return getCRC("DeviceID TransactionNo Volume Amount ", fi.pumpNumber, fi.nozzle, fi.transactionId, fi.volume, fi.amount);
        }
        private byte[] getCRC(string fieldsNames, int deviceId, int nozzle, int transactionId, decimal volume, decimal amount)
        {
            try
            {
                //FDCPOSManager.tracer.WriteLineIf(//FDCPOSManager.tracer.CheckTraceLevel(4), "init");
                Crc16Ccitt crccrypter = new Crc16Ccitt(InitialCrcValue.Zeros);
                byte[] temp; // = new byte[8];
                byte[] data = new byte[fieldsNames.Length + 1 + 2 + 5 + 5 + 3];

                // add fields names
                System.Text.ASCIIEncoding.ASCII.GetBytes(fieldsNames).CopyTo(data, 0);

                //FDCPOSManager.tracer.WriteLineIf(//FDCPOSManager.tracer.CheckTraceLevel(4), "1");

                // add deviceId
                data[fieldsNames.Length] = Convert.ToByte(deviceId);

                //FDCPOSManager.tracer.WriteLineIf(//FDCPOSManager.tracer.CheckTraceLevel(4), "2");

                // add transactionId - bcd4
                string sTrId = transactionId.ToString();
                sTrId = sTrId.PadLeft(4, '0');
                temp = System.Text.ASCIIEncoding.ASCII.GetBytes(sTrId);
                data[fieldsNames.Length + 1] = (byte)((temp[0] - 0x30) * 16 + (temp[1] - 0x30));
                data[fieldsNames.Length + 2] = (byte)((temp[2] - 0x30) * 16 + (temp[3] - 0x30));
                //FDCPOSManager.tracer.WriteLineIf(//FDCPOSManager.tracer.CheckTraceLevel(4), "3");
                // add volume - bin8+bcd8
                string stemp = FDCConvert.ToString(volume);
                int decpos = stemp.IndexOf(FDCConvert.DecimalSeparator);
                if (decpos != -1)
                    stemp = stemp.Replace(FDCConvert.DecimalSeparator, "");
                else
                    decpos = stemp.Length;
                //for (int i = 0; i < 8; i++)
                //    temp[i] = 0;
                temp = System.Text.ASCIIEncoding.ASCII.GetBytes(stemp);
                // bin8 is the decoimal position
                data[fieldsNames.Length + 3] = Convert.ToByte(decpos);
                for (int i = 0; i < 5; i++)
                {
                    if (temp.Length > 2 * i + 1)
                        data[fieldsNames.Length + 4 + i] = (byte)((temp[2 * i] - 0x30) * 16 + (temp[2 * i + 1] - 0x30));
                    else if (temp.Length > 2 * i)
                        data[fieldsNames.Length + 4 + i] = (byte)((temp[2 * i] - 0x30) * 16);
                    else
                        data[fieldsNames.Length + 4 + i] = 0;
                }
                ////FDCPOSManager.tracer.WriteLineIf(//FDCPOSManager.tracer.CheckTraceLevel(4), "4");

                // add amount - bin8+bcd8
                stemp = FDCConvert.ToString(amount);
                decpos = stemp.IndexOf(FDCConvert.DecimalSeparator);
                if (decpos != -1)
                    stemp = stemp.Replace(FDCConvert.DecimalSeparator, "");
                else
                    decpos = stemp.Length;
                //for (int i = 0; i < 8; i++ )
                //    temp[i] = 0;
                temp = System.Text.ASCIIEncoding.ASCII.GetBytes(stemp);
                // bin8 is the decoimal position
                data[fieldsNames.Length + 8] = Convert.ToByte(decpos);
                for (int i = 0; i < 5; i++)
                {
                    if (temp.Length > 2 * i + 1)
                        data[fieldsNames.Length + 9 + i] = (byte)((temp[2 * i] - 0x30) * 16 + (temp[2 * i + 1] - 0x30));
                    else if (temp.Length > 2 * i)
                        data[fieldsNames.Length + 9 + i] = (byte)((temp[2 * i] - 0x30) * 16);
                    else
                        data[fieldsNames.Length + 9 + i] = 0;
                }
                //FDCPOSManager.tracer.WriteLineIf(//FDCPOSManager.tracer.CheckTraceLevel(4), "5");
                ushort checksum = crccrypter.ComputeChecksum(data);
                data[fieldsNames.Length + 13] = 0;
                data[fieldsNames.Length + 14] = (byte)((checksum & 0xFF00) / 256);
                data[fieldsNames.Length + 15] = (byte)(checksum & 0x00FF);
                //FDCPOSManager.tracer.WriteLineIf(//FDCPOSManager.tracer.CheckTraceLevel(4), "end ok");
                return data;
            }
            catch (Exception ex)
            {
                //FDCPOSManager.tracer.WriteLine("Exception! " + ex.Message + " - " + ex.StackTrace);
            }
            //FDCPOSManager.tracer.WriteLineIf(//FDCPOSManager.tracer.CheckTraceLevel(4), "end ko");
            return null;
        }

    }
}