#include <stdio.h>

#include <assert.h>

#include <mysql.h>

#include "mx_log.h"

#include "mxq_group.h"
#include "mxq_job.h"
#include "mx_util.h"
#include "mx_mysql.h"

#define GROUP_FIELDS_CNT 36
#define GROUP_FIELDS \
            " group_id," \
            " group_name," \
            " group_status," \
            " group_flags," \
            " group_priority," \
            " group_blacklist," \
            " group_whitelist," \
            " prerequisites," \
            " tags," \
            " user_uid," \
            " user_name," \
            " user_gid," \
            " user_group," \
            " job_command," \
            " job_threads," \
            " job_memory," \
            " job_time," \
            " job_tmpdir_size," \
            " job_max_per_node," \
            " job_gpu," \
            " group_jobs," \
            " group_jobs_inq," \
            " group_jobs_running," \
            " group_jobs_finished," \
            " group_jobs_failed," \
            " group_jobs_cancelled," \
            " group_jobs_unknown," \
            " group_slots_running," \
            " stats_max_sumrss," \
            " stats_max_maxrss," \
            " stats_max_utime_sec," \
            " stats_max_stime_sec," \
            " stats_max_real_sec," \
            " stats_wait_sec," \
            " stats_run_sec," \
            " stats_idle_sec"


static void bind_result_group_fields(struct mx_mysql_bind *result, struct mxq_group *g)
{
    int idx = 0;

    mx_mysql_bind_init_result(result, GROUP_FIELDS_CNT);

    mx_mysql_bind_var(result, idx++, uint64, &(g->group_id));
    mx_mysql_bind_var(result, idx++, string, &(g->group_name));
    mx_mysql_bind_var(result, idx++, uint8,  &(g->group_status));
    mx_mysql_bind_var(result, idx++, uint64, &(g->group_flags));
    mx_mysql_bind_var(result, idx++, uint16, &(g->group_priority));
    mx_mysql_bind_var(result, idx++, string, &(g->group_blacklist));
    mx_mysql_bind_var(result, idx++, string, &(g->group_whitelist));
    mx_mysql_bind_var(result, idx++, string, &(g->prerequisites));
    mx_mysql_bind_var(result, idx++, string, &(g->tags));

    mx_mysql_bind_var(result, idx++, uint32, &(g->user_uid));
    mx_mysql_bind_var(result, idx++, string, &(g->user_name));
    mx_mysql_bind_var(result, idx++, uint32, &(g->user_gid));
    mx_mysql_bind_var(result, idx++, string, &(g->user_group));

    mx_mysql_bind_var(result, idx++, string, &(g->job_command));

    mx_mysql_bind_var(result, idx++, uint16, &(g->job_threads));
    mx_mysql_bind_var(result, idx++, uint64, &(g->job_memory));
    mx_mysql_bind_var(result, idx++, uint32, &(g->job_time));
    mx_mysql_bind_var(result, idx++, uint32, &(g->job_tmpdir_size));

    mx_mysql_bind_var(result, idx++, uint16, &(g->job_max_per_node));
    mx_mysql_bind_var(result, idx++, uint16, &(g->job_gpu));

    mx_mysql_bind_var(result, idx++, uint64, &(g->group_jobs));
    mx_mysql_bind_var(result, idx++, uint64, &(g->group_jobs_inq));
    mx_mysql_bind_var(result, idx++, uint64, &(g->group_jobs_running));
    mx_mysql_bind_var(result, idx++, uint64, &(g->group_jobs_finished));
    mx_mysql_bind_var(result, idx++, uint64, &(g->group_jobs_failed));
    mx_mysql_bind_var(result, idx++, uint64, &(g->group_jobs_cancelled));
    mx_mysql_bind_var(result, idx++, uint64, &(g->group_jobs_unknown));

    mx_mysql_bind_var(result, idx++, uint64, &(g->group_slots_running));

    mx_mysql_bind_var(result, idx++, uint64, &(g->stats_max_sumrss));
    mx_mysql_bind_var(result, idx++, uint64, &(g->stats_max_maxrss));
    mx_mysql_bind_var(result, idx++, int64, &(g->stats_max_utime.tv_sec));
    mx_mysql_bind_var(result, idx++, int64, &(g->stats_max_stime.tv_sec));
    mx_mysql_bind_var(result, idx++, int64, &(g->stats_max_real.tv_sec));

    mx_mysql_bind_var(result, idx++, uint64, &(g->stats_wait_sec));
    mx_mysql_bind_var(result, idx++, uint64, &(g->stats_run_sec));
    mx_mysql_bind_var(result, idx++, uint64, &(g->stats_idle_sec));
}

void mxq_group_free_content(struct mxq_group *g)
{
        mx_free_null(g->prerequisites);
        mx_free_null(g->tags);
        mx_free_null(g->group_whitelist);
        mx_free_null(g->group_blacklist);
        mx_free_null(g->group_name);
        mx_free_null(g->user_name);
        mx_free_null(g->user_group);
        mx_free_null(g->job_command);
}


static uint64_t mxq_group_jobs_done(struct mxq_group *g)
{
    uint64_t done = 0;

    done += g->group_jobs_finished;
    done += g->group_jobs_failed;
    done += g->group_jobs_cancelled;
    done += g->group_jobs_unknown;

    return done;
}

uint64_t mxq_group_jobs_active(struct mxq_group *g)
{
    uint64_t active;

    active  = g->group_jobs;
    active -= mxq_group_jobs_done(g);

    if (active != g->group_jobs_inq+g->group_jobs_running)
        mx_log_warning("BUG: mxq_group: inconsistent 'active'=%lu (inq=%lu+run=%lu)=%lu value",
            active, g->group_jobs_inq, g->group_jobs_running, g->group_jobs_inq+g->group_jobs_running);

    return active;
}

uint64_t mxq_group_jobs_inq(struct mxq_group *g)
{
    uint64_t inq;

    inq  = mxq_group_jobs_active(g);
    inq -= g->group_jobs_running;

    if (inq != g->group_jobs_inq)
        mx_log_warning("BUG: mxq_group: inconsistent inq value (%lu != %lu)",
            inq, g->group_jobs_inq);

    return inq;
}

int mxq_load_group(struct mx_mysql *mysql, struct mxq_group **mxq_groups, uint64_t group_id)
{
    int res;
    struct mxq_group *groups = NULL;
    struct mxq_group g = {0};
    struct mx_mysql_bind param = {0};
    struct mx_mysql_bind result = {0};

    assert(mysql);
    assert(mxq_groups);
    assert(!(*mxq_groups));

    char *query =
            "SELECT"
                GROUP_FIELDS
            " FROM mxq_group"
            " WHERE group_id = ?"
            " LIMIT 1";

    mx_mysql_bind_init_param(&param, 1);
    mx_mysql_bind_var(&param, 0, uint64, &group_id);

    bind_result_group_fields(&result, &g);

    res = mx_mysql_do_statement(mysql, query, &param, &result, &g, (void **)&groups, sizeof(*groups));
    if (res < 0) {
        mx_log_err("mx_mysql_do_statement(): %s", mx_mysql_error());
        return res;
    }

    *mxq_groups = groups;
    return res;
}

int mxq_load_all_groups(struct mx_mysql *mysql, struct mxq_group **mxq_groups)
{
    int res;
    struct mxq_group *groups = NULL;
    struct mxq_group g = {0};
    struct mx_mysql_bind result = {0};

    assert(mysql);
    assert(mxq_groups);
    assert(!(*mxq_groups));

    char *query =
            "SELECT"
                GROUP_FIELDS
            " FROM mxq_group"
            " ORDER BY user_name, group_mtime";

    bind_result_group_fields(&result, &g);

    res = mx_mysql_do_statement(mysql, query, NULL, &result, &g, (void **)&groups, sizeof(*groups));
    if (res < 0) {
        mx_log_err("mx_mysql_do_statement(): %s", mx_mysql_error());
        return res;
    }

    *mxq_groups = groups;
    return res;
}

int mxq_load_all_groups_for_user(struct mx_mysql *mysql, struct mxq_group **mxq_groups, uint64_t user_uid)
{
    int res;
    struct mxq_group *groups = NULL;
    struct mxq_group g = {0};
    struct mx_mysql_bind param = {0};
    struct mx_mysql_bind result = {0};

    assert(mysql);
    assert(mxq_groups);
    assert(!(*mxq_groups));

    char *query =
            "SELECT"
                GROUP_FIELDS
            " FROM mxq_group"
            " WHERE user_uid = ?"
            " ORDER BY user_name, group_mtime";

    mx_mysql_bind_init_param(&param, 1);
    mx_mysql_bind_var(&param, 0, uint64, &user_uid);

    bind_result_group_fields(&result, &g);

    res = mx_mysql_do_statement(mysql, query, &param, &result, &g, (void **)&groups, sizeof(*groups));
    if (res < 0) {
        mx_log_err("mx_mysql_do_statement(): %s", mx_mysql_error());
        return res;
    }

    *mxq_groups = groups;
    return res;
}

int mxq_load_active_groups_for_user(struct mx_mysql *mysql, struct mxq_group **mxq_groups, uint64_t user_uid)
{
    int res;
    struct mxq_group *groups = NULL;
    struct mxq_group g = {0};
    struct mx_mysql_bind result = {0};
    struct mx_mysql_bind param = {0};

    assert(mysql);
    assert(mxq_groups);
    assert(!(*mxq_groups));

    char *query =
            "SELECT"
                GROUP_FIELDS
            " FROM mxq_group"
            " WHERE ((group_jobs_inq > 0 OR group_jobs_running > 0)"
            "    OR (NOW()-group_date_end < 86400))"
            "   AND user_uid = ?"
            " ORDER BY user_name, group_mtime";

    mx_mysql_bind_init_param(&param, 1);
    mx_mysql_bind_var(&param, 0, uint64, &user_uid);

    bind_result_group_fields(&result, &g);

    res = mx_mysql_do_statement(mysql, query, &param, &result, &g, (void **)&groups, sizeof(*groups));
    if (res < 0) {
        mx_log_err("mx_mysql_do_statement(): %s", mx_mysql_error());
        return res;
    }

    *mxq_groups = groups;
    return res;
}

int mxq_load_running_groups(struct mx_mysql *mysql, struct mxq_group **mxq_groups)
{
    int res;
    struct mxq_group *groups = NULL;
    struct mxq_group g = {0};
    struct mx_mysql_bind result = {0};

    assert(mysql);
    assert(mxq_groups);
    assert(!(*mxq_groups));

    char *query =
            "SELECT"
                GROUP_FIELDS
            " FROM mxq_group"
            " WHERE (group_jobs_inq > 0 OR group_jobs_running > 0)"
            " ORDER BY user_name, group_mtime";

    bind_result_group_fields(&result, &g);

    res = mx_mysql_do_statement(mysql, query, NULL, &result, &g, (void **)&groups, sizeof(*groups));
    if (res < 0) {
        mx_log_err("mx_mysql_do_statement(): %s", mx_mysql_error());
        return res;
    }

    *mxq_groups = groups;
    return res;
}

int mxq_load_running_groups_for_user(struct mx_mysql *mysql, struct mxq_group **mxq_groups, uint64_t user_uid)
{
    int res;
    struct mxq_group *groups = NULL;
    struct mxq_group g = {0};
    struct mx_mysql_bind param = {0};
    struct mx_mysql_bind result = {0};

    assert(mysql);
    assert(mxq_groups);
    assert(!(*mxq_groups));

    char *query =
            "SELECT"
                GROUP_FIELDS
            " FROM mxq_group"
            " WHERE (group_jobs_inq > 0 OR group_jobs_running > 0)"
            "   AND user_uid = ?"
            " ORDER BY user_name, group_mtime";

    mx_mysql_bind_init_param(&param, 1);
    mx_mysql_bind_var(&param, 0, uint64, &user_uid);

    bind_result_group_fields(&result, &g);

    res = mx_mysql_do_statement(mysql, query, &param, &result, &g, (void **)&groups, sizeof(*groups));
    if (res < 0) {
        mx_log_err("mx_mysql_do_statement(): %s", mx_mysql_error());
        return res;
    }

    *mxq_groups = groups;
    return res;
}