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

namespace NFC.ISO7816_4
{
    public class APDUResponse
    {
        #region constructor
        public APDUResponse()
        {

        }

        /// <summary>
        /// Creates a new APDUResponse from the raw received data.
        /// </summary>
        public APDUResponse(byte[] raw)
        {
            Body = raw.Take(raw.Length - 1).ToArray();
            SW1 = raw[raw.Length - 2];
            SW2 = raw[raw.Length - 3];
        }
        #endregion

        #region Properties
        /// <summary>
        /// ISO 7816-4-4 - Body - Body
        /// </summary>
        public byte[] Body { get; set; }

        /// <summary>
        /// ISO 7816-4 - SW1 - Status Word 1
        /// </summary>
        public byte SW1 { get; set; }

        /// <summary>
        /// ISO 7816-4 - SW2 - Status Word 2
        /// </summary>
        public byte SW2 { get; set; }

        public APDUStatusWords StatusWord
        {
            get
            {
                // Some status words only require a specific first byte
                // and in some cases SW2 contains additional information.
                // This will filter out those errors. When there is more information separate methods for getting those are available.
                switch(SW1) {
                    case 0x61:
                        // Kommando erfolgreich ausgeführt. xx Datenbytes können mit dem ‚GET RESPONSE‘-Kommando abgeholt werden.	Statuswort zur Steuerung des T=0-Protokolls
                        return APDUStatusWords.DATA_READY;
                    case 0x62:
                        // Warnung; Zustand des nichtflüchtigen Speichers nicht verändert
                        return APDUStatusWords.STORAGE_NOT_CHANGED;
                    case 0x63:
                        if((SW2 & 0xF0) == 0xC0) {
                            // Zähler hat den Wert x erreicht (die genaue Bedeutung ist vom Kommando abhängig)	 
                            return APDUStatusWords.COUNTER_REACHED;
                        }
                        // Warnung; Zustand des nichtflüchtigen Speichers verändert
                        return APDUStatusWords.STORAGE_CHANGED;
                    case 0x64:
                        // Ausführungsfehler; Zustand des nichtflüchtigen Speichers nicht verändert
                        return APDUStatusWords.EXECUTION_ERROR_WITHOUT_CHANGE;
                    case 0x65:
                        // Ausführungsfehler; Zustand des nichtflüchtigen Speichers verändert
                        return APDUStatusWords.EXECUTION_ERROR_WITH_CHANGE;
                    case 0x6C:
                        // Falsche Länge Le; xx gibt die korrekte Länge an	Statuswort zur Steuerung des T=0-Protokolls
                        return APDUStatusWords.INVALID_LE; 
                }
                return (APDUStatusWords) (((UInt16) SW1) << 8 | ((UInt16) SW2));
            }
        }

        /// <summary>
        /// If the reponse status is DATA_READY this method can be used to get the amount of data that can be read from the card.
        /// </summary>
        public byte DataLength
        {
            get
            {
                return SW2;
            }
        }

        /// <summary>
        /// If the reponse status is COUNTER_REACHED this method can be used to get the value that the counter reached.
        /// </summary>
        public byte Counter
        {
            get
            {
                return (byte)(SW2 & 0x0F);
            }
        }

        /// <summary>
        /// If the reponse status is INVALID_LE this method can be used to get the correct LE.
        /// </summary>
        public byte CorrectLE
        {
            get
            {
                return SW2;
            }
        }
        #endregion

        #region Methodes
        public byte[] ToArray()
        {
            byte[] array = null;
            if (Body != null)
            {
                array = new byte[Body.Length + 2];
                Body.CopyTo(array, 0);

                array[Body.Length] = SW1;
                array[Body.Length + 1] = SW2;
            }
            else
            {
                array = new byte[2];
                array[0] = SW1;
                array[1] = SW2;
            }

            return array;                
        }

        public override bool Equals(object obj)
        {
            return obj is APDUResponse response &&
                   EqualityComparer<byte[]>.Default.Equals(Body, response.Body) &&
                   SW1 == response.SW1 &&
                   SW2 == response.SW2;
        }

        public override int GetHashCode()
        {
            return HashCode.Combine(Body, SW1, SW2);
        }

        public override string ToString()
        {
            if(Body == null)
            {
                return string.Format("SW1: 0x{0:x} | SW2: 0x{1:x}", SW1, SW2);
            }
            else
            {
                return string.Format("SW1: 0x{0:x} | SW2: 0x{1:x} | Body: {2:x}", SW1, SW2, BitConverter.ToString(Body).Replace("-", "").ToLower());
            }
            
        }
        #endregion
    }
}