#include <bsd/string.h>

#include <cache.h>
#include <logger.h>
#include <models/junction_relay_schedule.h>
#include <models/junction_tag.h>
#include <models/macro_action.h>
#include <sql/cache.h>


sqlite3 *cache_database;

static char*
cache_get_value(char *key)
{
    sqlite3_stmt *stmt;
    sqlite3_prepare_v2(cache_database, "SELECT value FROM cache WHERE key=?1;", -1, &stmt, NULL);
    sqlite3_bind_text(stmt, 1, key, -1, SQLITE_STATIC);

    char *result = NULL;

    while(1)
    {
        int s;

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

            if(result)
            {
                free(result);
            }
            result = (char*)malloc(sizeof(char) * (found_value_len + 1));
            strlcpy(result, found_value, found_value_len + 1);
        }
        else
        {
            if (s == SQLITE_DONE)
            {
                break;
            }
            else
            {
                LOGGER_WARNING("failed selecting %s from cache: %s\n", key, sqlite3_errstr(s));
                break;
            }
        }
    }

    sqlite3_finalize(stmt);

    return result;
}

static void
cache_insert_value(char *key, char *value)
{
    sqlite3_stmt *stmt;
    sqlite3_prepare_v2(cache_database, "INSERT INTO cache (key, value, expiration) VALUES (?1, ?2, 0);", -1, &stmt, NULL);
    sqlite3_bind_text(stmt, 1, key, -1, SQLITE_STATIC);
    sqlite3_bind_text(stmt, 2, value, -1, SQLITE_STATIC);

    int rc = sqlite3_step(stmt);

    sqlite3_finalize(stmt);

    if(rc != SQLITE_DONE)
    {
        LOGGER_WARNING("failed to save %s to cache: %s\n", key, sqlite3_errstr(rc));
    }
}

static void
cache_invalidate(char *key)
{
    LOGGER_DEBUG("invalidating %s\n", key);
    sqlite3_stmt *stmt;
    int rc;

    sqlite3_prepare_v2(cache_database, "DELETE FROM cache WHERE key=?1;", -1, &stmt, NULL);
    sqlite3_bind_text(stmt, 1, key, -1, SQLITE_STATIC);

    rc = sqlite3_step(stmt);
    sqlite3_finalize(stmt);

    if(rc != SQLITE_DONE)
    {
        LOGGER_WARNING("failed to invalidate %s in cache: %s\n", key, sqlite3_errstr(rc));
    }
}

void
cache_init()
{
    int rc = sqlite3_open(":memory:", &cache_database);

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

    char* err_msg;
    rc = sqlite3_exec(cache_database, (const char *)sql_cache_sql, NULL, NULL, &err_msg);
    if(rc)
    {
        LOGGER_CRIT("couldn't initialize cache (%s)\n", err_msg);
        exit(1);
    }
    LOGGER_DEBUG("cache initalized\n");
}

void
cache_free()
{
    sqlite3_close(cache_database);
}



void
cache_put_json_schedule(int schedule_id, char *schedule_json)
{
    char key[32];
    sprintf(key, "schedule_json:%d", schedule_id);
    cache_insert_value(key, schedule_json);
}

char*
cache_get_json_schedule(int schedule_id)
{
    char key[32];
    sprintf(key, "schedule_json:%d", schedule_id);
    return cache_get_value(key);
}

void
cache_invalidate_schedule(int schedule_id)
{
    char key[32];
    sprintf(key, "schedule_json:%d", schedule_id);
    cache_invalidate(key);

    int *relay_ids = junction_relay_schedule_get_relay_ids_with_schedule(schedule_id);
    for(int i = 0; relay_ids[i] != 0; ++i)
    {
        cache_invalidate_relay(relay_ids[i], -1);
    }
    free(relay_ids);

    int *macro_ids = macro_action_get_macro_ids_with_schedule(schedule_id);
    for(int i = 0; macro_ids[i] != 0; ++i)
    {
        cache_invalidate_macro(macro_ids[i]);
    }
    free(macro_ids);
}



void
cache_put_json_relay(int relay_id, int status_relay, char *relay_json)
{
    char key[32];
    sprintf(key, "relay_json:%d:%d", relay_id, status_relay);
    cache_insert_value(key, relay_json);
}

char*
cache_get_json_relay(int relay_id, int status_relay)
{
    char key[32];
    sprintf(key, "relay_json:%d:%d", relay_id, status_relay);
    return cache_get_value(key);
}

void
cache_invalidate_relay(int relay_id, int status_relay)
{
    if(status_relay == -1)
    {
        cache_invalidate_relay(relay_id, 0);
        cache_invalidate_relay(relay_id, 1);
        return;
    }
    char key[32];
    sprintf(key, "relay_json:%d:%d", relay_id, status_relay);
    cache_invalidate(key);

    int controller_id = relay_get_controller_id_for_relay(relay_id);
    if(controller_id)
    {
        cache_invalidate_controller(controller_id);
    }

    int *macro_ids = macro_action_get_macro_ids_with_relay(relay_id);
    for(int i = 0; macro_ids[i] != 0; ++i)
    {
        cache_invalidate_macro(macro_ids[i]);
    }
    free(macro_ids);
}



void
cache_put_json_controller(int controller_id, char *controller_json)
{
    char key[32];
    sprintf(key, "controller_json:%d", controller_id);
    cache_insert_value(key, controller_json);
}

char*
cache_get_json_controller(int controller_id)
{
    char key[32];
    sprintf(key, "controller_json:%d", controller_id);
    return cache_get_value(key);
}

void
cache_invalidate_controller(int controller_id)
{
    char key[32];
    sprintf(key, "controller_json:%d", controller_id);
    cache_invalidate(key);
}



void
cache_put_json_macro(int macro_id, char *macro_json)
{
    char key[32];
    sprintf(key, "macro_json:%d", macro_id);
    cache_insert_value(key, macro_json);
}

char*
cache_get_json_macro(int macro_id)
{
    char key[32];
    sprintf(key, "macro_json:%d", macro_id);
    return cache_get_value(key);
}

void
cache_invalidate_macro(int macro_id)
{
    char key[32];
    sprintf(key, "macro_json:%d", macro_id);
    cache_invalidate(key);
}



void
cache_invalidate_tagged(int tag_id)
{
    int *schedule_ids = junction_tag_get_schedules_for_tag_id(tag_id);
    for(int i = 0; schedule_ids[i] != 0; ++i)
    {
        cache_invalidate_schedule(schedule_ids[i]);
    }
    free(schedule_ids);

    int *relay_ids = junction_tag_get_relays_for_tag_id(tag_id);
    for(int i = 0; relay_ids[i] != 0; ++i)
    {
        cache_invalidate_relay(relay_ids[i], -1);
    }
    free(relay_ids);
}