package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble;

import java.util.UUID;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import android.os.AsyncTask;
import android.os.SystemClock;

import info.nightscout.androidaps.logging.L;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.GattAttributes;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkEncodingType;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.operations.BLECommOperationResult;
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
import info.nightscout.androidaps.plugins.pump.common.utils.ThreadUtil;

/**
 * Created by geoff on 5/26/16.
 */
public class RFSpyReader {

    private static final Logger LOG = LoggerFactory.getLogger(L.PUMPBTCOMM);
    private static AsyncTask<Void, Void, Void> readerTask;
    private RileyLinkBLE rileyLinkBle;
    private Semaphore waitForRadioData = new Semaphore(0, true);
    private LinkedBlockingQueue<byte[]> mDataQueue = new LinkedBlockingQueue<>();
    private int acquireCount = 0;
    private int releaseCount = 0;
    private boolean stopAtNull = true;


    public RFSpyReader(RileyLinkBLE rileyLinkBle) {
        this.rileyLinkBle = rileyLinkBle;
    }


    public void init(RileyLinkBLE rileyLinkBLE) {
        this.rileyLinkBle = rileyLinkBLE;
    }


    public void setRileyLinkBle(RileyLinkBLE rileyLinkBle) {
        if (readerTask != null) {
            readerTask.cancel(true);
        }
        this.rileyLinkBle = rileyLinkBle;
    }

    public void setRileyLinkEncodingType(RileyLinkEncodingType encodingType) {
        stopAtNull = !(encodingType == RileyLinkEncodingType.Manchester || //
                encodingType == RileyLinkEncodingType.FourByteSixByteRileyLink);
    }


    // This timeout must be coordinated with the length of the RFSpy radio operation or Bad Things Happen.
    public byte[] poll(int timeout_ms) {
        if (isLogEnabled())
            LOG.trace(ThreadUtil.sig() + "Entering poll at t==" + SystemClock.uptimeMillis() + ", timeout is " + timeout_ms
                + " mDataQueue size is " + mDataQueue.size());

        if (mDataQueue.isEmpty())
            try {
                // block until timeout or data available.
                // returns null if timeout.
                byte[] dataFromQueue = mDataQueue.poll(timeout_ms, TimeUnit.MILLISECONDS);
                if (dataFromQueue != null) {
                    if (isLogEnabled())
                        LOG.debug("Got data [" + ByteUtil.shortHexString(dataFromQueue) + "] at t=="
                            + SystemClock.uptimeMillis());
                } else {
                    if (isLogEnabled())
                        LOG.debug("Got data [null] at t==" + SystemClock.uptimeMillis());
                }
                return dataFromQueue;
            } catch (InterruptedException e) {
                LOG.error("poll: Interrupted waiting for data");
            }
        return null;
    }


    // Call this from the "response count" notification handler.
    public void newDataIsAvailable() {
        releaseCount++;

        if (isLogEnabled())
            LOG.trace(ThreadUtil.sig() + "waitForRadioData released(count=" + releaseCount + ") at t="
                + SystemClock.uptimeMillis());
        waitForRadioData.release();
    }


    public void start() {
        readerTask = new AsyncTask<Void, Void, Void>() {

            @Override
            protected Void doInBackground(Void... voids) {
                UUID serviceUUID = UUID.fromString(GattAttributes.SERVICE_RADIO);
                UUID radioDataUUID = UUID.fromString(GattAttributes.CHARA_RADIO_DATA);
                BLECommOperationResult result;
                while (true) {
                    try {
                        acquireCount++;
                        waitForRadioData.acquire();
                        if (isLogEnabled())
                            LOG.trace(ThreadUtil.sig() + "waitForRadioData acquired (count=" + acquireCount + ") at t="
                                + SystemClock.uptimeMillis());
                        SystemClock.sleep(100);
                        SystemClock.sleep(1);
                        result = rileyLinkBle.readCharacteristic_blocking(serviceUUID, radioDataUUID);
                        SystemClock.sleep(100);

                        if (result.resultCode == BLECommOperationResult.RESULT_SUCCESS) {
                            if (stopAtNull) {
                                // only data up to the first null is valid
                                for (int i = 0; i < result.value.length; i++) {
                                    if (result.value[i] == 0) {
                                        result.value = ByteUtil.substring(result.value, 0, i);
                                        break;
                                    }
                                }
                            }
                            mDataQueue.add(result.value);
                        } else if (result.resultCode == BLECommOperationResult.RESULT_INTERRUPTED) {
                            LOG.error("Read operation was interrupted");
                        } else if (result.resultCode == BLECommOperationResult.RESULT_TIMEOUT) {
                            LOG.error("Read operation on Radio Data timed out");
                        } else if (result.resultCode == BLECommOperationResult.RESULT_BUSY) {
                            LOG.error("FAIL: RileyLinkBLE reports operation already in progress");
                        } else if (result.resultCode == BLECommOperationResult.RESULT_NONE) {
                            LOG.error("FAIL: got invalid result code: " + result.resultCode);
                        }
                    } catch (InterruptedException e) {
                        LOG.error("Interrupted while waiting for data");
                    }
                }
            }
        }.execute();
    }

    private boolean isLogEnabled() {
        return L.isEnabled(L.PUMPBTCOMM);
    }

}