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

#include <logger.h>
#include <helpers.h>
#include <database.h>
#include <models/relay.h>
#include <models/junction_relay_schedule.h>

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

    sqlite3_bind_int(stmt, 1, relay->id);
    sqlite3_bind_int(stmt, 2, relay->number);
    sqlite3_bind_text(stmt, 3, relay->name, -1, SQLITE_STATIC);

    rc = sqlite3_step(stmt);

    sqlite3_finalize(stmt);

    return rc != SQLITE_DONE;
}
static relay_t*
relay_db_select_mapper(sqlite3_stmt *stmt)
{
    relay_t *new_relay = malloc(sizeof(relay_t));
    new_relay->is_on = 0;

    for(int i = 0; i < sqlite3_column_count(stmt); i++)
    {
        const char *name = sqlite3_column_name(stmt, i);
        switch(name[0])
        {
            case 'i':
                new_relay->id = sqlite3_column_int(stmt, i);
                break;
            case 'n':
                switch(name[1])
                {
                    case 'a': // name
                        strncpy(new_relay->name, (const char*)sqlite3_column_text(stmt, i), 127);
                        break;
                    case 'u': // number
                        new_relay->number = sqlite3_column_int(stmt, i);
                        break;
                    default:
                        break;
                }
                break;
            default: // ignore columns not implemented
                break;
        }
    }
    
    memset(new_relay->schedules, 0, sizeof(schedule_t*) * 7);
    relay_reload_schedules(new_relay);

    new_relay->is_on = -1;
    new_relay->is_on_schedule = -1;
    new_relay->pulse_timer = 0;
    new_relay->sent_to_broker = 0;

    return new_relay;
}

static relay_t**
relay_db_select(sqlite3_stmt *stmt)
{
    relay_t **all_relays = malloc(sizeof(relay_t*));

    int row = 0;

    while(true)
    {
        int s;

        s = sqlite3_step(stmt);
        if (s == SQLITE_ROW)
        {
            relay_t *new_relay = relay_db_select_mapper(stmt);
            row++;

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

int
relay_save(relay_t *relay)
{
    int opened_transaction = database_transaction_begin();
     
    sqlite3_stmt *stmt;
    if(relay->id)
    {
        sqlite3_prepare_v2(global_database, "UPDATE relays set number = ?2, name = ?3 WHERE id = ?1;", -1, &stmt, NULL);
    }
    else
    {
        sqlite3_prepare_v2(global_database, "INSERT INTO relays(number, name) values (?2, ?3);", -1, &stmt, NULL);
    }

    int result = db_update_insert(relay, stmt);

    if(result)
    {
        if(relay->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(relay->id == 0)
        {
            relay->id = sqlite3_last_insert_rowid(global_database);
            LOGGER_DEBUG("new relay - new id: %d\n", relay->id);
        }

        LOGGER_DEBUG("cleaning relay_schedule junction\n");
        junction_relay_schedule_remove_for_relay(relay->id);

        LOGGER_DEBUG("rebuilding relay_schedule junction\n");
        int schedule_ids[7];
        for(int i = 0; i < 7; ++i)
        {
            schedule_ids[i] = relay->schedules[i]->id;
        }
        junction_relay_schedule_insert_weekdays(relay->id, schedule_ids);

        if(opened_transaction)
        {
            database_transaction_commit();
        }
    }

    return result;
}


relay_t*
relay_create(uint8_t number)
{
    relay_t *new_relay = malloc(sizeof(relay_t));

    new_relay->id = 0;
    new_relay->number = number;
    new_relay->name[0] = '\0';

    new_relay->is_on = -1;
    new_relay->is_on_schedule = -1;
    new_relay->pulse_timer = 0;
    new_relay->sent_to_broker = 0;

    uuid_t off_id;
    memset(off_id, 0, sizeof(uuid_t));
    memcpy(off_id, "off", 3);

    for(int i = 0; i < 7; ++i)
    {
        new_relay->schedules[i] = schedule_get_by_uid(off_id);
    }

    return new_relay;
}

relay_t*
relay_load(uint8_t number)
{
    sqlite3_stmt *stmt;

    sqlite3_prepare_v2(global_database, "SELECT * FROM relays WHERE number = ?1;", -1, &stmt, NULL);
    sqlite3_bind_int(stmt, 1, number);

    relay_t **sql_result = relay_db_select(stmt);

    relay_t *result = sql_result[0];
    free(sql_result);

    return result;
}

void
relay_reload_schedules(relay_t *relay)
{
    schedule_t **schedules = schedule_get_relay_weekdays(relay->id);

    uuid_t off_id;
    memset(off_id, 0, sizeof(uuid_t));
    memcpy(off_id, "off", 3);

    int fill_with_off = 0;
    for(int i = 0; i < 7; ++i)
    {
        if(schedules[i] == NULL || fill_with_off)
        {
            LOGGER_WARNING("got only %d/7 schedules for relay_id %d\n", i, relay->id);
            relay->schedules[i] = schedule_get_by_uid(off_id);

            fill_with_off = 1;
        }
        else
        {
            if(relay->schedules[i])
            {
                schedule_free(relay->schedules[i]);
            }
            relay->schedules[i] = schedules[i];
        }
    }

    free(schedules); // don't free list, because contents are kept in relay->schedules
}

void
relay_set_name(relay_t *relay, const char *name)
{
    strncpy(relay->name, name, MAX_NAME_LENGTH);
    relay->name[MAX_NAME_LENGTH] = '\0';
}

int
relay_is_on_schedule(relay_t *relay, struct tm *time_struct)
{
    schedule_t *schedule = relay->schedules[helper_get_weekday(time_struct)];
    if(schedule->periods_count == 0)
    {
        return 0;
    }

    for(uint16_t i = 0; i < schedule->periods_count; ++i)
    {
        if(period_includes_time(schedule->periods[i], time_struct))
        {
            return 1;
        }
    }
    return 0;
}

void
relay_debug(relay_t *relay)
{
    if(relay == NULL)
    {
        LOGGER_DEBUG("relay is NULL\n");
        return;
    }
    LOGGER_DEBUG("(1/3) %3d @ %p\n", relay->number, (void*)relay);
    LOGGER_DEBUG("(2/3) id: %3d; name: %s\n", relay->id, relay->name);
    LOGGER_DEBUG("(3/3) schedules @ %p:\n", (void*)relay->schedules);
    for(int i = 0; i < 7; ++i)
    {
        schedule_debug(relay->schedules[i]);
    }
}

void
relay_free(relay_t *relay)
{
    for(int i = 0; i < 7; ++i)
    {
        schedule_free(relay->schedules[i]);
    }
    free(relay);
}