#include <exception>
#include <errno.h>
#include <string.h>
#include "CaribouLite.hpp"

std::shared_ptr<CaribouLite> CaribouLite::_instance = nullptr;
std::mutex CaribouLite::_instMutex;

//==================================================================
static const char* decode_si_code(int signo, int si_code) 
{
    switch (signo) {
        case SIGILL:
            switch (si_code) {
                case ILL_ILLOPC: return "Illegal opcode";
                case ILL_ILLOPN: return "Illegal operand";
                case ILL_ILLADR: return "Illegal addressing mode";
                case ILL_ILLTRP: return "Illegal trap";
                case ILL_PRVOPC: return "Privileged opcode";
                case ILL_PRVREG: return "Privileged register";
                case ILL_COPROC: return "Coprocessor error";
                case ILL_BADSTK: return "Internal stack error";
                default: return "Unknown SIGILL code";
            }
        case SIGFPE:
            switch (si_code) {
                case FPE_INTDIV: return "Integer divide-by-zero";
                case FPE_INTOVF: return "Integer overflow";
                case FPE_FLTDIV: return "Floating point divide-by-zero";
                case FPE_FLTOVF: return "Floating point overflow";
                case FPE_FLTUND: return "Floating point underflow";
                case FPE_FLTRES: return "Floating point inexact result";
                case FPE_FLTINV: return "Invalid floating point operation";
                case FPE_FLTSUB: return "Subscript out of range";
                default: return "Unknown SIGFPE code";
            }
        case SIGSEGV:
            switch (si_code) {
                case SEGV_MAPERR: return "Address not mapped";
                case SEGV_ACCERR: return "Invalid permissions";
                default: return "Unknown SIGSEGV code";
            }
        case SIGBUS:
            switch (si_code) {
                case BUS_ADRALN: return "Invalid address alignment";
                case BUS_ADRERR: return "Non-existent physical address";
                case BUS_OBJERR: return "Object-specific hardware error";
                default: return "Unknown SIGBUS code";
            }
        case SIGTRAP:
            switch (si_code) {
                case TRAP_BRKPT: return "Process breakpoint";
                case TRAP_TRACE: return "Process trace trap";
                default: return "Unknown SIGTRAP code";
            }
        case SIGCHLD:
            switch (si_code) {
                case CLD_EXITED: return "Child has exited";
                case CLD_KILLED: return "Child has terminated abnormally and did not create a core file";
                case CLD_DUMPED: return "Child has terminated abnormally and created a core file";
                case CLD_TRAPPED: return "Traced child has trapped";
                case CLD_STOPPED: return "Child has stopped";
                case CLD_CONTINUED: return "Stopped child has continued";
                default: return "Unknown SIGCHLD code";
            }
        case SIGPOLL:
            switch (si_code) {
                case POLL_IN: return "Data input available";
                case POLL_OUT: return "Output buffers available";
                case POLL_MSG: return "Input message available";
                case POLL_ERR: return "I/O error";
                case POLL_PRI: return "High priority input available";
                case POLL_HUP: return "Device disconnected";
                default: return "Unknown SIGPOLL/SIGIO code";
            }
        default:
            switch(si_code)
            {
                case SI_USER: return "Signal sent by kill()";
                case SI_QUEUE: return "Signal was sent by sigqueue()";
                case SI_TIMER: return "Signal was generated by expiration of a timer set by timer_settimer()";
                case SI_ASYNCIO: return "Signal was generated by completion of an asynchronous I/O request";
                case SI_MESGQ: return "Signal was generated by arrival of a message on an empty message queue.";
                default: return "Unknown General code";
            }
    }
}

//==================================================================
static void print_siginfo(const siginfo_t *si) 
{
    printf("  Signal Number: %d\n", si->si_signo);
    printf("  Signal Code: %d (%s)\n", si->si_code, decode_si_code(si->si_signo, si->si_code));
    printf("  Signal Value (int): %d\n", si->si_value.sival_int);
    printf("  Signal Value (ptr): %p\n", si->si_value.sival_ptr);
    printf("  Error Number: %d => '%s'\n", si->si_errno, strerror(errno));
    printf("  Sending Process ID: %d\n", si->si_pid);
    printf("  User ID: %d\n", si->si_uid);
    printf("  Faulting Instruction Address: %p\n", si->si_addr);
    printf("  Exit Value or Signal: %d\n", si->si_status);
    printf("  Band Event for SIGPOLL: %ld\n", si->si_band);
}
//==================================================================
void CaribouLite::DefaultSignalHandler(void* context, int signal_number, siginfo_t *si)
{
    CaribouLite* cl = (CaribouLite*)context;
    printf(">> DefaultSignalHandler: Signal caught (sig %d), additional information: \n", signal_number);
    print_siginfo(si);
    fflush(stdout);
    
    if (cl->_on_signal_caught) cl->_on_signal_caught(signal_number);
    
    exit(-1);
    //cl->ReleaseResources();
}

//==================================================================
void CaribouLite::RegisterSignalHandler(std::function<void(int)> on_signal_caught)
{
    _on_signal_caught = on_signal_caught;
}

//==================================================================
bool CaribouLite::DetectBoard(SysVersion *sysVer, std::string& name, std::string& guid)
{
    cariboulite_version_en hw_ver;
    char hw_name[64];
    char hw_guid[64];
    bool detected = cariboulite_detect_connected_board(&hw_ver, hw_name, hw_guid);
    if (detected)
    {
        name = std::string(hw_name);
        guid = std::string(hw_guid);
        if (sysVer) *sysVer = (SysVersion)hw_ver;
    }
    return detected;
}

//==================================================================
CaribouLite &CaribouLite::GetInstance(bool asyncApi, bool forceFpgaProg, LogLevel logLvl)
{
    SysVersion ver;
    std::string name, guid;
    if (!DetectBoard(&ver, name, guid))
    {
        throw std::runtime_error("CaribouLite was not detected");
    }
    
    std::lock_guard<std::mutex> lock(_instMutex);
    if (_instance == nullptr)
    {
        try
        {
            _instance = std::shared_ptr<CaribouLite>(new CaribouLite(asyncApi, forceFpgaProg, logLvl));
        }
        catch (std::exception& e)
        {
            throw e;
        }
    }
    return *_instance;
}

//==================================================================
CaribouLite::CaribouLite(bool asyncApi, bool forceFpgaProg, LogLevel logLvl)
{
    if (cariboulite_init(forceFpgaProg, (cariboulite_log_level_en)logLvl) != 0)
    {
        throw std::runtime_error("Driver initialization failed");
    }
    
    // register signal handler
    cariboulite_register_signal_handler (CaribouLite::DefaultSignalHandler, this);
    
    // get information
    DetectBoard(&_systemVersion, _productName, _productGuid);
    
    //printf("API TYPE: %d\n", asyncApi);
    CaribouLiteRadio::ApiType api_type = (asyncApi) ? CaribouLiteRadio::ApiType::Async : CaribouLiteRadio::ApiType::Sync;
    
    // populate the radio devices
    cariboulite_radio_state_st *radio_s1g = cariboulite_get_radio(cariboulite_channel_s1g);
    CaribouLiteRadio* radio_s1g_int = new CaribouLiteRadio(radio_s1g, CaribouLiteRadio::RadioType::S1G, api_type, this);
    _channels.push_back(radio_s1g_int);
    
    cariboulite_radio_state_st *radio_hif = cariboulite_get_radio(cariboulite_channel_hif);
    CaribouLiteRadio* radio_hif_int = new CaribouLiteRadio(radio_hif, CaribouLiteRadio::RadioType::HiF, api_type, this);
    _channels.push_back(radio_hif_int);
}

//==================================================================
void CaribouLite::ReleaseResources(void)
{
    if (!_instance) return;
    
    for (size_t i = 0; i < _instance->_channels.size(); i++)
    {
        if (_instance->_channels[i]) 
        {
            delete _instance->_channels[i];
        }
    }
    
    if (cariboulite_is_initialized()) 
    {
        cariboulite_close();
    }
}

//==================================================================
CaribouLite::~CaribouLite()
{
    if (_instance != nullptr) 
    {
        ReleaseResources();
        
        _instance.reset();
        _instance = nullptr;
    }
}

//==================================================================
bool CaribouLite::IsInitialized()
{
    return cariboulite_is_initialized();
}

//==================================================================
CaribouLiteVersion CaribouLite::GetApiVersion()
{
    cariboulite_lib_version_st v = {0};
    cariboulite_get_lib_version(&v);
    return CaribouLiteVersion(v.major_version, v.minor_version, v.revision);
}

//==================================================================
unsigned int CaribouLite::GetHwSerialNumber()
{
    return cariboulite_get_sn();
}

//==================================================================
CaribouLite::SysVersion CaribouLite::GetSystemVersion()
{
    return (CaribouLite::SysVersion)cariboulite_get_version();
}

//==================================================================
std::string CaribouLite::GetSystemVersionStr(CaribouLite::SysVersion v)
{
    switch(v)
    {
        case SysVersion::CaribouLiteFull: return std::string("CaribouLite6G"); break;
        case SysVersion::CaribouLiteISM: return std::string("CaribouLiteISM"); break;
        case SysVersion::Unknown: 
        default: return std::string("Unknown CaribouLite"); break;
    }
    return std::string(""); // unreachable.. hopefully
}

//==================================================================
std::string CaribouLite::GetSystemVersionStr(void)
{
    return CaribouLite::GetSystemVersionStr(GetSystemVersion());
}

//==================================================================
std::string CaribouLite::GetHwGuid(void)
{
    return _productGuid;
}

//==================================================================
CaribouLiteRadio* CaribouLite::GetRadioChannel(CaribouLiteRadio::RadioType ch)
{
    return _channels[(int)ch];
}
    
//==================================================================
void CaribouLite::SetLed0States (bool state)
{
    int led1 = 0;
    cariboulite_get_leds_state (NULL, &led1);
    cariboulite_set_leds_state (state, led1);
}

//==================================================================
bool CaribouLite::GetLed0States ()
{
    int led0 = 0;
    cariboulite_get_leds_state (&led0, NULL);
    return led0 != 0;
}

//==================================================================
void CaribouLite::SetLed1States (bool state)
{
    int led0 = 0;
    cariboulite_get_leds_state (&led0, NULL);
    cariboulite_set_leds_state (led0, state);

}

//==================================================================
bool CaribouLite::GetLed1States ()
{
    int led1 = 0;
    cariboulite_get_leds_state (NULL, &led1);
    return led1 != 0;
}

//==================================================================
bool CaribouLite::GetButtonState ()
{
    int btn = 0;
    cariboulite_get_button_state (&btn);
    return btn != 0;
}

//==================================================================
void CaribouLite::SetPmodState (uint8_t val)
{
    cariboulite_set_pmod_val (val);
}

//==================================================================
uint8_t CaribouLite::GetPmodState ()
{
    uint8_t val = 0;
    cariboulite_get_pmod_val (&val);
    return val;
}
