/* Use pthread.h to run background tasks such as reminder
 * aim is to avoid signals (per-process)
*/
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <glib.h>
#include "types.h"
#include "../core/context.h"

typedef struct BotTask {
    pthread_t tid;
    BotReminder *reminder;
} BotTask;

/* protected globals */
static BotCommand g_lastCommand = BOT_CMD_NONE;
static BotReminder *g_tmp_lastReminder = NULL;

/* store a thread per user */
static GHashTable* g_taskTable = NULL;

BotCommand bot_get_last_command()
{
    return g_lastCommand;
}

void bot_set_last_command(const BotCommand cmd)
{
    g_lastCommand = cmd;
}

BotReminder *bot_get_last_reminder()
{
    BotReminder *reminder = malloc(sizeof *reminder); 

    if(g_tmp_lastReminder) {
        /* deep copy, but we don't care about from */
        reminder->minutes = g_tmp_lastReminder->minutes;
        reminder->from = g_string_new("");
        /* free_bot_reminder(g_tmp_lastReminder); /\* can only be used once *\/ */
    }

    return reminder;
}

void bot_set_last_reminder(const unsigned int minutes)
{
    BotReminder *reminder;
    if(g_tmp_lastReminder) {
        fprintf(stderr, "WARNING: overwriting reminder. This should not happen.\n");
        free_bot_reminder(g_tmp_lastReminder); /* can only be used once */
    }
    reminder = malloc(sizeof *reminder);
    reminder->minutes = minutes;
    reminder->from = g_string_new(""); /* will be set afterwards */
    g_tmp_lastReminder = reminder;
}

void free_bot_task (BotTask * data)
{
    /* cancel and wait for the thread to finish */
    pthread_cancel(data->tid);
    pthread_join(data->tid, NULL);

    if(data->reminder) {
        free_bot_reminder(data->reminder);
    }
    free(data);
}

void bot_task_table_init()
{
    if (g_taskTable) {
        fprintf(stderr, "Programmer error: User table has already been initialized. Won't do it again.");
        return;
    }

    g_taskTable = g_hash_table_new(g_str_hash, g_str_equal);
}

void bot_task_table_insert(gchar * const key, BotTask * const value)
{
    BotTask * v;

    if (!g_taskTable) {
        bot_task_table_init();
    }

    if ((v = g_hash_table_lookup(g_taskTable, key))) { /* value already present */
        free_bot_task(v);
    }

    g_hash_table_insert(g_taskTable, key, value);
}

void *bot_reminder_task(void *arg)
{
    const BotReminder * const reminder = (const BotReminder * const)arg;
    BotXmppCtx * bctx = bot_get_ctx();
    GString *msg = g_string_new(NULL);
    g_string_printf(msg, "OHI! Segna le spese! Te lo ricordo di nuovo tra %d minuti.\n", reminder->minutes);

    while (1) {
        sleep(reminder->minutes * 60); // only this thread waits

        /* send a message */
        bctx = bot_get_ctx(); // TODO needs a lock
        send_message(bctx->conn, reminder->from, bctx->ctx, msg);
    }
}

/* start a thread and update task table */
int bot_start_task (BotReminder *reminder)
{
    pthread_t tid;
    BotTask *task;

    if ((task = g_hash_table_lookup(g_taskTable, reminder->from->str))) { /* value already present, stop reminder */
        free_bot_task(task);
    }

    task = malloc(sizeof *task);

    if(pthread_create(&tid, NULL, bot_reminder_task, reminder) != 0) {
        fprintf(stderr, "Unable to start thread. No reminder will be set.");
        return -1;
    }

    task->tid = tid;
    task->reminder = reminder;

    bot_task_table_insert(reminder->from->str, task);
    return 0;
}

void bot_cancel_task(const gchar * const user)
{
    BotTask *task;
    if ((task = g_hash_table_lookup(g_taskTable, user))) { /* value already present, stop reminder */
        free_bot_task(task);
    }
    g_hash_table_remove(g_taskTable, user);
}
