#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <uuid/uuid.h>

#include <sqlite3.h>
#include <models/controller.h>
#include <logger.h>
#include <config.h>
#include <constants.h>
#include <database.h>

controller_t *global_controller;

static int
db_update_insert(controller_t *controller, sqlite3_stmt *stmt)
{
    LOGGER_DEBUG("saving controller '%s' into database (id: %d)\n", controller->name, controller->id);
    int rc;

    sqlite3_bind_int(stmt, 1, controller->id);
    sqlite3_bind_blob(stmt, 2, controller->uid, sizeof(uuid_t), SQLITE_STATIC);
    sqlite3_bind_text(stmt, 3, controller->name, -1, SQLITE_STATIC);
    sqlite3_bind_int(stmt, 4, controller->command_port);

    rc = sqlite3_step(stmt);

    sqlite3_finalize(stmt);

    return rc != SQLITE_DONE;
}

static controller_t*
controller_db_select_mapper(sqlite3_stmt *stmt)
{
    controller_t *new_controller = malloc(sizeof(controller_t));
    for(int i = 0; i < sqlite3_column_count(stmt); i++)
    {
        const char *name = sqlite3_column_name(stmt, i);
        switch(name[0])
        {
            case 'i': // id
                new_controller->id = sqlite3_column_int(stmt, i);
                break;
            case 'c': // command_port
                new_controller->command_port = sqlite3_column_int(stmt, i);
                break;
            case 'n': // name
                strncpy(new_controller->name, (const char*)sqlite3_column_text(stmt, i), 127);
                break;
            case 'u': // uid
                uuid_copy(new_controller->uid, (const unsigned char*)sqlite3_column_blob(stmt, i));
                break;
            default: // ignore columns not implemented
                break;
        }
    }
    new_controller->relays = malloc(sizeof(relay_t) * global_config->relay_count);
    uint8_t i;
    for(i = 0; i < global_config->relay_count; ++i)
    {
        new_controller->relays[i] = relay_load(i);
        if(!new_controller->relays[i])
        {
            new_controller->relays[i] = relay_create(i);
            relay_save(new_controller->relays[i]);
        }
    }
    return new_controller;
}

static controller_t**
controller_db_select(sqlite3_stmt *stmt)
{
    controller_t **all_controllers = malloc(sizeof(controller_t*));

    int row = 0;

    for(;;)
    {
        int s;

        s = sqlite3_step(stmt);
        if (s == SQLITE_ROW)
        {
            controller_t *new_controller = controller_db_select_mapper(stmt);
            row++;

            all_controllers = (controller_t**)realloc(all_controllers, sizeof(controller_t*) * (row + 1));
            all_controllers[row - 1] = new_controller;
        }
        else
        {
            if(s == SQLITE_DONE)
            {
                break;
            }
            else
            {
                LOGGER_ERR("error selecting controllers from database: %s\n", sqlite3_errstr(s));
                break;
            }
        }
    }
    sqlite3_finalize(stmt);
    all_controllers[row] = NULL;
    return all_controllers;
}

int
controller_save()
{
    int opened_transaction = database_transaction_begin();
     
    sqlite3_stmt *stmt;
    if(global_controller->id)
    {
        sqlite3_prepare_v2(global_database, "UPDATE controllers set uid = ?2, name = ?3, command_port = ?4 WHERE id = ?1;", -1, &stmt, NULL);
    }
    else
    {
        sqlite3_prepare_v2(global_database, "INSERT INTO controllers(uid, name, command_port) values (?2, ?3, ?4);", -1, &stmt, NULL);
    }

    int result = db_update_insert(global_controller, stmt);

    if(result)
    {
        if(global_controller->id)
        {
            LOGGER_ERR("error inserting data: %s\n", sqlite3_errmsg(global_database));
        }
        else
        {
            LOGGER_ERR("error updating data: %s\n", sqlite3_errmsg(global_database));
        }

        if(opened_transaction)
        {
            database_transaction_rollback();
        }
    }
    else
    {
        if(!global_controller->id)
        {
            global_controller->id = sqlite3_last_insert_rowid(global_database);
        }

        if(opened_transaction)
        {
            database_transaction_commit();
        }
    }

    return result;
}

controller_t*
controller_load()
{
    LOGGER_DEBUG("getting controller from database\n");
    sqlite3_stmt *stmt;

    sqlite3_prepare_v2(global_database, "SELECT * FROM controllers LIMIT 1;", -1, &stmt, NULL);

    controller_t **sql_result = controller_db_select(stmt);

    controller_t *result = sql_result[0];
    free(sql_result);
    return result;
}

int
controller_remove(controller_t *controller)
{
    sqlite3_stmt *stmt;
    if(!controller->id)
    {
        return 0;
    }

    sqlite3_prepare_v2(global_database, "DELETE FROM controllers WHERE id=?1;", -1, &stmt, NULL);
    sqlite3_bind_int(stmt, 1, controller->id);

    int rc = sqlite3_step(stmt);

    sqlite3_finalize(stmt);

    return rc != SQLITE_DONE;
}

controller_t*
controller_create(void)
{
    controller_t *new_controller = malloc(sizeof(*new_controller));
    new_controller->id = 0;
    uuid_generate(new_controller->uid);

    strncpy(new_controller->name, global_config->name, MAX_NAME_LENGTH);
    new_controller->name[MAX_NAME_LENGTH] = '\0';

    new_controller->command_port = 0;

    new_controller->relays = malloc(sizeof(relay_t) * global_config->relay_count);
    uint8_t i;
    for(i = 0; i < global_config->relay_count; ++i)
    {
        new_controller->relays[i] = relay_load(i);
        if(!new_controller->relays[i])
        {
            new_controller->relays[i] = relay_create(i);
            relay_save(new_controller->relays[i]);
        }
    }

    return new_controller;
}

void
controller_set_name(controller_t *controller, const char *name)
{
    strncpy(controller->name, name, MAX_NAME_LENGTH);
    controller->name[MAX_NAME_LENGTH] = '\0';
}

void
controller_free(controller_t *controller)
{
    for(int i = 0; i < global_config->relay_count; ++i)
    {
        relay_free(controller->relays[i]);
    }
    free(controller->relays);
    free(controller);
}

void
controller_debug(controller_t *controller)
{
    if(controller == NULL)
    {
        LOGGER_DEBUG("controller is NULL\n");
        return;
    }
    char uuid_str[37];
    uuid_unparse(controller->uid, uuid_str);
    LOGGER_DEBUG("(1/3) %s @ %p\n", uuid_str, (void*)controller);
    LOGGER_DEBUG("(2/3) name: %s\n", controller->name);
    LOGGER_DEBUG("(3/3) relays @ %p:\n", (void*)controller->relays);
    for(int i = 0; i < global_config->relay_count; ++i)
    {
        relay_debug(controller->relays[i]);
    }
}