using NFC.Helper;
using System;
using System.Collections.Generic;
using System.Linq;

namespace NFC
{
    /// <summary>
    /// Application Protocol Data Unit
    /// https://de.wikipedia.org/wiki/Application_Protocol_Data_Unit
    /// https://github.com/danm-de/pcsc-sharp/blob/246fc4303190184d6acd98a2d66f48cb7ffd7094/src/PCSC.Iso7816/CommandApdu.cs
    /// </summary>
    public class APDUCommand
    {
        #region Constructors
        public APDUCommand(IsoCase isoCase, SCardProtocol sCardProtocol = SCardProtocol.ANY)
        {
            Case = isoCase;
            Protocol = sCardProtocol;
            Data = new byte[0];
        }
        #endregion

        #region Properties
        public IsoCase Case { get; set; }
        public SCardProtocol Protocol { get; set; }

        public byte CLA { get; set; } = 0x00;
        public byte INS { get; set; } = 0x00;
        public byte P1 { get; set; } = 0x00;
        public byte P2 { get; set; } = 0x00;
        public byte LC
        {
            get
            {
                return (byte)Data.Length;
            }
        }
        public byte[] Data { get; set; }
        public byte LE { get; set; } = 0x00;
        #endregion

        #region Methods
        public byte[] ToArray()
        {
            byte[] header = ByteOperation.Concatenate(new byte[] { CLA }, new byte[] { INS }, new byte[] { P1 }, new byte[] { P2 });
            switch (Case)
            {
                case IsoCase.Case1:
                    /* Regarding to OpenSC: T0 needs one additional 
                     * byte containing 0x00. */
                    if (Protocol == SCardProtocol.T0)
                    {
                        return ByteOperation.Concatenate(header, new byte[] { 0x00 });
                    }
                    else
                    {
                        return header;
                    }

                case IsoCase.Case2Short:
                    return ByteOperation.Concatenate(header, new byte[] { LE });

                case IsoCase.Case3Short:
                    return ByteOperation.Concatenate(header, new byte[] { LC }, Data);

                case IsoCase.Case4Short:
                    /* Regarding to OpenSC: T0 has no Le */
                    if (Protocol == SCardProtocol.T0)
                    {
                        return ByteOperation.Concatenate(header, new byte[] { LC }, Data, new byte[] { 0x00 });
                    }
                    else
                    {
                        return ByteOperation.Concatenate(header, new byte[] { LC }, Data, new byte[] { LE });
                    }

                default:
                    throw new NotSupportedException(string.Format("IsoCase {0} is not supported.", Case));
            }
        }

        public override bool Equals(object obj)
        {
            return obj is APDUCommand command &&
                  Case == command.Case &&
                  Protocol == command.Protocol &&
                  CLA == command.CLA &&
                  INS == command.INS &&
                  P1 == command.P1 &&
                  P2 == command.P2 &&
                  Data.SequenceEqual(command.Data) &&
                  LE == command.LE;
        }

        public override int GetHashCode()
        {
            int hashCode = -98047210;
            hashCode = hashCode * -1521134295 + Case.GetHashCode();
            hashCode = hashCode * -1521134295 + Protocol.GetHashCode();
            hashCode = hashCode * -1521134295 + CLA.GetHashCode();
            hashCode = hashCode * -1521134295 + INS.GetHashCode();
            hashCode = hashCode * -1521134295 + P1.GetHashCode();
            hashCode = hashCode * -1521134295 + P2.GetHashCode();
            hashCode = hashCode * -1521134295 + LC.GetHashCode();
            hashCode = hashCode * -1521134295 + EqualityComparer<byte[]>.Default.GetHashCode(Data);
            hashCode = hashCode * -1521134295 + LE.GetHashCode();
            return hashCode;
        }

        public override string ToString()
        {
            string pattern_case1 = "(CASE: 1) CLA: 0x{0:x} | INS: 0x{1:x} | P1: 0x{2:x} | P2: 0x{3:x}";
            string pattern_case2 = "(CASE: 2) CLA: 0x{0:x} | INS: 0x{1:x} | P1: 0x{2:x} | P2: 0x{3:x} | LE: 0x{4:x} |";
            string pattern_case3 = "(CASE: 3) CLA: 0x{0:x} | INS: 0x{1:x} | P1: 0x{2:x} | P2: 0x{3:x} | LC: 0x{4:x} | Data: {5:x}";
            string pattern_case4 = "(CASE: 4) CLA: 0x{0:x} | INS: 0x{1:x} | P1: 0x{2:x} | P2: 0x{3:x} | LC: 0x{4:x} | Data: {5:x} | LE: 0x{6:x} |";

            switch (Case)
            {
                case IsoCase.Case1:
                    return string.Format(pattern_case1, CLA, INS, P1, P2);
                case IsoCase.Case2Short:
                case IsoCase.Case2Extended:
                    return string.Format(pattern_case2, CLA, INS, P1, P2, LE);
                case IsoCase.Case3Short:
                case IsoCase.Case3Extended:
                    return string.Format(pattern_case3, CLA, INS, P1, P2, LC, BitConverter.ToString(Data).Replace("-", "").ToLower());
                case IsoCase.Case4Short:
                case IsoCase.Case4Extended:
                    return string.Format(pattern_case4, CLA, INS, P1, P2, LC, BitConverter.ToString(Data).Replace("-", "").ToLower(), LE);
                default:
                    throw new Exception("Unknown IsoCase");
            }
        }
        #endregion

        #region Operator Overloading
        public static bool operator ==(APDUCommand obj1, APDUCommand obj2)
        {
            return obj1.Equals(obj2);
        }

        public static bool operator !=(APDUCommand obj1, APDUCommand obj2)
        {
            return !(obj1.Equals(obj2));
        }
        #endregion
    }
}