/*  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.flipper.zero.support;

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

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.UUID;

import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic;
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.IntentListener;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery.BatteryInfo;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery.BatteryInfoProfile;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.protobuf.messagefields.MessageField;
import nodomain.freeyourgadget.gadgetbridge.util.protobuf.messagefields.NestedMessageField;
import nodomain.freeyourgadget.gadgetbridge.util.protobuf.messagefields.RootMessageField;
import nodomain.freeyourgadget.gadgetbridge.util.protobuf.messagefields.StringMessageField;
import nodomain.freeyourgadget.gadgetbridge.util.protobuf.messagefields.VarintMessageField;

public class FlipperZeroSupport extends FlipperZeroBaseSupport{
    private BatteryInfoProfile batteryInfoProfile = new BatteryInfoProfile(this);

    private final String UUID_SERIAL_SERVICE = "8fe5b3d5-2e7f-4a98-2a48-7acc60fe0000";
    private final String UUID_SERIAL_CHARACTERISTIC_WRITE = "19ed82ae-ed21-4c9d-4145-228e62fe0000";
    private final String UUID_SERIAL_CHARACTERISTIC_RESPONSE = "19ed82ae-ed21-4c9d-4145-228e61fe0000";

    private final String COMMAND_PLAY_FILE = "nodomain.freeyourgadget.gadgetbridge.flipper.zero.PLAY_FILE";
    private final String ACTION_PLAY_DONE = "nodomain.freeyourgadget.gadgetbridge.flipper.zero.PLAY_DONE";

    private final int REQUEST_ID_OPEN_APP = 16;
    private final int REQUEST_ID_EXIT_APP = 47;
    private final int REQUEST_ID_LOAD_FILE = 48;
    private final int REQUEST_ID_PRESS_BUTTON = 49;
    private final int REQUEST_ID_RELEASE_BUTTON = 50;

    private int messageId = 0;

    BroadcastReceiver receiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, final Intent intent) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    if(COMMAND_PLAY_FILE.equals(intent.getAction())){
                        handlePlaySubGHZ(intent);
                    }
                }
            }).start();
        }
    };
    boolean recevierRegistered = false;

    private void handlePlaySubGHZ(Intent intent) {
        String appName = intent.getExtras().getString("EXTRA_APP_NAME", "Sub-GHz");

        String filePath = intent.getStringExtra("EXTRA_FILE_PATH");
        if(filePath == null){
            GB.log("missing EXTRA_FILE_PATH in intent", GB.ERROR, null);
            return;
        }
        if(filePath.isEmpty()){
            GB.log("empty EXTRA_FILE_PATH in intent", GB.ERROR, null);
            return;
        }

        String buttonName = intent.getExtras().getString("EXTRA_BUTTON_NAME", "center");

        long millis = intent.getExtras().getInt("EXTRA_DURATION", 1000);

        GB.toast(String.format("playing %s file", appName), Toast.LENGTH_SHORT, GB.INFO);
        playFile(appName, filePath, buttonName, millis);

        Intent response = new Intent(ACTION_PLAY_DONE);
        getContext().sendBroadcast(response);
    }

    public FlipperZeroSupport() {
        super();

        batteryInfoProfile.addListener(new IntentListener() {
            @Override
            public void notify(Intent intent) {
                BatteryInfo info = intent.getParcelableExtra(BatteryInfoProfile.EXTRA_BATTERY_INFO);
                GBDeviceEventBatteryInfo batteryEvent = new GBDeviceEventBatteryInfo();
                batteryEvent.state = BatteryState.BATTERY_NORMAL;
                batteryEvent.level = info.getPercentCharged();
                evaluateGBDeviceEvent(batteryEvent);
            }
        });
        addSupportedService(GattService.UUID_SERVICE_BATTERY_SERVICE);
        addSupportedService(GattService.UUID_SERVICE_DEVICE_INFORMATION);
        addSupportedProfile(batteryInfoProfile);

        addSupportedService(UUID.fromString(UUID_SERIAL_SERVICE));
    }

    @Override
    protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
        if(!recevierRegistered) {
            getContext().registerReceiver(receiver, new IntentFilter(COMMAND_PLAY_FILE));
            recevierRegistered = true;
        }

        builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
        builder.read(getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_FIRMWARE_REVISION_STRING));

        batteryInfoProfile.requestBatteryInfo(builder);
        batteryInfoProfile.enableNotify(builder, true);

        return builder
                .notify(getCharacteristic(UUID.fromString(UUID_SERIAL_CHARACTERISTIC_RESPONSE)), true)
                .requestMtu(512)
                .add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
    }

    @Override
    public boolean onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
        if(characteristic.getUuid().equals(GattCharacteristic.UUID_CHARACTERISTIC_FIRMWARE_REVISION_STRING)){
            String revision = characteristic.getStringValue(0);
            getDevice().setFirmwareVersion(revision);
            getDevice().sendDeviceUpdateIntent(getContext());
        }
        return super.onCharacteristicRead(gatt, characteristic, status);
    }

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

        if(recevierRegistered) {
            getContext().unregisterReceiver(receiver);
            recevierRegistered = false;
        }
    }

    private void sendSerialData(byte[] data){
        new TransactionBuilder("send serial data")
                .write(getCharacteristic(UUID.fromString(UUID_SERIAL_CHARACTERISTIC_WRITE)), data)
                .queue(getQueue());
    }

    private RootMessageField createMainRequest(int requestFieldNumber, MessageField... children){
        return new RootMessageField(
                new VarintMessageField(1, messageId++),
                new NestedMessageField(requestFieldNumber, children)
        );
    }

    private void sendMainRequest(int requestFieldNumber, MessageField... children) throws IOException {
        RootMessageField root = createMainRequest(requestFieldNumber, children);
        sendSerialData(root.encodeToBytes());
    }

    private void openApp(String appName) throws IOException {
        sendMainRequest(
                REQUEST_ID_OPEN_APP,
                new StringMessageField(1, appName),
                new StringMessageField(2, "RPC")
        );
    }

    private void openSubGhzApp() throws IOException {
        openApp("Sub-GHz");
    }

    private void appLoadFile(String filePath) throws IOException {
        sendMainRequest(
                REQUEST_ID_LOAD_FILE,
                new StringMessageField(1, filePath)
        );
    }

    private void appButtonPress(String button) throws IOException {
        sendMainRequest(
                REQUEST_ID_PRESS_BUTTON,
                new StringMessageField(1, button)
        );
    }

    private void appButtonRelease() throws IOException {
        sendMainRequest(
                REQUEST_ID_RELEASE_BUTTON
        );
    }
    private void appExitRequest() throws IOException {
        sendMainRequest(
                REQUEST_ID_EXIT_APP
        );
    }

    @Override
    public void onTestNewFunction() {
        try {
            openApp("Infrared");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void playFile(String appName, String filePath, String buttonName, long durationMillis){
        try {
            openApp(appName);
            Thread.sleep(1000);
            appLoadFile(filePath);
            Thread.sleep(500);
            appButtonPress(buttonName);
            Thread.sleep(durationMillis);
            appButtonRelease();
            Thread.sleep(100);
            appExitRequest();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onFetchRecordedData(int dataTypes) {
        super.onFetchRecordedData(dataTypes);

        onTestNewFunction();
    }
}
