/*  Copyright (C) 2022-2024 Daniel Dakhno

    This file is part of Gadgetbridge.

    Gadgetbridge is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published
    by the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Gadgetbridge 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 Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor;

import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.widget.Toast;

import androidx.localbroadcastmanager.content.LocalBroadcastManager;

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

import java.nio.ByteBuffer;
import java.util.UUID;

import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
import nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.constants.MessageId;
import nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.constants.ParameterId;
import nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.constants.ReportState;
import nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.constants.SensorType;
import nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.message.GetSensorRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.message.Response;
import nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.message.SetSensorRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.parameter.Parameter;
import nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.parameter.SensorState;
import nodomain.freeyourgadget.gadgetbridge.util.GB;

public class BinarySensorSupport extends BinarySensorBaseSupport {
    final public static String BINARY_SENSOR_SERVICE_UUID = "0000183b-0000-1000-8000-00805f9b34fb";
    final public static String BINARY_SENSOR_CONTROL_CHARACTERISTIC_UUID = "00002b2b-0000-1000-8000-00805f9b34fb";
    final public static String BINARY_SENSOR_RESPONSE_CHARACTERISTIC_UUID = "00002b2c-0000-1000-8000-00805f9b34fb";

    final public static String ACTION_SENSOR_STATE_CHANGED = "nodomain.freeyourgadget.gadgetbridge.binary_sensor.STATE_CHANGED";
    final public static String ACTION_SENSOR_STATE_REQUEST = "nodomain.freeyourgadget.gadgetbridge.binary_sensor.STATE_REQUEST";

    private static final Logger logger = LoggerFactory.getLogger(BinarySensorSupport.class);

    private nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.constants.SensorState sensorState = null;
    private int sensorCount = -1;

    public BinarySensorSupport() {
        super(logger);
        addSupportedService(UUID.fromString(BINARY_SENSOR_SERVICE_UUID));

        LocalBroadcastManager.getInstance(getContext())
                .registerReceiver(
                        stateRequestReceiver,
                        new IntentFilter(ACTION_SENSOR_STATE_REQUEST)
                );
    }

    @Override
    public void dispose() {
        super.dispose();

        LocalBroadcastManager.getInstance(getContext())
                .unregisterReceiver(stateRequestReceiver);
    }

    BroadcastReceiver stateRequestReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            sendStateChangeIntent(false);
        }
    };

    @Override
    public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
        if (characteristic.getUuid().toString().equals(BINARY_SENSOR_RESPONSE_CHARACTERISTIC_UUID)) {
            handleResponseValue(characteristic.getValue());
            return true;
        }

        return false;
    }

    Response decodeResponse(byte[] value) {
        ByteBuffer buffer = ByteBuffer.wrap(value);
        buffer.get(); // split packet header
        buffer.get(); // RFU
        byte messageIdByte = buffer.get();
        buffer.get(); // RFU
        int parameterCount = buffer.get();

        Parameter[] parameters = new Parameter[parameterCount];

        MessageId messageId = MessageId.fromMessageIdByte(messageIdByte);
        for (int i = 0; i < parameterCount; i++) {
            byte parameterIdByte = buffer.get();
            byte payloadLength = buffer.get();
            buffer.get(); // RFU
            buffer.get(); // RFU

            ParameterId parameterId = ParameterId.fromParameterIdByte(parameterIdByte);

            byte[] payload = new byte[payloadLength];
            buffer.get(payload);

            parameters[i] = Parameter.decode(parameterId, payload);
        }

        return new Response(
                messageId,
                parameters
        );
    }

    void sendStateChangeIntent(boolean sendGlobally){
        Intent intent = new Intent(ACTION_SENSOR_STATE_CHANGED);

        intent.putExtra("EXTRA_SENSOR_CLOSED", sensorState == nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.protocol.constants.SensorState.SENSOR_STATE_CLOSED);
        intent.putExtra("EXTRA_SENSOR_COUNT", sensorCount);

        LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
        if(sendGlobally) {
            getContext().sendBroadcast(intent);
        }
    }

    void handleResponseValue(byte[] value) {
        Response response = decodeResponse(value);

        for (Parameter parameter : response.getParameters()) {
            if (parameter instanceof SensorState) {
                if(getDevice().getState() != GBDevice.State.INITIALIZED){
                    new TransactionBuilder("set device state")
                            .add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()))
                            .queue(getQueue());
                }

                SensorState stateParameter = (SensorState) parameter;
                logger.debug("sensor state: " + stateParameter.getSensorState() + "  count: " + stateParameter.getCount());

                this.sensorState = stateParameter.getSensorState();
                this.sensorCount = stateParameter.getCount();

                sendStateChangeIntent(true);
            }
        }
    }

    private void sendPacketToDevice(byte[] data, TransactionBuilder builder) {
        byte[] fullData = new byte[data.length + 1];
        fullData[0] = 0x00;
        System.arraycopy(data, 0, fullData, 1, data.length);

        builder.write(getCharacteristic(UUID.fromString(BINARY_SENSOR_CONTROL_CHARACTERISTIC_UUID)), fullData);
    }

    private void sendPacketToDevice(byte[] data) {
        TransactionBuilder builder = new TransactionBuilder("BSS control");
        sendPacketToDevice(data, builder);
        builder.queue(getQueue());
    }

    @Override
    public boolean onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
        if (status != BluetoothGatt.GATT_SUCCESS) {
            GB.toast("error setting indication", Toast.LENGTH_LONG, GB.ERROR);
        }
        return true;
    }

    @Override
    protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
        logger.debug("initializing device");

        builder
                .add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()))
                .notify(getCharacteristic(UUID.fromString(BINARY_SENSOR_RESPONSE_CHARACTERISTIC_UUID)), true)
        ;

        SetSensorRequest setSensorRequest = new SetSensorRequest(SensorType.SENSOR_TYPE_OPENING_CLOSING, ReportState.REPORT_STATUS_ENABLED);
        GetSensorRequest getSensorRequest = new GetSensorRequest(SensorType.SENSOR_TYPE_OPENING_CLOSING);

        sendPacketToDevice(getSensorRequest.encode(), builder);
        sendPacketToDevice(setSensorRequest.encode(), builder);

        return builder;
    }
}
