package com.iotize.plugin.cordova.zebrarfid;

import android.content.Context;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

// import com.getcapacitor.PluginMethod;
import com.zebra.rfid.api3.IEvents;
import com.zebra.rfid.api3.InvalidUsageException;
import com.zebra.rfid.api3.LOCK_DATA_FIELD;
import com.zebra.rfid.api3.LOCK_PRIVILEGE;
import com.zebra.rfid.api3.MEMORY_BANK;
import com.zebra.rfid.api3.OperationFailureException;
import com.zebra.rfid.api3.RFIDReader;
import com.zebra.rfid.api3.RfidEventsListener;
import com.zebra.rfid.api3.RfidReadEvents;
import com.zebra.rfid.api3.RfidStatusEvents;
import com.zebra.rfid.api3.TagAccess;
import com.zebra.rfid.api3.TagData;

import org.apache.cordova.PluginResult;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;

public class ZebraRfidAdapter {

    private static final String TAG = "ZebraRfidAdapter";

    @NonNull
    private final ZebraRfidImpl implementation;

    @NonNull
    private final PluginTaskHelper task = new PluginTaskHelper();

    @Nullable
    private RfidEventsListenerPluginCall eventListener;

    ZebraRfidAdapter(Context context) {
        this.implementation = new ZebraRfidImpl(context);
    }

    public void connect(PluginResponse pluginResponse) {
        this.task.async(new AsyncJSONObjectPluginCallCallback(pluginResponse) {
            @Override
            public JSONObject doInBackground() throws Throwable {
                 implementation.connect();
                 return new JSONObject();
            }
        });
    }

    public void disconnect(PluginResponse pluginResponse) {
        this.task.async(new AsyncJSONObjectPluginCallCallback(pluginResponse) {
            @Override
            public JSONObject doInBackground() throws Throwable {
                implementation.disconnect();
                return new JSONObject();
            }
        });
    }

    public void scan(PluginResponse pluginResponse) throws OperationFailureException, InvalidUsageException, CodeErrorException {
        this._cleanupEventListenerIfNeeded();
        RfidEventsListenerPluginCall eventListener = new RfidEventsListenerPluginCall(pluginResponse);
        RFIDReader reader = this.implementation.listenToEvents(eventListener);

        reader.Events.setAttachTagDataWithReadEvent(true);
        reader.Events.setTagReadEvent(true);

        this.eventListener = eventListener;
        this.implementation.performInventoryIfNeeded();
    }


    // @PluginMethod()
    public void stopScan(PluginResponse pluginResponse) throws OperationFailureException, InvalidUsageException {
        this.task.async(new AsyncJSONObjectPluginCallCallback(pluginResponse) {
            @Override
            public JSONObject doInBackground() throws Throwable {
                _cleanupEventListenerIfNeeded();
                implementation.stopInventory();
                return new JSONObject();
            }
        });
    }

    private void _cleanupEventListenerIfNeeded() throws OperationFailureException, InvalidUsageException {
        if (this.eventListener != null) {
            this.implementation.stopListenToEvents();
            this.eventListener.call.success();
            this.eventListener = null;
        }
    }

    public void read(PluginResponse pluginResponse, String id, Integer offset, Integer count, MEMORY_BANK memoryBank, Long password) throws PluginError, OperationFailureException, InvalidUsageException {
        this.task.async(new AsyncPluginCallCallback(pluginResponse) {
            @Override
            public PluginResult doInBackground() throws Throwable {
                TagAccess.ReadAccessParams params = new TagAccess().new ReadAccessParams();
                params.setMemoryBank(memoryBank);
                int offsetInt = offset == null ? 0 : offset;
                params.setOffset(offsetInt);
                int countInt = count == null ? 1 : count;
                params.setCount(countInt);
                params.setAccessPassword(password);
                Log.d(TAG, "read memory bank " + memoryBank.toString() + " ["+offsetInt+";" +(offsetInt+countInt-1)+"]");
                TagData res = implementation.read(id, params);
                if (res != null) {
                    return new PluginResult(PluginResult.Status.OK, res.getMemoryBankData());
                } else {
                    throw CodeErrorException.noRFIDReader();
                }
            }
        });
    }

    public void write(PluginResponse pluginResponse, String id, String data, Integer offset, MEMORY_BANK memoryBank, Integer writeRetries, Long password) throws CodeErrorException, OperationFailureException, InvalidUsageException {
        this.task.async(new AsyncJSONObjectPluginCallCallback(pluginResponse) {
            @Override
            public JSONObject doInBackground() throws Throwable {
                String dataWithPadding = DataConverter.getHexStringWithPadding(data);
                TagAccess.WriteAccessParams params = new TagAccess().new WriteAccessParams();
                params.setMemoryBank(memoryBank);
                params.setOffset(offset == null ? 0 : offset);
                params.setWriteData(dataWithPadding);
                params.setWriteDataLength(dataWithPadding.length() / 4);
                params.setAccessPassword(password);
                params.setWriteRetries(writeRetries == null ? 1 : writeRetries);
                implementation.write(id, params);
                return new JSONObject();
            }
        });
    }

    public void writeAccessPassword(PluginResponse pluginResponse, String tagId, Long accessPassword, String newAccessPassword) throws Exception {
        this.write(
                pluginResponse,
                tagId,
                newAccessPassword,
                2,
                MEMORY_BANK.MEMORY_BANK_RESERVED,
                2,
                accessPassword
        );
    }

    public void writeKillPassword(PluginResponse pluginResponse, String tagId, Long accessPassword, String newKillPassword) throws Exception {
        if (newKillPassword.length() > 8) {
            newKillPassword = newKillPassword.substring(0, 8);
        }
        this.write(
                pluginResponse,
                tagId,
                newKillPassword,
                0,
                MEMORY_BANK.MEMORY_BANK_RESERVED,
                2,
                accessPassword
        );
    }

    public void writeLockSettings(PluginResponse pluginResponse, String id, LOCK_DATA_FIELD dataField, LOCK_PRIVILEGE privilege, Long password) throws CodeErrorException, OperationFailureException, InvalidUsageException {
        this.task.async(new AsyncJSONObjectPluginCallCallback(pluginResponse) {
            @Override
            public JSONObject doInBackground() throws Throwable {
                TagAccess.LockAccessParams params = new TagAccess().new LockAccessParams();
                params.setAccessPassword(password);
                params.setLockPrivilege(
                    dataField,
                    privilege
                );
                implementation.lock(id,params);
                return new JSONObject();
            }
        });
    }

    public void startScan(PluginResponse pluginResponse) throws CodeErrorException, OperationFailureException, InvalidUsageException {
        this.task.async(new AsyncJSONObjectPluginCallCallback(pluginResponse) {
            @Override
            public JSONObject doInBackground() throws Throwable {
                implementation.performInventoryIfNeeded();
                return new JSONObject();
            }
        });
    }

    public void getTags(PluginResponse pluginResponse) throws CodeErrorException, OperationFailureException, InvalidUsageException, JSONException {
        TagData[] tags = this.implementation.getTags();
        HashMap<String, TagData> uniqTags = new HashMap<>();
        for (TagData tag: tags) {
            if (!uniqTags.containsKey(tag.getTagID())) {
                uniqTags.put(tag.getTagID(), tag);
            }
        }
        pluginResponse.success(DataConverter.toJSArray(uniqTags.values()));
    }


    private class RfidEventsListenerPluginCall implements RfidEventsListener {
        private final PluginResponse call;

        public RfidEventsListenerPluginCall(@NonNull final PluginResponse call) {
            this.call = call;
        }

        @Override
        public void eventReadNotify(RfidReadEvents rfidReadEvents) {
            try {
                Log.d(TAG, "eventReadNotify: " + rfidReadEvents.toString());
                final IEvents.ReadEventData readEventData = rfidReadEvents.getReadEventData();
                if (readEventData != null) {
                    JSONObject result = new JSONObject();
                    final TagData tagData = rfidReadEvents.getReadEventData().tagData;
                    result.put("tag", DataConverter.toJSONObject(tagData));
                    call.newResult(result);
                }
            } catch (Exception e) {
                Log.e(TAG, e.getMessage(), e);
            }
        }

        @Override
        public void eventStatusNotify(RfidStatusEvents rfidStatusEvents) {
            Log.d(TAG, "eventReadNotify: " + rfidStatusEvents.toString());
        }
    }
}
