#include <stdint.h>
#include <stddef.h>

#include <logger.h>
#include <database.h>

#include <sql/migration_0.h>
#include <sql/migration_1.h>

sqlite3 *global_database;
static database_transaction_lock *transaction_lock;

void
database_init()
{
    int rc = sqlite3_open(global_config->database, &global_database);

    if(rc)
    {
        LOGGER_CRIT("can't open database: %s\n", sqlite3_errmsg(global_database));
        exit(1);
    }

    LOGGER_DEBUG("Opened database %s\n", global_config->database);

    database_migrate();

    sqlite3_exec(global_database, "PRAGMA foreign_keys = ON", 0, 0, 0);
    transaction_lock = NULL;
}

void
database_free()
{
    sqlite3_close(global_database);
}

static void
database_migrate_step_simple(int level, const char* sql_migration)
{
    LOGGER_INFO("migrating LEVEL %d\n", level);
    char* err_msg;

    sqlite3_exec(global_database, "BEGIN TRANSACTION;", NULL, NULL, NULL);

    int rc = sqlite3_exec(global_database, sql_migration, NULL, NULL, &err_msg);
    if(rc)
    {
        LOGGER_CRIT("couldn't migrate LEVEL %d (%s)\n", level, err_msg);
        sqlite3_exec(global_database, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
        exit(1);
    }

    LOGGER_DEBUG("storing new user_version %d\n", level + 1);
    char pragma_query[32];
    sprintf(pragma_query, "PRAGMA user_version=%d;", level + 1);
    sqlite3_exec(global_database, pragma_query, 0, 0, 0);

    sqlite3_exec(global_database, "COMMIT TRANSACTION;", NULL, NULL, NULL);
}

void
database_migrate()
{
    uint16_t version_num = 0;
    int s;
    sqlite3_stmt *stmt;
    sqlite3_prepare_v2(global_database, "PRAGMA user_version;", -1, &stmt, NULL);
    s = sqlite3_step(stmt);
    if (s == SQLITE_ROW)
    {
        version_num = sqlite3_column_int(stmt, 0);
    }
    else
    {
        version_num = 0;
    }

    sqlite3_finalize(stmt);

    switch(version_num)
    {
        case 0:
            database_migrate_step_simple(0, (const char*)sql_migration_0_sql);
            __attribute__ ((fallthrough));
        case 1:
            database_migrate_step_simple(1, (const char*)sql_migration_1_sql);
            __attribute__ ((fallthrough));
        default:
            break;
    }
}

void
database_transaction_begin(database_transaction_lock *lock)
{
    if(transaction_lock == NULL)
    {
        LOGGER_DEBUG("beginning transaction\n");
        sqlite3_exec(global_database, "BEGIN TRANSACTION;", NULL, NULL, NULL);
        transaction_lock = lock;
    }
}

void
database_transaction_commit(const database_transaction_lock *lock)
{
    if(transaction_lock == lock)
    {
        LOGGER_DEBUG("commiting transaction\n");
        sqlite3_exec(global_database, "COMMIT TRANSACTION;", NULL, NULL, NULL);
        transaction_lock = NULL;
    }
}

void
database_transaction_rollback(const database_transaction_lock *lock)
{
    if(transaction_lock == lock)
    {
        LOGGER_DEBUG("rolling back transaction\n");
        sqlite3_exec(global_database, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
        transaction_lock = NULL;
    }
}

int
database_helper_get_id(sqlite3_stmt *stmt)
{
    int result = 0;

    for(;;)
    {
        int s;

        s = sqlite3_step(stmt);
        if (s == SQLITE_ROW)
        {
            result = sqlite3_column_int(stmt, 0);
        }
        else
        {
            if (s == SQLITE_DONE)
            {
                break;
            }

            LOGGER_ERR("error selecting id from database: %s\n", sqlite3_errstr(s));
            sqlite3_finalize(stmt);
            return 0;
        }
    }

    sqlite3_finalize(stmt);

    return result;
}

int*
database_helper_get_ids(sqlite3_stmt *stmt)
{
    int *result = malloc(sizeof(int));
    int new_id;

    int row = 0;

    for(;;)
    {
        int s;

        s = sqlite3_step(stmt);
        if (s == SQLITE_ROW)
        {
            new_id = sqlite3_column_int(stmt, 0);
            if(new_id != 0) // found row for other target (relay <> schedule)
            {
                row++;

                result = (int*)realloc(result, sizeof(int) * (row + 1));
                result[row - 1] = new_id;
            }
        }
        else
        {
            if (s == SQLITE_DONE)
            {
                break;
            }

            if(result)
            {
                free(result);
            }
            LOGGER_ERR("error selecting ids from database: %s\n", sqlite3_errstr(s));
            sqlite3_finalize(stmt);
            return NULL;
        }
    }

    sqlite3_finalize(stmt);
    result[row] = 0;

    return result;
}

char*
database_helper_get_string(sqlite3_stmt *stmt)
{
    char *result = NULL;

    for(;;)
    {
        int s;

        s = sqlite3_step(stmt);
        if (s == SQLITE_ROW)
        {
            const char *found_string = (const char *)sqlite3_column_text(stmt, 0);
            size_t found_string_len = sqlite3_column_bytes(stmt, 0);

            if(result)
            {
                free(result);
            }
            result = (char*)malloc(sizeof(char) * (found_string_len + 1));
            strncpy(result, found_string, found_string_len);
            result[found_string_len] = '\0';
        }
        else
        {
            if (s == SQLITE_DONE)
            {
                break;
            }

            if(result)
            {
                free(result);
            }
            LOGGER_ERR("error selecting string from database: %s\n", sqlite3_errstr(s));
            sqlite3_finalize(stmt);
            return NULL;
        }
    }

    sqlite3_finalize(stmt);

    return result;
}

char**
database_helper_get_strings(sqlite3_stmt *stmt)
{
    char **result = malloc(sizeof(char*));

    int row = 0;

    for(;;)
    {
        int s;

        s = sqlite3_step(stmt);
        if (s == SQLITE_ROW)
        {
            const char *new_string = (const char *)sqlite3_column_text(stmt, 0);
            size_t new_string_len = sqlite3_column_bytes(stmt, 0);
            row++;

            result = (char**)realloc(result, sizeof(char*) * (row + 1));
            result[row - 1] = malloc(sizeof(char) * (new_string_len + 1));
            strncpy(result[row - 1], new_string, new_string_len);
            result[row - 1][new_string_len] = '\0';
        }
        else
        {
            if(s == SQLITE_DONE)
            {
                break;
            }

            LOGGER_ERR("error selecting strings from database: %s\n", sqlite3_errstr(s));
            break;
        }
    }
    sqlite3_finalize(stmt);
    result[row] = NULL;
    return result;
}