using System;
using System.Globalization;
using System.Text;
using System.IO;


namespace Wayne.FDCPOSInterface
{
    public class SignedFuellingInfo
    {
        /// <summary>
        /// A class that calculates a CRC32 value from various sources.
        /// </summary>
        public class Crc32
        {
            #region Fields

            private UInt32[] table;
            private UInt32 initialValue;
            private UInt32 crc;
            private UInt32 length;
            private byte[] tempBuffer;

            #endregion

            #region Construction

            /// <summary>
            /// Constructor.
            /// Initializes the CRC to 0xFFFFFFFF.
            /// </summary>
            public Crc32()
                : this(0xffffffff)
            {
            }

            /// <summary>
            /// Constructor.
            /// </summary>
            /// <param name="initialValue">The initial value of the CRC.</param>
            public Crc32(UInt32 initialValue)
            {
                this.initialValue = initialValue;
                UInt32 poly = 0xedb88320;
                table = new UInt32[256];
                UInt32 temp = 0;
                for (UInt32 i = 0; i < table.Length; i++)
                {
                    temp = i;
                    for (UInt32 j = 8; j > 0; j--)
                    {
                        if ((temp & 1) == 1)
                            temp = (UInt32)((temp >> 1) ^ poly);
                        else
                            temp >>= 1;
                    }
                    table[i] = temp;
                }
                Reset();
            }

            #endregion

            #region Properties

            /// <summary>
            /// The CRC32 value.
            /// </summary>
            public UInt32 Value
            {
                get { return ~crc; }
            }

            /// <summary>
            /// The total length of the data.
            /// </summary>
            public UInt32 Length
            {
                get { return length; }
            }

            #endregion

            #region Methods

            /// <summary>
            /// Resets the CRC32 value.
            /// </summary>
            public void Reset()
            {
                crc = initialValue;
                length = 0;
            }

            /// <summary>
            /// Appends a byte array to the CRC32 value.
            /// </summary>
            /// <param name="bytes">The bytes to append.</param>
            public void Append(byte[] bytes)
            {
                Append(bytes, 0, bytes.Length);
            }

            /// <summary>
            /// Appends a byte array to the CRC32 value.
            /// </summary>
            /// <param name="bytes">The bytes to append.</param>
            /// <param name="offset">The offset of the array of where to start.</param>
            /// <param name="length">The number of bytes to read from the array.</param>
            public void Append(byte[] bytes, int offset, int length)
            {
                this.length += (UInt32)length;
                for (int i = offset; i < length; i++)
                {
                    byte index = (byte)(((crc) & 0xff) ^ bytes[i]);
                    crc = (UInt32)((crc >> 8) ^ table[index]);
                }
            }

            /// <summary>
            /// Appends a text string to the CRC32 value.
            /// </summary>
            /// <param name="text">The text to append.</param>
            public void Append(string text)
            {
                if (!string.IsNullOrEmpty(text))
                    Append(Encoding.Unicode.GetBytes(text));
            }

            /// <summary>
            /// Appends the content of a file to the CRC32 value.
            /// </summary>
            /// <param name="filename">The filename of the file to append.</param>
            public void AppendFile(string filename)
            {
                AppendFile(filename, 16384);
            }

            /// <summary>
            /// Appends the content of a file to the CRC32 value.
            /// </summary>
            /// <param name="filename">The filename of the file to append.</param>
            /// <param name="bufferSize">The size of the read-buffer to use.</param>
            public void AppendFile(string filename, int bufferSize)
            {
                try
                {
                    if ((tempBuffer == null) || (bufferSize != tempBuffer.Length))
                        tempBuffer = new byte[bufferSize];

                    using (FileStream fileStream = new FileStream(filename, FileMode.Open, FileAccess.Read))
                    {
                        using (BinaryReader binaryReader = new BinaryReader(fileStream))
                        {
                            int usedBufferSize;
                            do
                            {
                                usedBufferSize = binaryReader.Read(tempBuffer, 0, bufferSize);
                                if (usedBufferSize > 0)
                                    Append(tempBuffer, 0, usedBufferSize);
                            }
                            while (usedBufferSize == bufferSize);
                        }
                    }
                }
                catch { }
            }

            #endregion
        }

        public class Data
        {
            public DateTime DateTime { get; set; }
            public int PumpNumber { get; set; }
            public string ProductName { get; set; }
            public decimal TotalAmount { get; set; }
            public decimal Volume { get; set; }
            public decimal Price { get; set; }
            public string CurrencySymbol { get; set; }
            public bool CurrencySymbolAfterValue { get; set; }
            public bool SpaceBetweenCurrencySymbolAndValue { get; set; }
            public string VolumeUnit { get; set; }
            public int ControllerModuleCrc { get; set; }
            public int PrinterWidth { get; set; }
        }

        private const string Iv = "@BEPQRV]";

        private readonly Crc32 crc32 = new Crc32((uint)(
            ((Iv[6] - Iv[0]) * (Iv[7] - Iv[0]) + (Iv[2] - Iv[0])) * 1024 * 1024 +
            ((Iv[3] - Iv[0]) * (Iv[5] - Iv[0]) + (Iv[1] - Iv[0])) * 1024 +
            ((Iv[6] - Iv[0]) * (Iv[7] - Iv[0]) + (Iv[4] - Iv[0]))));

        private const string SignChar = "^";
        private const string BadSignReplaceChar = " ";

        private const string SignError = "\r\n^*PUMP INFO SIGN ERROR*^\r\n";

        public string EncodeReceiptText(Data data)
        {
            StringBuilder result = new StringBuilder();
            NumberFormatInfo numberFormatInfo = new NumberFormatInfo
            {
                NumberDecimalDigits = 2,
                NumberDecimalSeparator = ","
            };

            if (data.PrinterWidth < 30)
            {
                // Design for 24 chars
                // ===================

                string preText = data.PrinterWidth > 24 ? new string(' ', data.PrinterWidth - 24) : string.Empty;

                //^ #12 [20100503085824] ^45697A3C
                result.Append(preText + GetSignedString(string.Concat(SignChar, " #", data.PumpNumber.ToString().PadRight(3),
                    "[", data.DateTime.ToString("yyyyMMddHHmmss"), "] ", SignChar)));

                //^ Blyfri 95            ^2C5BD546
                result.Append(preText + GetSignedString(string.Concat(SignChar, " ", data.ProductName.PadRight(21), SignChar)));

                //^           563,74 DKK ^65B9F8A3
                result.Append(preText + GetSignedString(string.Concat(SignChar, GetCurrencyString(numberFormatInfo, data).PadLeft(21), " ", SignChar)));

                //^     54,52 L          ^98C35479
                result.Append(preText + GetSignedString(string.Concat(SignChar, data.Volume.ToString(numberFormatInfo).PadLeft(10), " ", data.VolumeUnit.PadRight(11), SignChar)));

                //^   @ 10,34 DKK/L      ^465D9872
                result.Append(preText + GetSignedString(string.Concat(SignChar, string.Concat("@ ", data.Price.ToString(numberFormatInfo)).PadLeft(10), " ", string.Concat(data.CurrencySymbol, "/", data.VolumeUnit).PadRight(11), SignChar)));

                //^ CS A8F0EC90:######## ^6A9C8FC4)
                result.Append(preText + GetSignedString(string.Concat(SignChar, " CS ", data.ControllerModuleCrc.ToString("X8"), " ######## ", SignChar)));
            }
            else
            {
                // Design for 30 chars
                // ===================

                string preText = data.PrinterWidth > 30 ? new string(' ', data.PrinterWidth - 30) : string.Empty;

                //123456789012345678901234567890
                //^ #12 [20100503085824]       ^4A7692AE
                result.Append(preText + GetSignedString(string.Concat(SignChar, " #", data.PumpNumber.ToString().PadRight(3),
                    "[", data.DateTime.ToString("yyyyMMddHHmmss"), "]       ", SignChar)));

                //123456789012345678901234567890
                //^ Unlead 95       563,74 SEK ^3CE18720
                result.Append(preText + GetSignedString(string.Concat(SignChar, " ", data.ProductName.PadRight(14), GetCurrencyString(numberFormatInfo, data).PadLeft(12), " ", SignChar)));

                //123456789012345678901234567890
                //^ 54,52 L @ 10,34 SEK/L      ^0A1B0FB2
                result.Append(preText + GetSignedString(string.Concat(SignChar, " ", string.Concat(data.Volume.ToString(numberFormatInfo), " ", data.VolumeUnit,
                    " @ ", data.Price.ToString(numberFormatInfo), " ", data.CurrencySymbol, "/", data.VolumeUnit).PadRight(27), SignChar)));

                //123456789012345678901234567890
                //^ C/S A8F0EC90:########      ^05198FB7
                result.Append(preText + GetSignedString(string.Concat(SignChar, " C/S ", data.ControllerModuleCrc.ToString("X8"), " ########      ", SignChar)));
            }

            return result.ToString();
        }

        public string DecodeReceiptText(string text, int printerModuleCrc)
        {
            bool hasError = false;
            int offset = 0;
            int startCharIndex, endCharIndex;
            do
            {
                startCharIndex = text.IndexOf(SignChar, offset);
                if (startCharIndex > -1)
                {
                    endCharIndex = text.IndexOf(SignChar, startCharIndex + 1);
                    if (endCharIndex == -1)
                    {
                        text = text.Replace(SignChar, BadSignReplaceChar);
                        hasError = true;
                    }
                    else
                    {
                        if (text.Length <= endCharIndex + 8)
                        {
                            text = text.Remove(startCharIndex, 1).Insert(startCharIndex, BadSignReplaceChar).Remove(endCharIndex, 1).Insert(endCharIndex, BadSignReplaceChar);
                            hasError = true;
                        }
                        else
                        {
                            string crcString = text.Substring(endCharIndex + 1, 8);
                            string signedText = text.Substring(startCharIndex, endCharIndex - startCharIndex + 1);
                            if (!crcString.Equals(GetStringSignValue(signedText), StringComparison.InvariantCultureIgnoreCase))
                            {
                                text = text.Remove(startCharIndex, 1).Insert(startCharIndex, BadSignReplaceChar).Remove(endCharIndex, 1).Insert(endCharIndex, BadSignReplaceChar);
                                hasError = true;
                            }
                            int printerModuleCrcIndex = signedText.IndexOf("########");
                            if (printerModuleCrcIndex > -1)
                                text = text.Remove(startCharIndex + printerModuleCrcIndex, 8).Insert(startCharIndex + printerModuleCrcIndex, printerModuleCrc.ToString("X8"));
                            text = text.Remove(endCharIndex + 1, 8);
                        }
                        offset = endCharIndex + 1;
                    }
                }
                else
                    endCharIndex = -1;
            }
            while ((startCharIndex > -1) && (endCharIndex > -1));
            if (hasError)
                return text + SignError;
            return text;
        }

        private static string GetCurrencyString(IFormatProvider numberFormatInfo, Data data)
        {
            if (data.CurrencySymbolAfterValue)
                return string.Concat(data.TotalAmount.ToString(numberFormatInfo), data.SpaceBetweenCurrencySymbolAndValue ? " " : string.Empty, data.CurrencySymbol);
            return string.Concat(data.CurrencySymbol, data.SpaceBetweenCurrencySymbolAndValue ? " " : string.Empty, data.TotalAmount.ToString(numberFormatInfo));
        }

        private string GetSignedString(string text)
        {
            return string.Concat(text, GetStringSignValue(text));
        }

        private string GetStringSignValue(string text)
        {
            crc32.Reset();
            crc32.Append(text);
            return crc32.Value.ToString("X8");
        }
    }
}