/*  Copyright (C) 2022-2024 Damien Gaignon

    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.btbr;

import org.slf4j.Logger;

import android.location.Location;

import java.io.IOException;
import java.util.ArrayList;
import java.util.UUID;

import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.Logging;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
import nodomain.freeyourgadget.gadgetbridge.model.WorldClock;
import nodomain.freeyourgadget.gadgetbridge.service.AbstractDeviceSupport;

/**
 * Abstract base class for devices connected through a serial protocol, like RFCOMM BT or TCP socket.
 * <p/>
 * The connection to the device and all communication is made with a generic {@link BtClassicIo}.
 * Messages to the device are encoded
 * sent via {@link BtClassicIo}.
 *
 * @see BtClassicIo
 */
public abstract class AbstractBTBRDeviceSupport extends AbstractDeviceSupport implements SocketCallback {
    private BtBRQueue mQueue;
    private UUID mSupportedService = null;
    private int mBufferSize = 1024;
    private Logger logger;

    public AbstractBTBRDeviceSupport(Logger logger) {
        this.logger = logger;
        if (logger == null) {
            throw new IllegalArgumentException("logger must not be null");
        }
    }

    @Override
    public boolean connect() {
        if (mQueue == null) {
            mQueue = new BtBRQueue(getBluetoothAdapter(), getDevice(), getContext(), this, getSupportedService(), getBufferSize());
        }
        return mQueue.connect();
    }

    public void disconnect() {
        if (mQueue != null) {
            mQueue.disconnect();
        }
    }

    /**
     * Subclasses should populate the given builder to initialize the device (if necessary).
     *
     * @return the same builder as passed as the argument
     */
    protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
        return builder;
    }

    @Override
    public void dispose() {
        if (mQueue != null) {
            mQueue.dispose();
            mQueue = null;
        }
    }

    public TransactionBuilder createTransactionBuilder(String taskName) {
        return new TransactionBuilder(taskName);
    }

    /**
     * Ensures that the device is connected and (only then) performs the actions of the given
     * transaction builder.
     *
     * In contrast to {@link #performInitialized(String)}, no initialization sequence is performed
     * with the device, only the actions of the given builder are executed.
     * @param transaction
     * @throws IOException
     * @see {@link #performInitialized(String)}
     */
    public void performConnected(Transaction transaction) throws IOException {
        if (!isConnected()) {
            if (!connect()) {
                throw new IOException("2: Unable to connect to device: " + getDevice());
            }
        }
        getQueue().add(transaction);
    }

    public BtBRQueue getQueue() {
        return mQueue;
    }

    /**
     * Subclasses should call this method to add services they support.
     * Only supported services will be queried for characteristics.
     *
     * @param aSupportedService
     */
    protected void addSupportedService(UUID aSupportedService) {
        mSupportedService = aSupportedService;
    }

    protected UUID getSupportedService() {
        return mSupportedService;
    }

    protected void setBufferSize(int bufferSize) {
        mBufferSize = bufferSize;
    }

    protected int getBufferSize() {
        return mBufferSize;
    }

    /**
     * Utility method that may be used to log incoming messages when we don't know how to deal with them yet.
     *
     * @param value
     */
    public void logMessageContent(byte[] value) {
        logger.info("RECEIVED DATA WITH LENGTH: " + ((value != null) ? value.length : "(null)"));
        Logging.logBytes(logger, value);
    }

    public void onConnectionEstablished() {
        try {
            initializeDevice(createTransactionBuilder("Initializing device")).queue(getQueue());
        } catch (final Exception ex) {
            final GBDevice device = getDevice();

            if (device != null) {
                logger.error("Exception raised while initializing device {} (address {}), disconnecting", device.getName(), device.getAddress(), ex);
                device.setState(GBDevice.State.WAITING_FOR_RECONNECT);
                device.sendDeviceUpdateIntent(getContext());
            } else {
                logger.error("Exception raised while initializing unknown device", ex);
            }
        }
    }

    @Override
    public void onFindPhone(boolean start) {}

    @Override
    public void onSetFmFrequency(float frequency) {}

    @Override
    public void onSetLedColor(int color) {}

    @Override
    public void onSetGpsLocation(Location location) {}

    @Override
    public void onSetWorldClocks(ArrayList<? extends WorldClock> clocks) {}

    @Override
    public void onPowerOff() {}

    @Override
    public void onSetPhoneVolume(final float volume) {}

    @Override
    public void onSetReminders(ArrayList<? extends Reminder> reminders) {}

}
