package com.dosse.airpods.pods;

import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanResult;
import android.os.SystemClock;

import com.dosse.airpods.utils.Logger;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

/**
 * TODO: proper javadoc
 * On a normal OS, we would use the bluetooth address of the device to filter out beacons from other devices.
 * UNFORTUNATELY, someone at google was so concerned about privacy (yea, as if they give a shit) that he decided it was a good idea to not allow access to the bluetooth address of incoming BLE beacons.
 * As a result, we have no reliable way to make sure that the beacon comes from YOUR airpods and not the guy sitting next to you on the bus.
 * What we did to workaround this issue is this:
 * - When a beacon arrives that looks like a pair of AirPods, look at the other beacons received in the last 10 seconds and get the strongest one
 * - If the strongest beacon's fake address is the same as this, use this beacon; otherwise use the strongest beacon
 * - Filter for signals stronger than -60db
 * - Decode...
 */
public abstract class PodsStatusScanCallback extends ScanCallback {
    private static final long RECENT_BEACONS_MAX_T_NS = 10000000000L; //10s

    private static final int AIRPODS_MANUFACTURER = 76;
    private static final int AIRPODS_DATA_LENGTH = 27;
    private static final int MIN_RSSI = -60;

    private final List<ScanResult> mRecentBeacons = new ArrayList<>();

    public abstract void onStatus(PodsStatus status);

    public static List<ScanFilter> getScanFilters() {
        byte[] manufacturerData = new byte[AIRPODS_DATA_LENGTH];
        byte[] manufacturerDataMask = new byte[AIRPODS_DATA_LENGTH];

        manufacturerData[0] = 7;
        manufacturerData[1] = 25;

        manufacturerDataMask[0] = -1;
        manufacturerDataMask[1] = -1;

        ScanFilter.Builder builder = new ScanFilter.Builder();
        builder.setManufacturerData(AIRPODS_MANUFACTURER, manufacturerData, manufacturerDataMask);

        return Collections.singletonList(builder.build());
    }

    @Override
    public void onBatchScanResults(List<ScanResult> scanResults) {
        for (ScanResult result : scanResults) {
            onScanResult(-1, result);
        }

        super.onBatchScanResults(scanResults);
    }

    @Override
    public void onScanResult(int callbackType, ScanResult result) {
        try {
            if (!isAirpodsResult(result)) {
                return;
            }

            result.getDevice().getAddress();

            Logger.debug(result.getRssi() + "db");
            Logger.debug(decodeResult(result));

            result = getBestResult(result);
            if (result == null || result.getRssi() < MIN_RSSI) {
                return;
            }

            PodsStatus status = new PodsStatus(decodeResult(result));
            onStatus(status);
        } catch (Throwable t) {
            Logger.error(t);
        }
    }

    private ScanResult getBestResult(ScanResult result) {
        mRecentBeacons.add(result);
        ScanResult strongestBeacon = null;

        for (int i = 0; i < mRecentBeacons.size(); i++) {
            if (SystemClock.elapsedRealtimeNanos() - mRecentBeacons.get(i).getTimestampNanos() > RECENT_BEACONS_MAX_T_NS) {
                mRecentBeacons.remove(i--);
                continue;
            }

            if (strongestBeacon == null || strongestBeacon.getRssi() < mRecentBeacons.get(i).getRssi()) {
                strongestBeacon = mRecentBeacons.get(i);
            }
        }

        if (strongestBeacon != null && Objects.equals(strongestBeacon.getDevice().getAddress(), result.getDevice().getAddress())) {
            strongestBeacon = result;
        }

        return strongestBeacon;
    }

    private static boolean isAirpodsResult(ScanResult result) {
        return result != null
                && result.getScanRecord() != null
                && isDataValid(result.getScanRecord().getManufacturerSpecificData(AIRPODS_MANUFACTURER));
    }

    private static boolean isDataValid(byte[] data) {
        return data != null && data.length == AIRPODS_DATA_LENGTH;
    }

    private static String decodeResult(ScanResult result) {
        if (result != null && result.getScanRecord() != null) {
            byte[] data = result.getScanRecord().getManufacturerSpecificData(AIRPODS_MANUFACTURER);
            if (isDataValid(data)) {
                return decodeHex(data);
            }
        }

        return null;
    }

    private static String decodeHex(byte[] byteArray) {
        StringBuilder ret = new StringBuilder();

        for (byte b : byteArray) {
            ret.append(String.format("%02X", b));
        }

        return ret.toString();
    }
}