using System;
using System.Collections.Generic;
using System.Diagnostics.Eventing.Reader;
using System.Diagnostics.Metrics;
using System.IO;
using System.Linq;
using System.Runtime.ConstrainedExecution;
using System.Text;
using System.Threading.Tasks;
using Genji = GenjiDotNetInterop;

namespace GenjiDotNet
{
    public class APIException : Exception
    {
        public int errorNo;

        public APIException() { }
        public APIException(string message, int errorNo_) : base(message) 
        { 
            errorNo=errorNo_;
        }
    }
    public class API
    {
        public API() 
        {
        }

        /// <summary>
        /// Starts the Matrix API. 
        /// </summary>
        /// <returns>
        /// 0: Success,
        /// -1, -10, -20, -21: LPT related error are ignored
        /// -22(USB driver too old) throw APIError
        /// </returns>
        public short Init()
        {
            short ret = Genji.API.Init_MatrixAPI();
            if (ret == -22)
                throw new APIException("Init", ret);

            return ret;
        }

        /// <summary>
        /// Close MatrixAPI. You have to close the Matrix-API after using the dongle functions, to free all allocated resources.
        /// </summary>
        /// <returns>No return value</returns>
        public short Release()
        {
            return Genji.API.Release_MatrixAPI();
        }

        /// <summary>
        /// Reads the hardwware version number of the dongle
        /// </summary>
        /// <param name="DngNr">Select the target dongle when multiple dongles are connected. 
        /// Give 1 when only one dongle is connected
        /// </param>
        /// <returns>32 bit value. 
        /// Major version (higher 16bits) + Minor version( lower 16bits )
        /// </returns>
        public Version Version(short DngNr)
        {
            int ver = Genji.API.Dongle_Version(DngNr, 85);
            if (ver < 1)
                throw new APIException("Version", ver);

            return new Version(ver >> 16, ver & 0xffff);
        }

        /// <summary>
        /// Return Matrix API version
        /// </summary>
        /// <returns>32 bit value. 
        /// Major version (higher 16bits) + Minor version( lower 16bits ).
        /// </returns>
        public Version VersionAPI()
        {
            int ver = Genji.API.GetVersionAPI();
            if (ver < 1)
                throw new APIException("VersionAPI", ver);

            return new Version(ver >> 16, ver & 0xffff);
        }

        /// <summary>
        /// Return Matrix USB driver version
        /// </summary>
        /// <returns>32 bit value. 
        /// Major version (higher 16bits) + Minor version( lower 16bits )
        /// </returns>
        public Version VersionDRV_USB()
        {
            int ver=Genji.API.GetVersionDRV_USB();
            if (ver < 1)
                throw new APIException("VersionDRV_USB", ver);

            return new Version(ver >> 16, ver & 0xffff);
        }

        /// <summary>
        /// Return Matrix dongle model number
        /// </summary>
        /// <param name="DngNr">Select the target dongle when multiple dongles are connected. 
        /// Give 1 when only one dongle is connected
        /// </param>
        /// <returns>The model number of the Matrix hardware is returned, for example 257, 657 etc</returns>
        public int Model(short DngNr)
        {
            int ret = Genji.API.Dongle_Model(DngNr, 85);
            if (ret < 1)
                throw new APIException("Model", ret);

            return ret;
        }

        /// <summary>
        /// Return the memory size in bytes( 60 for MLU-60, 316 for MLU-316 )
        /// </summary>
        /// <param name="DngNr">Select the target dongle when multiple dongles are connected. 
        /// Give 1 when only one dongle is connected
        /// </param>
        /// <returns>Memory size in bytes</returns>
        public short MemSize(short DngNr)
        {
            short ret = Genji.API.Dongle_MemSize(DngNr, 85);
            if (ret < 1)
                throw new APIException("MemSize", ret);

            return ret;
        }

        /// <summary>
        ///Returns the number of dongles available at the current DngPort (SetDngPort())
        /// </summary>
        /// <returns>the number of dongle.</returns>
        public int Count()
        {
            short ret = Genji.API.Dongle_Count(85);
            if (ret < 1)
                throw new APIException("Count", ret);

            return ret;
        }

        /// <summary>
        /// Reads the unique serial number which is assigned to each Matrix-dongle
        /// </summary>
        /// <param name="UserCode">Your personal UserCode</param>
        /// <param name="DngNr">Select the target dongle when multiple dongles are connected. 
        /// Give 1 when only one dongle is connected </param>
        /// <returns>
        /// 32bit serial number
        /// </returns>
        public int ReadSerNr(int UserCode, short DngNr)
        {
            int ret  = Genji.API.Dongle_ReadSerNr(UserCode, DngNr, 85);
            if (ret < 1)
                throw new APIException("ReadSerNr", ret);

            return ret;
        }

        /// <summary> 
        /// Reads the data from the Matrix-dongle beginning from the first memory field.
        /// </summary>
        /// <param name="UserCode">Your personal UserCode</param>
        /// <param name="data">32bit int array into which file data is read</param>
        /// <param name="count">the number of fields to be read</param>
        /// <param name="DngNr">Select the target dongle when multiple dongles are connected. 
        /// Give 1 when only one dongle is connected</param>
        /// <returns>the number of data fields which were successfully read from the dongle</returns>
        public short ReadData(int UserCode, int[] data, short count, short DngNr )
        {
            short ret  = Genji.API.Dongle_ReadData(UserCode, data, count, DngNr, 85);
            if (ret < 1)
                throw new APIException("ReadData", ret);

            return ret;
        }

        /// <summary>
        /// Reads the data from the Matrix-dongle beginning from the specifyed memory field.
        /// </summary>
        /// <param name="UserCode">Your personal UserCode</param>
        /// <param name="data">32bit int array into which file data is read</param>
        /// <param name="fpos">the field number to start reading</param>
        /// <param name="count">the number of fields to be read</param>
        /// <param name="DngNr">Select the target dongle when multiple dongles are connected. 
        /// Give 1 when only one dongle is connected</param>
        /// <returns>the number of data fields which were successfully read from the dongle</returns>
        public short ReadDataEx(int UserCode, int[] data, short fpos, short count,  short DngNr)
        {
            short ret = Genji.API.Dongle_ReadDataEx(UserCode, data, fpos, count, DngNr, 85);
            if (ret < 1)
                throw new APIException("ReadDataEx", ret);

            return ret;
        }

        /// <summary>
        /// Writes data into the Matrix-dongle beginning from the first memory field.
        /// </summary>
        /// <param name="UserCode">Your personal UserCode</param>
        /// <param name="data">32bit int array holding the data to be written to the dongle</param>
        /// <param name="count">the number of fields to be written</param>
        /// <param name="DngNr">Select the target dongle when multiple dongles are connected. 
        /// Give 1 when only one dongle is connected</param>
        /// <returns>the number of data fields which were successfully written to the dongle</returns>
        public short WriteData(int UserCode, int[] data, short count, short DngNr)
        {
            short ret = Genji.API.Dongle_WriteData(UserCode, data, count, DngNr, 85);
            if (ret < 1)
                throw new APIException("WriteData", ret);

            return ret;
        }

        /// <summary>
        /// Writes data into the Matrix-dongle beginning from the specifyed memory field
        /// </summary>
        /// <param name="UserCode">Your personal UserCode</param>
        /// <param name="data">32bit int array holding the data to be written to the dongle</param>
        /// <param name="fpos">the field number to start writing</param>
        /// <param name="count">the number of fields to be written</param>
        /// <param name="DngNr">Select the target dongle when multiple dongles are connected. 
        /// Give 1 when only one dongle is connected</param>
        /// <returns>the number of data fields which were successfully written to the dongle</returns>
        public short WriteDataEx(int UserCode, int[] data, short fpos, short count, short DngNr)
        {
            short ret = Genji.API.Dongle_WriteDataEx(UserCode, data, fpos, count, DngNr, 85);
            if (ret < 1)
                throw new APIException("WriteDataEx", ret);

            return ret;
        }

        /// <summary>
        /// Saves the 128-bit XTEA key in the dongle
        /// </summary>
        /// <param name="UserCode">Your personal UserCode</param>
        /// <param name="key">Pointer to XTEA key buffer(int[4]) to be stored in the dongle.</param>
        /// <param name="DngNr">Select the target dongle when multiple dongles are connected. 
        /// Give 1 when only one dongle is connected</param>
        /// <returns>
        /// More than 0 for success</returns>
        public short WriteKey(int UserCode, uint[] key, short DngNr)
        {
            short ret = Genji.API.Dongle_WriteKey(UserCode, key, DngNr, 85);
            if (ret < 1)
                throw new APIException("WriteKey", ret);

            return ret;
        }

        /// <summary>
        /// Checks whether a 128-bit XTEA key different to zero is available in the dongle.
        /// </summary>
        /// <param name="UserCode">Your personal UserCode</param>
        /// <param name="DngNr">Select the target dongle when multiple dongles are connected.
        /// Give 1 when only one dongle is connected </param>
        /// <returns>
        /// 1 if a key is available in the dongle or 0 if the key is not present in the  dongle(Zero-Key -> all key bytes = 0).
        /// </returns>
        public short KeyFlag(int UserCode, short DngNr)
        {
            short ret = Genji.API.Dongle_GetKeyFlag(UserCode, DngNr, 85);
            if (ret < 1)
                throw new APIException("KeyFlag", ret);

            return ret;
        }

        /// <summary>
        /// Transmit a 8 bytes clear data block to the dongle.
        /// </summary>
        /// <param name="UserCode">Your personal UserCode</param>
        /// <param name="data">8 bytes data buffer(int[2]) with the values to be encrypted.the buffer is overwritten</param>
        /// <param name="DngNr">Select the target dongle when multiple dongles are connected. 
        /// Give 1 when only one dongle is connected</param>
        /// <returns>a value higher than 0</returns>
        public short EncryptData(int UserCode, int[] data, short DngNr)
        {
            short ret =Genji.API.Dongle_EncryptData(UserCode, data, DngNr, 85);
            if (ret < 0)
                throw new APIException("EncryptData", ret);

            return ret;
        }

        /// <summary>
        /// Transmit a 8 bytes encrypted data block to the dongle.This is returned decrypted as clear data
        /// </summary>
        /// <param name="UserCode">Your personal UserCode</param>
        /// <param name="data">8 bytes data buffer(int[2]) with the values to be decrypted. the buffer is overwritten</param>
        /// <param name="DngNr">Select the target dongle when multiple dongles are connected.
        /// Give 1 when only one dongle is connected</param>
        /// <returns>a value higher than 0</returns>
        public short DecryptData(int UserCode, int[] data, short DngNr)
        {
            short ret = Genji.API.Dongle_DecryptData(UserCode, data, DngNr, 85);
            if (ret < 1)
                throw new APIException("DecryptData", ret);

            return ret;
        }


        /// <summary>
        /// Return the memory size of MemoryArea2 in bytes( the default size is 8192(8K) )
        /// </summary>
        /// <param name="DngNr">Select the target dongle when multiple dongles are connected. 
        /// Give 1 when only one dongle is connected
        /// </param>
        /// <returns>Memory size in bytes</returns>
        public short MemSize2(short DngNr)
        {
            short ret = Genji.API.Dongle_MemSize2(DngNr, 85);
            if (ret < 1)
                throw new APIException("MemSize2", ret);

            return ret;
        }

        /// <summary>
        /// Turn on, turn off or blink the dongle LED
        /// </summary>
        /// <param name="mode">
        ///     <0 : Turn on LED,
        ///     0  : Turn off LED,
        ///     >0 : Blink LED mode times per second
        /// </param>
        /// <param name="DngNr">Select the target dongle when multiple dongles are connected. 
        /// Give 1 when only one dongle is connected
        /// </param>
        /// <returns>1 on success</returns>
        public short SetLED(short mode, short DngNr)
        {
            short ret = Genji.API.Dongle_SetLED(mode, DngNr, 85);
            if( ret != 1)
                throw new APIException("SetLED", ret);

            return ret;
        }

        /// <summary>
        /// Read the dongle's Globally Unique Serial Number(GUSN)
        /// </summary>
        /// <param name="UserCode">Your personal UserCode</param>
        /// <param name="gSN">Buffer to receive 16-byte GUSN.Must be at least 16 byte long</param>
        /// <param name="DngNr">Select the target dongle when multiple dongles are connected. 
        /// Give 1 when only one dongle is connected
        /// </param>
        /// <returns>1 on success</returns>
        public string ReadGUSN(int UserCode, short DngNr)
        {
            byte[] gSN=new byte[24];

            short ret = Genji.API.Dongle_ReadGUSN(UserCode, gSN, DngNr, 85);
            if (ret != 1)
                throw new APIException("ReadGUSN", ret);

            try
            {
                string s = Encoding.UTF8.GetString(gSN);
                return s;
            }
            catch ( Exception ex)
            {
                throw new APIException("ReadGUSN", -999);
            }

        }

        /// <summary>
        /// Write-protect or write-unprotect the first memory area ( MemArea1 ) with a 32bit integer as the lock key.
        /// Trying to unlock the locked area with wrong keys a certain number of times successively will lock the area permanently. 
        /// </summary>
        /// <param name="UserCode">Your personal UserCode</param>
        /// <param name="bLock">1 for Lock, 0 for unlock</param>
        /// <param name="lockKey">any 32bit integer</param>
        /// <param name="DngNr">Select the target dongle when multiple dongles are connected. 
        /// Give 1 when only one dongle is connected
        /// </param>
        /// <returns>1 on success</returns>
        public short LockData(int UserCode, int bLock, uint lockKey, short DngNr)
        {
            short ret = Genji.API.Dongle_LockData(UserCode, bLock, lockKey,DngNr, 85);
            if (ret != 1)
                throw new APIException("LockData", ret);

            return ret;
        }

        /// <summary>
        /// Write-protect or write-unprotect the second memory area ( MemArea2 ) with a 32bit integer as the lock key.
        /// Trying to unlock the locked area with wrong keys a certain number of times successively will lock the area permanently. 
        /// </summary>
        /// <param name="UserCode">Your personal UserCode</param>
        /// <param name="bLock">1 for Lock, 0 for unlock</param>
        /// <param name="lockKey">any 32bit integer</param>
        /// <param name="DngNr">Select the target dongle when multiple dongles are connected. 
        /// Give 1 when only one dongle is connected
        /// </param>
        /// <returns>1 on success</returns>
        public short LockData2(int UserCode, int bLock, uint lockKey, short DngNr)
        {
            short ret = Genji.API.Dongle_LockData2(UserCode, bLock, lockKey, DngNr, 85);
            if (ret != 1)
                throw new APIException("LockData2", ret);

            return ret;
        }


        /// <summary>
        /// Read the specified amount(len) of data from MemArea2 starting the specified offset(pos) 
        /// </summary>
        /// <param name="UserCode">Your personal UserCode</param>
        /// <param name="data">Buffer to receive the read data</param>
        /// <param name="pos">Offset to start reading the data from</param>
        /// <param name="len">Length of the data to be read</param>
        /// <param name="DngNr">Select the target dongle when multiple dongles are connected. 
        /// Give 1 when only one dongle is connected</param>
        /// <returns>Number of read bytes</returns>
        public short ReadData2(int UserCode, byte[] data, short pos, short len, short DngNr)
        {
            short ret = Genji.API.Dongle_ReadData2(UserCode, data, pos, len, DngNr, 85);
            if (ret < 1)
                throw new APIException("ReadData2", ret);

            return ret;
        }

        /// <summary>
        /// Write the specified amount(len) of data to MemArea2, starting the specified offset(pos) 
        /// </summary>
        /// <param name="UserCode">Your personal UserCode</param>
        /// <param name="data">Buffer holding the data to be writteen</param>
        /// <param name="pos">Offset to start writing the data to</param>
        /// <param name="len">Length of the data to be written</param>
        /// <param name="DngNr">Select the target dongle when multiple dongles are connected. 
        /// Give 1 when only one dongle is connected</param>
        /// <returns>Number of written bytes</returns>
        public short WriteData2(int UserCode, byte[] data, short pos, short len, short DngNr)
        {
            short ret = Genji.API.Dongle_WriteData2(UserCode, data, pos, len, DngNr, 85);
            if (ret < 1)
                throw new APIException("WriteData2", ret);

            return ret;
        }

        /// <summary>
        /// Create RSA key pair at the specified key index. You can create up to 3 key pairs at the index 1,2 or 3.
        /// </summary>
        /// <param name="UserCode">Your personal UserCode</param>
        /// <param name="keyIndex">Where to save the created key pair. Value can be 1,2 or 3</param>
        /// <param name="DngNr">Select the target dongle when multiple dongles are connected. 
        /// Give 1 when only one dongle is connected</param>
        /// <returns>1 on success</returns>
        public short CreateRSAKeyPair(int UserCode, short keyIndex, short DngNr)
        {
            short ret = Genji.API.Dongle_CreateRSAKeyPair(UserCode, keyIndex, DngNr, 85);
            if (ret != 1)
                throw new APIException("CreateRSAKeyPair", ret);

            return ret;
        }

        /// <summary>
        /// Encrypt the given data(plain) by the public key of RSA key pair at the specified key index 
        /// </summary>
        /// <param name="UserCode">Your personal UserCode</param>
        /// <param name="keyIndex">Key index of RSA Kay pair. Value can be 1,2 or 3</param>
        /// <param name="plain">Plain data to be encrypted</param>
        /// <param name="plainLen">Length of plain data</param>
        /// <param name="cipher">Buffer to receive encrypted data</param>
        /// <param name="cipherLen">Length of encrypted data</param>
        /// <param name="DngNr">Select the target dongle when multiple dongles are connected. 
        /// Give 1 when only one dongle is connected</param>
        /// <returns>1 on success</returns>
        public short EncryptDataRSA(int UserCode, short keyIndex, byte[] plain, short plainLen, byte[] cipher, ref short cipherLen, short DngNr)
        {
            short ret = Genji.API.Dongle_EncryptDataRSA(UserCode, keyIndex, plain, plainLen, cipher, ref cipherLen, DngNr, 85);
            if (ret != 1)
                throw new APIException("EncryptDataRSA", ret);

            return ret;
        }

        /// <summary>
        /// Decrypt the given data(cipher) by the private key of RSA key pair at the specified key index 
        /// </summary>
        /// <param name="UserCode">Your personal UserCode</param>
        /// <param name="keyIndex">Key index of RSA Kay pair. Value can be 1,2 or 3</param>
        /// <param name="cipher">Encrypted data to be decrypted</param>
        /// <param name="cipherLen">Length of encrypted data</param>
        /// <param name="cipher">Buffer to receive decrypted data</param>
        /// <param name="cipherLen">Length of decrypted data</param>
        /// <param name="DngNr">Select the target dongle when multiple dongles are connected. 
        /// Give 1 when only one dongle is connected</param>
        /// <returns>1 on success</returns>
        public short DecryptDataRSA(int UserCode, short keyIndex, byte[] cipher, short cipherLen, byte[] plain, ref short plainLen, short DngNr)
        {
            short ret = Genji.API.Dongle_DecryptDataRSA(UserCode, keyIndex, cipher, cipherLen, plain, ref plainLen, DngNr, 85);
            if (ret != 1)
                throw new APIException("EncryptDataRSA", ret);

            return ret;
        }


        /// <summary>
        /// Sign the given data(msg) by RSA key at the specified key index. Msg is hashed by SHA256 before signing
        /// </summary>
        /// <param name="UserCode">Your personal UserCode</param>
        /// <param name="keyIndex">Key index of RSA Kay pair. Value can be 1,2 or 3</param>
        /// <param name="msg">Data to be signed</param>
        /// <param name="msgLen">Length of msg</param>
        /// <param name="sig">Buffer to receive signature</param>
        /// <param name="sigLen">Length of signature</param>
        /// <param name="DngNr">Select the target dongle when multiple dongles are connected. 
        /// Give 1 when only one dongle is connected</param>
        /// <returns>1 on success</returns>
        public short SignDataRSA(int UserCode, short keyIndex, byte[] msg, short msgLen, byte[] sig, ref short sigLen, short DngNr)
        {
            short ret = Genji.API.Dongle_SignDataRSA(UserCode, keyIndex, msg, msgLen, sig, ref sigLen, DngNr, 85);
            if (ret != 1)
                throw new APIException("SignDataRSA", ret);

            return ret;
        }

        /// <summary>
        /// Verify the signature using RSA key at the specified key index.
        /// </summary>
        /// <param name="UserCode">Your personal UserCode</param>
        /// <param name="keyIndex">Key index of RSA Kay pair. Value can be 1,2 or 3</param>
        /// <param name="msg">Data to be signed</param>
        /// <param name="msgLen">Length of msg</param>
        /// <param name="sig">signature to be verified</param>
        /// <param name="sigLen">Length of signature</param>
        /// <param name="DngNr">Select the target dongle when multiple dongles are connected. 
        /// Give 1 when only one dongle is connected</param>
        /// <returns>1 on success</returns>
        public short VerifyDataRSA(int UserCode, short keyIndex, byte[] msg, short msgLen, byte[] sig, short sigLen, short DngNr)
        {
            short ret = Genji.API.Dongle_VerifySigRSA(UserCode, keyIndex, msg, msgLen, sig, sigLen, DngNr, 85);
            if (ret != 1)
                throw new APIException("VerifyDataRSA", ret);

            return ret;
        }

        /// <summary>
        /// Set the timer mode
        /// </summary>
        /// <param name="UserCode">Your personal UserCode</param>
        /// <param name="mode">Timer mode.
        /// 0:Non-cycle mode. Timer stops when the counter number overflows. 1:Cycle mode. Timer restarts when the counter number overflows.  
        /// </param>
        /// <param name="DngNr">Select the target dongle when multiple dongles are connected. 
        /// Give 1 when only one dongle is connected</param>
        /// <returns>1 on success</returns>
        public short SetTimer(int UserCode, short mode, short DngNr)
        {
            short ret = Genji.API.Dongle_SetTimer(UserCode, mode, DngNr, 85);
            if (ret != 1)
                throw new APIException("SetTimer", ret);

            return ret;
        }

        /// <summary>
        /// Start the timer
        /// </summary>
        /// <param name="UserCode">Your personal UserCode</param>
        /// <param name="DngNr">Select the target dongle when multiple dongles are connected. 
        /// Give 1 when only one dongle is connected</param>
        /// <returns>1 on success</returns>
        public short StartTimer(int UserCode, short DngNr)
        {
            short ret = Genji.API.Dongle_StartTimer(UserCode, DngNr, 85);
            if (ret != 1)
                throw new APIException("StartTimer", ret);

            return ret;
        }

        /// <summary>
        /// Stop the timer
        /// </summary>
        /// <param name="UserCode">Your personal UserCode</param>
        /// <param name="DngNr">Select the target dongle when multiple dongles are connected. 
        /// Give 1 when only one dongle is connected</param>
        /// <returns>1 on success</returns>
        public short StopTimer(int UserCode, short DngNr)
        {
            short ret = Genji.API.Dongle_StopTimer(UserCode, DngNr, 85);
            if (ret != 1)
                throw new APIException("StopTimer", ret);

            return ret;
        }

        /// <summary>
        /// Stop the timer
        /// </summary>
        /// <param name="UserCode">Your personal UserCode</param>
        /// <param name="dwCount">Address of the value to receive the timer counter.
        /// The counter increases by one for 64 CPU clock cycles. The CPU frequency is 24MHZ.The time span represented by one counter unit is 1*64/24000000 ≈ 2.7μs
        /// </param>
        /// <param name="DngNr">Select the target dongle when multiple dongles are connected. 
        /// Give 1 when only one dongle is connected</param>
        /// <returns>1 on success</returns>
        public short GetTimer(int UserCode, ref uint dwCount, short DngNr)
        {
            return Genji.API.Dongle_GetTimer(UserCode, ref dwCount, DngNr, 85);
        }

        /// <summary>
        /// Genreate a random bytes( up to 127 bytes )
        /// </summary>
        /// <param name="UserCode">Your personal UserCode</param>
        /// <param name="siz">Size of the random bytes (in byte ) to be generated</param>
        /// <param name="rand">Buffer to receive the random bytes</param>
        /// <param name="DngNr">Select the target dongle when multiple dongles are connected. 
        /// Give 1 when only one dongle is connected</param>
        /// <returns>1 on success</returns>
        public short GetRand(int UserCode, short siz, byte[] rand, short DngNr)
        {
            return Genji.API.Dongle_GetRand(UserCode, siz, rand, DngNr, 85);
        }

        public void RegisterGenjiCallbackPort(int port)
        {
            Genji.API.RegisterGenjiCallbackPort(port);
        }

    }
}
