package com.iotize.plugin.cordova.zebrarfid;

import android.content.Context;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.zebra.rfid.api3.ENUM_TRANSPORT;
import com.zebra.rfid.api3.IRFIDLogger;
import com.zebra.rfid.api3.InvalidUsageException;
import com.zebra.rfid.api3.OperationFailureException;
import com.zebra.rfid.api3.RFIDReader;
import com.zebra.rfid.api3.ReaderDevice;
import com.zebra.rfid.api3.Readers;
import com.zebra.rfid.api3.RfidEventsListener;
import com.zebra.rfid.api3.START_TRIGGER_TYPE;
import com.zebra.rfid.api3.STOP_TRIGGER_TYPE;
import com.zebra.rfid.api3.TagAccess;
import com.zebra.rfid.api3.TagAccess.ReadAccessParams;
import com.zebra.rfid.api3.TagData;
import com.zebra.rfid.api3.TriggerInfo;

import java.util.ArrayList;

public class ZebraRfidImpl {

    private static final String TAG = "ZebraRfidImpl";

    @Nullable
    private Readers rfidReadersDiscovery;

    @NonNull
    private Context context;

    @Nullable
    private RfidEventsListener eventListener;

    @Nullable
    RFIDReader reader;

    ZebraRfidImpl(Context context) {
        this.context = context;
    }


    public ArrayList<ReaderDevice> discoverRFIDReaders() throws InvalidUsageException {
        if (rfidReadersDiscovery == null) {
            rfidReadersDiscovery = new Readers(context, ENUM_TRANSPORT.RE_SERIAL);
        }
        return rfidReadersDiscovery.GetAvailableRFIDReaderList();
    }

    @NonNull
    public RFIDReader connect() throws OperationFailureException, InvalidUsageException, CodeErrorException {
        if (this.reader == null) {
            this.reader = this._getReader();
        }
        if (!this.reader.isConnected()) {
            this.reader.connect();
        }
        return this.reader;
    }

    @NonNull
    public RFIDReader _getReader() throws CodeErrorException, InvalidUsageException {
        ArrayList<ReaderDevice> readers = this.discoverRFIDReaders();
        if (!readers.isEmpty()) {
            return readers.get(0).getRFIDReader();
        }
        throw CodeErrorException.noRFIDReader();
    }

    public Void disconnect() throws OperationFailureException, InvalidUsageException {
        if (reader != null) {
            if (eventListener != null) {
                reader.Events.removeEventsListener(eventListener);
                eventListener = null;
            }
            if (reader.isConnected()) {
                reader.disconnect();
            }
            reader = null;
        }
        return null;
    }

    /**
     * Listen to events
     * If already listening, the old one will be removed
     *
     * @param eventListener
     * @return
     * @throws OperationFailureException
     * @throws InvalidUsageException
     */
    public RFIDReader listenToEvents(RfidEventsListener eventListener) throws CodeErrorException, OperationFailureException, InvalidUsageException {
        RFIDReader reader = connect();
        if (this.eventListener != null) {
            reader.Events.removeEventsListener(this.eventListener);
        }
        this.eventListener = eventListener;
        reader.Events.addEventsListener(eventListener);
        ConfigureReader();
        return reader;
    }

    public void stopListenToEvents() throws OperationFailureException, InvalidUsageException {
        if (this.reader != null && this.eventListener != null) {
            reader.Events.removeEventsListener(this.eventListener);
            this.eventListener = null;
        }
    }

    synchronized void performInventory() throws OperationFailureException, InvalidUsageException, CodeErrorException {
        RFIDReader reader = connect();
        reader.Events.setTagReadEvent(true);
        reader.Actions.Inventory.perform();
    }

    synchronized void stopInventory() throws OperationFailureException, InvalidUsageException {
        if (reader != null) {
            reader.Actions.Inventory.stop();
        }
    }

    @Nullable
    synchronized TagData read(String tagID, ReadAccessParams readAccessParams) throws OperationFailureException, InvalidUsageException, CodeErrorException {
        RFIDReader reader = connect();
        return reader.Actions.TagAccess.readWait(
                tagID,
                readAccessParams,
                null
        );
    }

    synchronized void write(@Nullable String tagId, @NonNull TagAccess.WriteAccessParams params) throws OperationFailureException, InvalidUsageException, CodeErrorException {
        RFIDReader reader = connect();
        TagData tagData = null; // response ?
        reader.Actions.TagAccess.writeWait(
                tagId,
                params,
                null,
                tagData
        );
    }

    synchronized void lock(@Nullable String tagId, @NonNull TagAccess.LockAccessParams params) throws OperationFailureException, InvalidUsageException, CodeErrorException {
        RFIDReader reader = connect();
        reader.Actions.TagAccess.lockWait(
                tagId,
                params,
                null
        );
    }


    synchronized void writeAccessPassword(@Nullable String tagId, @NonNull TagAccess.WriteSpecificFieldAccessParams params) throws OperationFailureException, InvalidUsageException {
        if (reader == null) {
            return;
        }
        reader.Actions.TagAccess.writeAccessPasswordWait(
                tagId,
                params,
                null
        );
    }

    synchronized void writeKillPassword(@Nullable String tagId, @NonNull TagAccess.WriteSpecificFieldAccessParams params) throws OperationFailureException, InvalidUsageException {
        if (reader == null) {
            return;
        }
        reader.Actions.TagAccess.writeKillPasswordWait(
                tagId,
                params,
                null
        );
    }

    synchronized public void blockPermaLock(String tagId, TagAccess.BlockPermalockAccessParams params) throws OperationFailureException, InvalidUsageException, CodeErrorException {
        RFIDReader reader = connect();
        reader.Actions.TagAccess.blockPermalockWait(
                tagId,
                params,
                null
        );
    }

    private void ConfigureReader() throws OperationFailureException, InvalidUsageException {
        Log.d(TAG, "ConfigureReader " + reader.getHostName());
        IRFIDLogger.getLogger("SDKSAmpleApp").EnableDebugLogs(true);
        TriggerInfo triggerInfo = new TriggerInfo();
        triggerInfo.StartTrigger.setTriggerType(START_TRIGGER_TYPE.START_TRIGGER_TYPE_IMMEDIATE);
        triggerInfo.StopTrigger.setTriggerType(STOP_TRIGGER_TYPE.STOP_TRIGGER_TYPE_IMMEDIATE);
        // HH event
        reader.Events.setHandheldEvent(true);
        // tag event with tag data
        reader.Events.setTagReadEvent(true);
        reader.Events.setAttachTagDataWithReadEvent(false);
    }

    public void performInventoryIfNeeded() throws CodeErrorException, InvalidUsageException, OperationFailureException {
        try {
            this.performInventory();
        }
        catch (OperationFailureException ex){
            if (ex.getStatusDescription().equals("RFID_INVENTORY_IN_PROGRESS")) {
                return;
            }
            else {
                throw ex;
            }
        }
    }

    @NonNull
    public TagData[] getTags() throws CodeErrorException, OperationFailureException, InvalidUsageException {
        if (reader == null) {
            return new TagData[0];
        }
        TagData[] tags  = reader.Actions.getReadTags(10000);
        if (tags == null) {
            return new TagData[0];
        }
        return tags;
    }
}
