#include "SoilMoisture.h"

namespace trygvis {
namespace soil_moisture {


#define BLUETOOTH_UUID_INITIALIZER \
    { \
        0x00, 0x00, 0x00, 0x00, \
        0x00, 0x00, \
        0x10, 0x00, \
        0x80, 0x00, \
        0x00, 0x80, 0x5f, 0x9b, 0x34, 0xfb \
    };

auto trygvis_io_base_uuid = boost::uuids::uuid {
        0x32, 0xd0, 0x00, 0x00,
        0x03, 0x5d,
        0x59, 0xc5,
        0x70, 0xd3,
        0xbc, 0x8e, 0x4a, 0x1f, 0xd8, 0x3f};

const boost::uuids::uuid soil_moisture_service = makeUuid(trygvis_io_base_uuid, 0x00, 0x10);
const boost::uuids::uuid soil_moisture_characteristic = makeUuid(trygvis_io_base_uuid, 0x00, 0x11);

using namespace trygvis::bluetooth;

ByteBuffer createGetSensorCount() {
    return ByteBuffer::alloc(100).
            write8(static_cast<uint8_t>(sm_cmd_code::SM_CMD_GET_SENSOR_COUNT));
}

ByteBuffer createGetValue(uint8_t sensor) {
    return ByteBuffer::alloc(100).
            write8(static_cast<uint8_t>(sm_cmd_code::SM_CMD_GET_VALUE)).
            write16le(sensor);
}

ByteBuffer createSetWarningValue(uint8_t sensor, uint16_t warning_value) {
    return ByteBuffer::alloc(100).
            write8(static_cast<uint8_t>(sm_cmd_code::SM_CMD_SET_WARNING_VALUE)).
            write8(sensor).
            write16le(warning_value);
}

ByteBuffer createSetSensorName(uint8_t sensor, string name) {
    return ByteBuffer::alloc(100).
            write8(static_cast<uint8_t>(sm_cmd_code::SM_CMD_SET_SENSOR_NAME)).
            write8(sensor).
            write(reinterpret_cast<const uint8_t *>(name.c_str()), name.length());
}

ByteBuffer createGetSensorName(uint8_t sensor) {
    return ByteBuffer::alloc(100).
            write8(static_cast<uint8_t>(sm_cmd_code::SM_CMD_GET_SENSOR_NAME)).
            write8(sensor);
}

ByteBuffer createSetUpdateInterval(uint8_t sensor, uint8_t interval_in_seconds) {
    return ByteBuffer::alloc(100).
            write8(static_cast<uint8_t>(sm_cmd_code::SM_CMD_SET_UPDATE_INTERVAL)).
            write8(sensor).
            write8(interval_in_seconds);
}

SoilMoisture SoilMoisture::create(BluetoothGatt & gatt) {
    gatt.discoverServices();

    auto service = gatt.findService(soil_moisture_service);

    if (!service) {
        throw runtime_error("The device is missing the soil moisture service");
    }

    auto c = service->findCharacteristic(soil_moisture_characteristic);

    if (!c) {
        throw runtime_error("The device is missing the soil moisture characteristic");
    }

    return SoilMoisture(gatt, *service, *c);
}

SoilMoisture::SoilMoisture(BluetoothGatt & gatt, BluetoothGattService &s, BluetoothGattCharacteristic &c) :
        gatt(gatt), s(s), c(c) {
}

ByteBuffer SoilMoisture::writeAndRead(ByteBuffer &requestBytes) {
    requestBytes.setCursor(0);

    uint8_t expectedCode = requestBytes.get8(0);

    gatt.writeValue(c, requestBytes);

    auto responseBytes = gatt.readValue(c);

    if (responseBytes.getSize() < 1) {
        throw runtime_error("Unexpected number of bytes read: " + to_string(requestBytes.getSize()));
    }

    uint8_t actualCode = responseBytes.read8();
    if (actualCode != expectedCode) {
        throw runtime_error("Unexpected response code: " + to_string(actualCode) + ", expected " + to_string(expectedCode));
    }

    return responseBytes;
}

uint8_t SoilMoisture::getSensorCount() {
    auto buffer = createGetSensorCount();
    return writeAndRead(buffer).read8();
}

uint16_t SoilMoisture::getValue(uint8_t sensor) {
    auto req = createGetValue(sensor);

    return writeAndRead(req).read16le();
}

}
}