using System;

namespace NFC.Crypto
{
    /// <summary>
    /// Key for DESFire Card
    /// </summary>
    public class CipherKey
    {
        #region Constructors
        /// <summary>
        /// Creates Key from Array
        /// </summary>
        /// <param name="key">Key</param>
        /// <param name="cipher">Cipher for Key</param>
        /// <param name="keyVersion">Version of Key</param>
        public CipherKey(byte[] key, CipherType cipher, byte keyVersion)
        {
            _Cipher = cipher;

            if (cipher == CipherType.AES && keyVersion < 0x10)
            {
                throw new ArgumentOutOfRangeException("KeyVersion is to low for AES Key (Minimum = 0x10)");
            }
            _KeyVersion = keyVersion;

            if (!CheckKey(key, cipher))
            {
                throw new ArgumentException("Key is not vaild for CipherType");
            }

            if (cipher == CipherType.TDES || cipher == CipherType.TDES_2K || cipher == CipherType.TDES_3K)
            {
                _Key = SetKeyVersion(key, keyVersion);
            }
            else
            {
                _Key = key;
            }
        }

        /// <summary>
        /// Generates Empty Key
        /// </summary>
        /// <param name="cipher">Cipher for Key</param>
        /// <param name="keyVerion"></param>
        public CipherKey(CipherType cipher)
        {
            _Cipher = cipher;
            _Key = GenerateEmptyKey(cipher);

            if (cipher == CipherType.AES)
            {
                _KeyVersion = 0x10;
            }
            else
            {
                _KeyVersion = 0x00;
            }
        }
        #endregion

        #region Properties
        /// <summary>
        /// Key as Array
        /// </summary>
        public byte[] _Key { get; private set; }

        /// <summary>
        /// CipherType of Key
        /// </summary>
        public CipherType _Cipher { get; private set; }

        /// <summary>
        /// KeyVersion of Key
        /// For AES 0x10 is minimum
        /// </summary>
        public byte _KeyVersion { get; private set; }
        #endregion

        #region Methods
        /// <summary>
        /// Generate Empty Key for CipherType
        /// </summary>
        /// <param name="cipher">Type of Cipher</param>
        public byte[] GenerateEmptyKey(CipherType cipher)
        {
            uint size = GetKeySize(cipher);

            byte[] key = new byte[size];
            for (int i = 0; i < size; i++)
            {
                key[i] = 0;
            }

            return key;
        }

        /// <summary>
        /// Check Key Array
        /// </summary>
        /// <param name="key">Key</param>
        /// <param name="cipher">Cipher Type of Key</param>
        public bool CheckKey(byte[] key, CipherType cipher)
        {
            if (key.Length != GetKeySize(cipher))
            {
                return false;
            }
            else
            {
                return true;
            }
        }

        /// <summary>
        /// Get KeySize for CipherType
        /// </summary>
        /// <param name="cipher">Type of Cipher</param>
        public uint GetKeySize(CipherType cipher)
        {
            switch (cipher)
            {
                case CipherType.TDES:
                    return 8;
                case CipherType.TDES_2K:
                    return 16;
                case CipherType.TDES_3K:
                    return 24;
                case CipherType.AES:
                    return 16;
                default:
                    throw new ArgumentOutOfRangeException("Unknown CipherType.");
            }
        }

        /// <summary>
        /// Set Key Version for DES/TDES Keys
        /// KeyVersion is stored in the LSBits of the first 8 Bytes
        /// Parity Bits are not used from DESFire Cars
        /// </summary>
        /// <param name="key"></param>
        /// <param name="version"></param>
        /// <returns></returns>
        public byte[] SetKeyVersion(byte[] key, byte version)
        {
            byte[] pow2 = new byte[]
            {
                0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80
            };

            byte[] new_key = new byte[key.Length];
            key.CopyTo(new_key, 0);

            for (int i = 0; i < 8; i++)
            {
                if ((version & pow2[i]) > 0)
                {
                    new_key[i] = (byte)(new_key[5] | 0x01);
                }
                else
                {
                    new_key[i] = (byte)(new_key[5] & 0x7F);
                }
            }

            return new_key;
        }
        #endregion
    }
}