/* Copyright (C) 2018 Erik Johansson <[email protected]>
 *
 *    This program is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation, either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>
 */

package com.health.openscale.core.bluetooth;

import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.content.Context;

import com.welie.blessed.BluetoothPeripheral;

import java.util.HashMap;

import timber.log.Timber;

public class BluetoothDebug extends BluetoothCommunication {
    HashMap<Integer, String> propertyString;

    BluetoothDebug(Context context) {
        super(context);

        propertyString = new HashMap<>();
        propertyString.put(BluetoothGattCharacteristic.PROPERTY_BROADCAST, "BROADCAST");
        propertyString.put(BluetoothGattCharacteristic.PROPERTY_READ, "READ");
        propertyString.put(BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE, "WRITE_NO_RESPONSE");
        propertyString.put(BluetoothGattCharacteristic.PROPERTY_WRITE, "WRITE");
        propertyString.put(BluetoothGattCharacteristic.PROPERTY_NOTIFY, "NOTIFY");
        propertyString.put(BluetoothGattCharacteristic.PROPERTY_INDICATE, "INDICATE");
        propertyString.put(BluetoothGattCharacteristic.PROPERTY_SIGNED_WRITE, "SIGNED_WRITE");
        propertyString.put(BluetoothGattCharacteristic.PROPERTY_EXTENDED_PROPS, "EXTENDED_PROPS");
    }

    @Override
    public String driverName() {
        return "Debug";
    }

    private boolean isBlacklisted(BluetoothGattService service, BluetoothGattCharacteristic characteristic) {
        // Reading this triggers a pairing request on Beurer BF710
        if (service.getUuid().equals(BluetoothGattUuid.fromShortCode(0xffe0))
                && characteristic.getUuid().equals(BluetoothGattUuid.fromShortCode(0xffe5))) {
            return true;
        }

        return false;
    }

    private boolean isWriteType(int property, int writeType) {
        switch (property) {
            case BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE:
                return writeType == BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE;
            case BluetoothGattCharacteristic.PROPERTY_WRITE:
                return writeType == BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT;
            case BluetoothGattCharacteristic.PROPERTY_SIGNED_WRITE:
                return writeType == BluetoothGattCharacteristic.WRITE_TYPE_SIGNED;
        }
        return false;
    }

    private String propertiesToString(int properties, int writeType) {
        StringBuilder names = new StringBuilder();
        for (int property : propertyString.keySet()) {
            if ((properties & property) != 0) {
                names.append(propertyString.get(property));
                if (isWriteType(property, writeType)) {
                    names.append('*');
                }
                names.append(", ");
            }
        }

        if (names.length() == 0) {
            return "<none>";
        }

        return names.substring(0, names.length() - 2);
    }

    private String permissionsToString(int permissions) {
        if (permissions == 0) {
            return "";
        }
        return String.format(" (permissions=0x%x)", permissions);
    }

    private String byteToString(byte[] value) {
        return new String(value).replaceAll("\\p{Cntrl}", "?");
    }

    private void logService(BluetoothGattService service, boolean included) {
        Timber.d("Service %s%s", BluetoothGattUuid.prettyPrint(service.getUuid()),
                included ? " (included)" : "");

        for (BluetoothGattCharacteristic characteristic : service.getCharacteristics()) {
            Timber.d("|- characteristic %s (#%d): %s%s",
                    BluetoothGattUuid.prettyPrint(characteristic.getUuid()),
                    characteristic.getInstanceId(),
                    propertiesToString(characteristic.getProperties(), characteristic.getWriteType()),
                    permissionsToString(characteristic.getPermissions()));
            byte[] value = characteristic.getValue();
            if (value != null && value.length > 0) {
                Timber.d("|--> value: %s (%s)", byteInHex(value), byteToString(value));
            }

            for (BluetoothGattDescriptor descriptor : characteristic.getDescriptors()) {
                Timber.d("|--- descriptor %s%s",
                        BluetoothGattUuid.prettyPrint(descriptor.getUuid()),
                        permissionsToString(descriptor.getPermissions()));

                value = descriptor.getValue();
                if (value != null && value.length > 0) {
                    Timber.d("|-----> value: %s (%s)", byteInHex(value), byteToString(value));
                }
            }
        }

        for (BluetoothGattService includedService : service.getIncludedServices()) {
            logService(includedService, true);
        }
    }

    private int readServiceCharacteristics(BluetoothGattService service, int offset) {
        for (BluetoothGattCharacteristic characteristic : service.getCharacteristics()) {
            if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_READ) != 0
                    && !isBlacklisted(service, characteristic)) {

                if (offset == 0) {
                    readBytes(service.getUuid(), characteristic.getUuid());
                    return -1;
                }

                offset -= 1;
            }

            for (BluetoothGattDescriptor descriptor : characteristic.getDescriptors()) {
                if (offset == 0) {
                    readBytes(service.getUuid(), characteristic.getUuid());
                    return -1;
                }

                offset -= 1;
            }
        }

        for (BluetoothGattService included : service.getIncludedServices()) {
            offset = readServiceCharacteristics(included, offset);
            if (offset == -1) {
                return offset;
            }
        }

        return offset;
    }

    @Override
    protected void onBluetoothDiscovery(BluetoothPeripheral peripheral) {
        int offset = 0;

        for (BluetoothGattService service : peripheral.getServices()) {
            offset = readServiceCharacteristics(service, offset);
        }

        for (BluetoothGattService service : peripheral.getServices()) {
            logService(service, false);
        }

        setBluetoothStatus(BT_STATUS.CONNECTION_LOST);
        disconnect();
    }


    @Override
    protected boolean onNextStep(int stateNr) {
        return false;
    }

}