package SecureDongle;

import lombok.Getter;
import lombok.Setter;
import org.tinylog.Logger;

import java.util.function.Consumer;

import static Config.SomeCodes.ToShort;

public class SecureDongle {

    private final int[] lp1 = new int[1];
    private final int[] lp2 = new int[1];
    // for Open/Close Handle
    private final short[] handle = new short[1];
    // P1
    private final short[] p1 = new short[1];
    // P2
    private final short[] p2 = new short[1];
    // P3
    private final short[] p3 = new short[1];
    // P4
    private final short[] p4 = new short[1];

    private final byte[] buffer = new byte[1024];

    LibSecureDongle SD;

    private @Getter boolean Opened = false;
    private @Getter short Handle;
    private @Getter int HardwareID = 0;
    private @Getter int UserID = 0;

    private @Setter SecureDongleEvent event;

    /**
     * Create SecureDongle with P1, P2, P3, P4
     * @param P1 Password 1
     * @param P2 Password 2
     * @param P3 Password 3
     * @param P4 Password 4
     */
    public SecureDongle(Short P1, Short P2, Short P3, Short P4){
        this.p1[0] = P1;
        this.p2[0] = P2;
        this.p3[0] = P3;
        this.p4[0] = P4;
        SD = LibSecureDongle.Instance;
    }

    /**
     * Create SecureDongle with P1, P2, P3, P4
     * @param P1 Password 1
     * @param P2 Password 2
     * @param P3 Password 3
     * @param P4 Password 4
     */
    public SecureDongle(String P1, String P2, String P3, String P4){
        this.p1[0] = ToShort(P1);
        this.p2[0] = ToShort(P2);
        this.p3[0] = ToShort(P3);
        this.p4[0] = ToShort(P4);
        SD = LibSecureDongle.Instance;
    }




    /**
     * Find SecureDongle
     * This must be executed first time
     * @return true if found
     */
    public boolean Find(){
        HardwareID = 0;
        short result = SD.SecureDongle(LibSecureDongle.SD_FIND, handle, lp1, lp2, p1, p2, p3, p4, buffer);

        if (result== LibSecureDongle.ERR_SUCCESS){
            HardwareID = lp1[0];
            //System.out.println("SecureDongle found with HardwareID="+HardwareID);
            return true;
        } else {
            if (event!=null) event.onDongleError("Find", result);
        }
        return false;
    }

    /**
     * Open SecureDongle
     * @return true if success
     */
    public boolean Open(){
        Handle=0;
        if (HardwareID!=0){
            lp1[0] = HardwareID;
            short result = SD.SecureDongle(LibSecureDongle.SD_OPEN, handle, lp1, lp2, p1, p2, p3, p4, buffer);
            if (result== LibSecureDongle.ERR_SUCCESS){
                Handle = handle[0];
                Opened = true;
                return true;
            } else if (event!=null) event.onDongleError("Open", result);
        } //else System.out.println("HardwareID not found, Find SecureDongle first");
        return false;
    }

    /**
     * Close SecureDongle
     * @return true if success
     */
    public boolean Close(){
        handle[0] = Handle;
        short result = SD.SecureDongle(LibSecureDongle.SD_CLOSE, handle, lp1, lp2, p1, p2, p3, p4, buffer);
        if (result== LibSecureDongle.ERR_SUCCESS){
            return true;
        } else if (event!=null) event.onDongleError("Close", result);
        Opened = false;
        return false;
    }

    /**
     * Read from User Data Zone (UDZ)
     * @param StartAddress Start Address, zero based
     * @param Length Length of data to read, max 1000 bytes
     * @return byte array of data, length=0 if failed
     */
    public byte[] Read(short StartAddress, short Length){
        if (Opened){
            handle[0] = Handle;
            p1[0] = StartAddress>=0 ? StartAddress : 0;
            if (Length<1) Length=1;
            if (Length>1000) Length=1000;
            p2[0] = Length;
            short result = SD.SecureDongle(LibSecureDongle.SD_READ, handle, lp1, lp2, p1, p2, p3, p4, buffer);
            if (result== LibSecureDongle.ERR_SUCCESS){
                //System.out.println("SecureDongle HardwareID="+HardwareID+" read success ");
                byte[] data = new byte[Length];
                System.arraycopy(buffer, 0, data, 0, Length);
                return data;
            } else if (event!=null) event.onDongleError("Read", result);
        } //else System.out.println("SecureDongle not opened");

        return new byte[0];
    }

    /**
     * Write to User Data Zone (UDZ)
     * @param StartAddress start address of UDZ, zero based
     * @param length length of data to write, max 1000 bytes
     * @param data data to write
     * @return true if success
     */
    public boolean Write(short StartAddress, short length, byte[] data){
        if (Opened){
            handle[0] = Handle;
            p1[0] = StartAddress>=0 ? StartAddress : 0;
            if (length<1) length=1;
            if (length>1000) length=1000;
            p2[0] = length;
            System.arraycopy(data, 0, buffer, 0, length);
            short result = SD.SecureDongle(LibSecureDongle.SD_WRITE, handle, lp1, lp2, p1, p2, p3, p4, buffer);
            if (result== LibSecureDongle.ERR_SUCCESS){
                //System.out.println("SecureDongle HardwareID="+HardwareID+" write success ");
                return true;
            } else if (event!=null) event.onDongleError("Write", result);
        } //else System.out.println("SecureDongle not opened");

        return false;
    }

    /**
     * Generate Random Number
     * @return short array of random number
     */
    public short[] GenerateRandomNumber(){
        short[] random = new short[4];
        if (Opened){
            handle[0] = Handle;
            short result = SD.SecureDongle(LibSecureDongle.SD_RANDOM, handle, lp1, lp2, p1, p2, p3, p4, buffer);
            if (result== LibSecureDongle.ERR_SUCCESS){
                //System.out.println("SecureDongle HardwareID="+HardwareID+" generate random success ");
                random[0] = p1[0];
                random[1] = p2[0];
                random[2] = p3[0];
                random[3] = p4[0];
                return random;
            } else if (event!=null) event.onDongleError("GenerateRandomNumber", result);
        } //else System.out.println("SecureDongle not opened");

        return random;
    }

    /**
     * Write UserID to SecureDongle
     * @param UserID UserID to write
     * @return true if success
     */
    public boolean WriteUserID(int UserID){
        if (Opened){
            handle[0] = Handle;
            lp1[0] = UserID;
            short result = SD.SecureDongle(LibSecureDongle.SD_WRITE_USERID, handle, lp1, lp2, p1, p2, p3, p4, buffer);
            if (result== LibSecureDongle.ERR_SUCCESS){
                //System.out.println("SecureDongle HardwareID="+HardwareID+" set UserID success ");
                this.UserID = UserID;
                return true;
            } else if (event!=null) event.onDongleError("WriteUserID", result);
        } //else System.out.println("SecureDongle not opened");

        return false;
    }

    /**
     * Read UserID from SecureDongle
     * @return UserID, 0 if failed
     */
    public int ReadUserID(){
        if (Opened){
            handle[0] = Handle;
            short result = SD.SecureDongle(LibSecureDongle.SD_READ_USERID, handle, lp1, lp2, p1, p2, p3, p4, buffer);
            if (result== LibSecureDongle.ERR_SUCCESS){
                this.UserID = lp1[0];
                //System.out.println("SecureDongle HardwareID="+HardwareID+" read UserID success, value = "+UserID);
                return UserID;
            } else if (event!=null) event.onDongleError("ReadUserID", result);
        } //else System.out.println("SecureDongle not opened");

        return 0;
    }

    boolean ismonitoring = false;

    /**
     * Stop Monitoring SecureDongle
     */
    public void StopMonitor(){
        ismonitoring = false;
    }

    /**
     * Start Monitoring SecureDongle
     * if dongle missing, will raise event onDongleMissing
     */
    public void StartMonitor(){
        new Thread(()->{
            if (HardwareID==0) Find();
            Open();
            int firstUserID = ReadUserID();
            Close();
            if (firstUserID!=0){
                ismonitoring = true;
                Logger.info("Start Monitoring UserID="+Integer.toHexString(firstUserID));
                while (ismonitoring){
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    Open();
                    int newUserID = ReadUserID();
                    Close();
                    if (newUserID!=firstUserID){
                        if (event!=null) event.onDongleMissing();
                    }

                }
                System.out.println("Stop Monitoring");
            } else System.out.println("Canceled Monitoring, UserID not found");

        }).start();
    }

}
