#include "tt_hal_radio.h"

#include "Tactility/Check.h"
#include "Tactility/hal/Device.h"
#include "Tactility/hal/radio/RadioDevice.h"


static RadioState fromCpp(tt::hal::radio::RadioDevice::State state);
static tt::hal::radio::RadioDevice::State toCpp(RadioState state);
static Modulation fromCpp(tt::hal::radio::RadioDevice::Modulation modulation);
static tt::hal::radio::RadioDevice::Modulation toCpp(Modulation modulation);
static RadioParameter fromCpp(tt::hal::radio::RadioDevice::Parameter parameter);
static tt::hal::radio::RadioDevice::Parameter toCpp(RadioParameter parameter);
static RadioParameterStatus fromCpp(tt::hal::radio::RadioDevice::ParameterStatus status);
static tt::hal::radio::RadioDevice::ParameterStatus toCpp(RadioParameterStatus status);
static RadioTxState fromCpp(tt::hal::radio::RadioDevice::TransmissionState state);
static tt::hal::radio::RadioDevice::TransmissionState toCpp(RadioTxState state);

struct DeviceWrapper {
    std::shared_ptr<tt::hal::radio::RadioDevice> device;
    std::string name;
    std::string description;
    DeviceWrapper(std::shared_ptr<tt::hal::radio::RadioDevice> device)
        : device(device)
        , name(device->getName())
        , description(device->getDescription()) {}
};

static std::shared_ptr<tt::hal::radio::RadioDevice> findValidRadioDevice(tt::hal::Device::Id id) {
    auto device = tt::hal::findDevice(id);
    if (device == nullptr || device->getType() != tt::hal::Device::Type::Radio) {
        return nullptr;
    }
    return std::reinterpret_pointer_cast<tt::hal::radio::RadioDevice>(device);
}

extern "C" {

    RadioHandle tt_hal_radio_alloc(DeviceId radioId) {
        auto radio = findValidRadioDevice(radioId);
        return new DeviceWrapper(radio);
    }

    void tt_hal_radio_free(RadioHandle handle) {
        auto wrapper = static_cast<DeviceWrapper*>(handle);
        delete wrapper;
    }

    const char* const tt_hal_radio_get_name(RadioHandle handle) {
        auto wrapper = static_cast<DeviceWrapper*>(handle);
        return wrapper->name.c_str();
    }

    const char* const tt_hal_radio_get_desc(RadioHandle handle) {
        auto wrapper = static_cast<DeviceWrapper*>(handle);
        return wrapper->description.c_str();
    }

    RadioState tt_hal_radio_get_state(RadioHandle handle) {
        auto wrapper = static_cast<DeviceWrapper*>(handle);
        return fromCpp(wrapper->device->getState());
    }

    bool tt_hal_radio_set_modulation(RadioHandle handle, Modulation modulation) {
        auto wrapper = static_cast<DeviceWrapper*>(handle);
        return wrapper->device->setModulation(toCpp(modulation));
    }

    Modulation tt_hal_radio_get_modulation(RadioHandle handle) {
        auto wrapper = static_cast<DeviceWrapper*>(handle);
        return fromCpp(wrapper->device->getModulation());
    }

    RadioParameterStatus tt_hal_radio_set_parameter(RadioHandle handle, RadioParameter parameter, float value) {
        auto wrapper = static_cast<DeviceWrapper*>(handle);
        return fromCpp(wrapper->device->setParameter(toCpp(parameter), value));
    }

    RadioParameterStatus tt_hal_radio_get_parameter(RadioHandle handle, RadioParameter parameter, float *value) {
        auto wrapper = static_cast<DeviceWrapper*>(handle);
        // This is a programming error not an input error, thus assert.
        // TODO: Does TactC even know assert? Maybe putting a crash is the optimal solution.
        assert(value);
        return fromCpp(wrapper->device->getParameter(toCpp(parameter), *value));
    }

    void tt_hal_radio_get_parameter_unit_str(RadioHandle handle, RadioParameter parameter, char str[], unsigned maxSize) {
        auto wrapper = static_cast<DeviceWrapper*>(handle);
        assert(str);
        std::string unitString = wrapper->device->getParameterUnit(toCpp(parameter)).toString();
        size_t i = 0;
        for (; i < (maxSize - 1); ++i) {
            str[i] = unitString[i];
        }
        str[i] = '\0';
    }

    bool tt_hal_radio_can_transmit(RadioHandle handle, Modulation modulation) {
        auto wrapper = static_cast<DeviceWrapper*>(handle);
        return wrapper->device->canTransmit(toCpp(modulation));
    }

    bool tt_hal_radio_can_receive(RadioHandle handle, Modulation modulation) {
        auto wrapper = static_cast<DeviceWrapper*>(handle);
        return wrapper->device->canReceive(toCpp(modulation));
    }

    bool tt_hal_radio_start(RadioHandle handle) {
        auto wrapper = static_cast<DeviceWrapper*>(handle);
        return wrapper->device->start();
    }

    bool tt_hal_radio_stop(RadioHandle handle) {
        auto wrapper = static_cast<DeviceWrapper*>(handle);
        return wrapper->device->stop();
    }

    RadioTxId tt_hal_radio_transmit(RadioHandle handle, RadioTxPacket packet, RadioTxStateCallback callback) {
        auto wrapper = static_cast<DeviceWrapper*>(handle);
        auto ttPacket = tt::hal::radio::TxPacket{
            .data = std::vector<uint8_t>(packet.data, packet.data + packet.size),
            .address = packet.address
        };
        return wrapper->device->transmit(ttPacket, [callback](tt::hal::radio::RadioDevice::TxId id, tt::hal::radio::RadioDevice::TransmissionState state) {
            if (callback) {
                callback(id, fromCpp(state));
            }
        });
    }

    RadioRxSubscriptionId radio_subscribe_receive(RadioHandle handle, RadioOnReceiveCallback callback) {
        auto wrapper = static_cast<DeviceWrapper*>(handle);
        return wrapper->device->subscribeRx([callback](tt::hal::Device::Id id, const tt::hal::radio::RxPacket& ttPacket) {
            if (callback) {
                auto ttcPacket = RadioRxPacket{
                    .data = ttPacket.data.data(),
                    .size = ttPacket.data.size(),
                    .rssi = ttPacket.rssi,
                    .snr = ttPacket.snr
                };
                callback(id, &ttcPacket);
            }
        });
    }

    void radio_unsubscribe_receive(RadioHandle handle, RadioRxSubscriptionId id) {
        auto wrapper = static_cast<DeviceWrapper*>(handle);
        wrapper->device->unsubscribeRx(id);
    }
}

static RadioState fromCpp(tt::hal::radio::RadioDevice::State state) {
    switch (state) {
        case tt::hal::radio::RadioDevice::State::PendingOn:
            return RADIO_PENDING_ON;
        case tt::hal::radio::RadioDevice::State::On:
            return RADIO_ON;
        case tt::hal::radio::RadioDevice::State::Error:
            return RADIO_ERROR;
        case tt::hal::radio::RadioDevice::State::PendingOff:
            return RADIO_PENDING_OFF;
        case tt::hal::radio::RadioDevice::State::Off:
            return RADIO_OFF;
        default:
            tt_crash("Radio state not supported");
    }
}

static tt::hal::radio::RadioDevice::State toCpp(RadioState state) {
    switch (state) {
        case RADIO_PENDING_ON:
            return tt::hal::radio::RadioDevice::State::PendingOn;
        case RADIO_ON:
            return tt::hal::radio::RadioDevice::State::On;
        case RADIO_ERROR:
            return tt::hal::radio::RadioDevice::State::Error;
        case RADIO_PENDING_OFF:
            return tt::hal::radio::RadioDevice::State::PendingOff;
        case RADIO_OFF:
            return tt::hal::radio::RadioDevice::State::Off;
        default:
            tt_crash("Radio state not supported");
    }
}

static Modulation fromCpp(tt::hal::radio::RadioDevice::Modulation modulation) {
    switch (modulation) {
        case tt::hal::radio::RadioDevice::Modulation::LoRa:
            return MODULATION_LORA;
        case tt::hal::radio::RadioDevice::Modulation::Fsk:
            return MODULATION_FSK;
        case tt::hal::radio::RadioDevice::Modulation::LrFhss:
            return MODULATION_LRFHSS;
        default:
            tt_crash("Modulation not supported");
    }
}

static tt::hal::radio::RadioDevice::Modulation toCpp(Modulation modulation) {
    switch (modulation) {
        case MODULATION_LORA:
            return tt::hal::radio::RadioDevice::Modulation::LoRa;
        case MODULATION_FSK:
            return tt::hal::radio::RadioDevice::Modulation::Fsk;
        case MODULATION_LRFHSS:
            return tt::hal::radio::RadioDevice::Modulation::LrFhss;
        default:
            tt_crash("Modulation not supported");
    }
}

static RadioParameter fromCpp(tt::hal::radio::RadioDevice::Parameter parameter) {
    switch (parameter) {
        case tt::hal::radio::RadioDevice::Parameter::Power:
            return RADIO_POWER;
        case tt::hal::radio::RadioDevice::Parameter::Frequency:
            return RADIO_FREQUENCY;
        case tt::hal::radio::RadioDevice::Parameter::Bandwidth:
            return RADIO_BANDWIDTH;
        case tt::hal::radio::RadioDevice::Parameter::SpreadFactor:
            return RADIO_SPREADFACTOR;
        case tt::hal::radio::RadioDevice::Parameter::CodingRate:
            return RADIO_CODINGRATE;
        case tt::hal::radio::RadioDevice::Parameter::SyncWord:
            return RADIO_SYNCWORD;
        case tt::hal::radio::RadioDevice::Parameter::PreambleLength:
            return RADIO_PREAMBLES;
        case tt::hal::radio::RadioDevice::Parameter::FrequencyDeviation:
            return RADIO_FREQDIV;
        case tt::hal::radio::RadioDevice::Parameter::DataRate:
            return RADIO_DATARATE;
        case tt::hal::radio::RadioDevice::Parameter::AddressWidth:
            return RADIO_ADDRWIDTH;
        case tt::hal::radio::RadioDevice::Parameter::NarrowGrid:
            return RADIO_NARROWGRID;
        default:
            tt_crash("Parameter not supported");
    }
}

static tt::hal::radio::RadioDevice::Parameter toCpp(RadioParameter parameter) {
    switch (parameter) {
        case RADIO_POWER:
            return tt::hal::radio::RadioDevice::Parameter::Power;
        case RADIO_FREQUENCY:
            return tt::hal::radio::RadioDevice::Parameter::Frequency;
        case RADIO_BANDWIDTH:
            return tt::hal::radio::RadioDevice::Parameter::Bandwidth;
        case RADIO_SPREADFACTOR:
            return tt::hal::radio::RadioDevice::Parameter::SpreadFactor;
        case RADIO_CODINGRATE:
            return tt::hal::radio::RadioDevice::Parameter::CodingRate;
        case RADIO_SYNCWORD:
            return tt::hal::radio::RadioDevice::Parameter::SyncWord;
        case RADIO_PREAMBLES:
            return tt::hal::radio::RadioDevice::Parameter::PreambleLength;
        case RADIO_FREQDIV:
            return tt::hal::radio::RadioDevice::Parameter::FrequencyDeviation;
        case RADIO_DATARATE:
            return tt::hal::radio::RadioDevice::Parameter::DataRate;
        case RADIO_ADDRWIDTH:
            return tt::hal::radio::RadioDevice::Parameter::AddressWidth;
        case RADIO_NARROWGRID:
            return tt::hal::radio::RadioDevice::Parameter::NarrowGrid;
        default:
            tt_crash("Parameter not supported");
    }
}

static RadioParameterStatus fromCpp(tt::hal::radio::RadioDevice::ParameterStatus status) {
    switch (status) {
        case tt::hal::radio::RadioDevice::ParameterStatus::Unavailable:
            return RADIO_PARAM_UNAVAILABLE;
        case tt::hal::radio::RadioDevice::ParameterStatus::ValueError:
            return RADIO_PARAM_VALERROR;
        case tt::hal::radio::RadioDevice::ParameterStatus::Success:
            return RADIO_PARAM_SUCCESS;
        default:
            tt_crash("Parameter status not supported");
    }
}

static tt::hal::radio::RadioDevice::ParameterStatus toCpp(RadioParameterStatus status) {
    switch (status) {
        case RADIO_PARAM_UNAVAILABLE:
            return tt::hal::radio::RadioDevice::ParameterStatus::Unavailable;
        case RADIO_PARAM_VALERROR:
            return tt::hal::radio::RadioDevice::ParameterStatus::ValueError;
        case RADIO_PARAM_SUCCESS:
            return tt::hal::radio::RadioDevice::ParameterStatus::Success;
        default:
            tt_crash("Parameter status not supported");
    }
}

static RadioTxState fromCpp(tt::hal::radio::RadioDevice::TransmissionState state) {
    switch (state) {
        case tt::hal::radio::RadioDevice::TransmissionState::Queued:
            return RADIO_TX_QUEUED;
        case tt::hal::radio::RadioDevice::TransmissionState::PendingTransmit:
            return RADIO_TX_PENDING_TRANSMIT;
        case tt::hal::radio::RadioDevice::TransmissionState::Transmitted:
            return RADIO_TX_TRANSMITTED;
        case tt::hal::radio::RadioDevice::TransmissionState::Timeout:
            return RADIO_TX_TIMEOUT;
        case tt::hal::radio::RadioDevice::TransmissionState::Error:
            return RADIO_TX_ERROR;
        default:
            tt_crash("Transmission state not supported");
    }
}

static tt::hal::radio::RadioDevice::TransmissionState toCpp(RadioTxState state) {
    switch (state) {
        case RADIO_TX_QUEUED:
            return tt::hal::radio::RadioDevice::TransmissionState::Queued;
        case RADIO_TX_PENDING_TRANSMIT:
            return tt::hal::radio::RadioDevice::TransmissionState::PendingTransmit;
        case RADIO_TX_TRANSMITTED:
            return tt::hal::radio::RadioDevice::TransmissionState::Transmitted;
        case RADIO_TX_TIMEOUT:
            return tt::hal::radio::RadioDevice::TransmissionState::Timeout;
        case RADIO_TX_ERROR:
            return tt::hal::radio::RadioDevice::TransmissionState::Error;
        default:
            tt_crash("Transmission state not supported");
    }
}
