#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/resource.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include "outlog.h"
#include "fileops.h"
#include "mjson.h"

#define BASE_JSON_PATH "/global/info/outlog/markdown/outdir"
#define DEFAULT_SAVEDIR "/mnt/UDISK/md"
#define DEFAULT_RESULT "/mnt/UDISK/md/result-list"
#define DEFAULT_REPORT_NAME "report.md"
#define PREFIX_LOG "log"
#define PREFIX_ATTR "attr"
#define SUFFIX_MD ".md"

#define TITLE_OVERVIEW "overview"
#define TITLE_ATTRIBUTE "attribute"
#define TITLE_INPUT "input"
#define TITLE_RESOURCE "resource"
#define TITLE_LOG "log"

#define RESULT_PASS "<font color=green>Pass</font>"
#define RESULT_FAILED "**<font color=red>Failed</font>**"

static char *outdir;

// get base dir
static char *md_get_outdir(void)
{
    char *outdir = NULL;
    if ((outdir = mjson_fetch_string(BASE_JSON_PATH)) == NULL)
        outdir = DEFAULT_SAVEDIR;

    if (outdir[strlen(outdir) - 1] == '/')
        outdir[strlen(outdir) - 1] = '\0';

    return outdir;
}

// get outlog and outattr
static int md_get_outname(char *keypath, char *outlog, int loglen, char *outattr, int attrlen)
{
    int ret = -1;
    if (keypath == NULL) {
        ERROR("keypath is NULL\n");
        goto out;
    }

    char *p = NULL;

    // outlog
    snprintf(outlog, loglen, PREFIX_LOG);
    p = outlog + strlen(PREFIX_LOG);
    strncpy(p, keypath, loglen - strlen(PREFIX_LOG));
    while ((p = strchr(p, (int)'/')) != NULL)
        *p = (char)'-';
    p = outlog + strlen(outlog);
    strncpy(p, SUFFIX_MD, strlen(SUFFIX_MD));

    // outattr
    snprintf(outattr, attrlen, PREFIX_ATTR);
    p = outattr + strlen(PREFIX_ATTR);
    strncpy(p, keypath, attrlen - strlen(PREFIX_ATTR));
    while ((p = strchr(p, (int)'/')) != NULL)
        *p = (char)'-';
    p = outattr + strlen(outattr);
    strncpy(p, SUFFIX_MD, strlen(SUFFIX_MD));

    ret = 0;
out:
    return ret;
}

static inline char *strtoupper(char *str)
{
    int len = strlen(str);
    for (int num = 0; num < len; num++)
        str[num] = toupper(str[num]);

    return str;
}

static int md_get_result(const char *keypath)
{
    int ret = -1;
    if (is_existed(DEFAULT_RESULT) == false) {
        ERROR("%s is non-existent\n", DEFAULT_RESULT);
        goto err;
    }

    FILE *fp = fopen(DEFAULT_RESULT, "r");
    if (fp == NULL) {
        ERROR("open %s failed\n", DEFAULT_RESULT);
        goto err;
    }

    char *line = NULL, *p = NULL;
    size_t len = 0;
    while (getline(&line, &len, fp) != -1) {
        p = strchr(line, ' ');
        *p = '\0';
        if (strcmp(keypath, line) == 0) {
            p++;
            ret = atoi(p);
            break;
        }
    }

    free(line);
    fclose(fp);
err:
    return ret;
}

static int md_save_result(const char *keypath, int result)
{
    int ret = -1;
    FILE *fp = fopen(DEFAULT_RESULT, "a");
    if (fp == NULL) {
        ERROR("open %s failed\n", DEFAULT_RESULT);
        goto err;
    }
    fprintf(fp, "%s %d\n", keypath, result);
    fclose(fp);

    ret = 0;
err:
    return ret;
}

static int md_out_log(struct task *task, char *path)
{
    int ret = -1;
    char *line = NULL;
    size_t len = 0, bytes = 0;
    FILE *fpout = NULL, *fpin = NULL;

    fpout = fopen(path, "w");
    if (fpout == NULL) {
        ERROR("open %s failed - %s\n", path, strerror(errno));
        goto err;
    }
    fpin = fopen(task->logpath, "r");
    if (fpin == NULL) {
        ERROR("open %s failed - %s\n", task->logpath, strerror(errno));
        goto err;
    }

    char *buf = malloc(sizeof(TITLE_LOG) + 3);
    if (buf == NULL) {
        ERROR("malloc failed - %s\n", strerror(errno));
        goto err;
    }
    memcpy(buf, TITLE_LOG, sizeof(TITLE_LOG));

    // title
    fprintf(fpout, "### %s\n\n", strtoupper(buf));
    free(buf);

    while ((bytes = getline(&line, &len, fpin)) != -1) {
        line[bytes - 1] = '\0';
        fprintf(fpout, "    %s  \n", line);
    }

    ret = 0;
    free(line);
err:
    fclose(fpin);
    fclose(fpout);
    return ret;
}

static inline void md_out_attr_attribute(struct task *task, FILE *fp)
{
    char *buf = malloc(sizeof(TITLE_ATTRIBUTE) + 3);
    if (buf == NULL) {
        ERROR("malloc failed - %s\n", strerror(errno));
        return;
    }
    memcpy(buf, TITLE_ATTRIBUTE, sizeof(TITLE_ATTRIBUTE));

    // title
    fprintf(fp, "### %s\n\n", strtoupper(buf));
    free(buf);

    fprintf(fp, "| class | key | value |\n");
    fprintf(fp, "| --- | --- | --- |\n");
    fprintf(fp, "| task | command | %s |\n", task->command);
    if (task->env.info.date == true) {
        struct tm *tm = gmtime(&task->begin_date);
        fprintf(fp, "| task | begin date | %d-%d-%d %d:%d:%d |\n",
                tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
                tm->tm_hour, tm->tm_min, tm->tm_sec);
        tm = gmtime(&task->end_date);
        fprintf(fp, "| task | end date | %d-%d-%d %d:%d:%d |\n",
                tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
                tm->tm_hour, tm->tm_min, tm->tm_sec);
    }
    switch (task->input_t) {
        case TASK_NO_INPUT: {
                fprintf(fp, "| task | input | none |\n");
                break;
            }
        case TASK_STDIN: {
                fprintf(fp, "| task | input | stdin |\n");
                break;
            }
        case TASK_FSTDIN: {
                fprintf(fp, "| task | input | file-stdin |\n");
                break;
            }
    }
    if (task->env.limit.may_reboot == true) {
        fprintf(fp, "| task | reboot times (real/max) | %d/%d |\n",
                task->rebooted_times, task->env.limit.run_times);
    }
    fprintf(fp, "| info | date | %s |\n",
            task->env.info.date == true ? "true" : "false" );
    fprintf(fp, "| info | resource | %s |\n",
            task->env.info.resource == true ? "true" : "false" );
    fprintf(fp, "| info | real_time_log | %s |\n",
            task->env.info.real_time_log == true ? "true" : "false" );
    fprintf(fp, "| limit | run_times (real/max) | %d/%d |\n",
            task->run_times, task->env.limit.run_times);
    fprintf(fp, "| limit | run_alone | %s |\n",
            task->env.limit.run_alone == true ? "true" : "false" );
    fprintf(fp, "| limit | run_parallel | %s |\n",
            task->env.limit.run_parallel == true ? "true" : "false" );
    fprintf(fp, "| limit | may_reboot | %s |\n",
            task->env.limit.may_reboot == true ? "true" : "false" );
    fprintf(fp, "| limit | testcase_run_once_time | %d |\n",
            task->env.limit.testcase_run_once_time_sec);
    fprintf(fp, "\n");
}

static void md_out_attr_overview(struct task *task, FILE *fp)
{
    int result = 0;
    struct tm *time;
    char *buf = malloc(sizeof(TITLE_OVERVIEW) + 3);
    if (buf == NULL) {
        ERROR("malloc failed - %s\n", strerror(errno));
        return;
    }
    memcpy(buf, TITLE_OVERVIEW, sizeof(TITLE_OVERVIEW));

    // title
    fprintf(fp, "### %s\n\n", strtoupper(buf));
    free(buf);

    fprintf(fp, "| num | pid | pgid | return | begin | end | killed by signal |\n");
    fprintf(fp, "| :---: | :---: | :---: | :---: | :---: | :---: | :---: |\n");
    for (int num = 0; num < task->run_times; num++) {
        fprintf(fp, "| %d | %d | %d | %d |", num, -task->pid[num],
                abs(task->shmem->pgid), task->result[num]);

        time = gmtime(&task->begin_time[num]);
        fprintf(fp, " %2.2d:%2.2d:%2.2d |", time->tm_hour, time->tm_min, time->tm_sec);

        time = gmtime(&task->end_time[num]);
        fprintf(fp, " %2.2d:%2.2d:%2.2d |", time->tm_hour, time->tm_min, time->tm_sec);

        if (task->killed[num])
            fprintf(fp, " %s(%d) |\n",
                    strsignal(task->killed[num]), task->killed[num]);
        else
            fprintf(fp, " none |\n");

        // get result
        if (task->result[num] != 0)
            result = task->result[num];
    }
    fprintf(fp, "\n");

    md_save_result(task->keypath, result);
}

static void md_out_attr_resource(struct task *task, FILE *fp)
{
    char *buf = malloc(sizeof(TITLE_RESOURCE) + 3);
    if (buf == NULL) {
        ERROR("malloc failed - %s\n", strerror(errno));
        return;
    }
    memcpy(buf, TITLE_RESOURCE, sizeof(TITLE_RESOURCE));

    // title
    fprintf(fp, "### %s\n\n", strtoupper(buf));
    free(buf);

    fprintf(fp, "| item | value |\n");
    fprintf(fp, "| ---- | ---- |\n");
    fprintf(fp, "| user cpu time | %ld.%ld |\n",
            task->res->ru_utime.tv_sec, task->res->ru_utime.tv_usec/1000);
    fprintf(fp, "| system cpu time | %ld.%ld |\n",
            task->res->ru_stime.tv_sec, task->res->ru_stime.tv_usec/1000);
    fprintf(fp, "| maximum resident size | %ldkB |\n",
            task->res->ru_maxrss);
    fprintf(fp, "| page faults break times (without I/O) | %ld |\n",
            task->res->ru_minflt);
    fprintf(fp, "| page faults break times (with I/O) | %ld |\n",
            task->res->ru_majflt);
    fprintf(fp, "| input times | %ld |\n", task->res->ru_inblock);
    fprintf(fp, "| output times | %ld |\n", task->res->ru_oublock);
    fprintf(fp, "| wait resource actively times | %ld |\n", task->res->ru_nvcsw);
    fprintf(fp, "| wait resource passively times | %ld |\n", task->res->ru_nivcsw);
    fprintf(fp, "\n");
}

static void md_out_attr_input(struct task *task, FILE *fp)
{
    char *buf = malloc(sizeof(TITLE_INPUT) + 3);
    if (buf == NULL) {
        ERROR("malloc failed - %s\n", strerror(errno));
        return;
    }
    memcpy(buf, TITLE_INPUT, sizeof(TITLE_INPUT));

    // title
    fprintf(fp, "### %s\n\n", strtoupper(buf));
    free(buf);

    switch (task->input_t) {
        case TASK_NO_INPUT: {
            break;
        }
        case TASK_STDIN: {
            char **p = (char **)task->input;
            int cnt = atoi(p[0]);
            for (int num = 1; num <= cnt; num++)
                fprintf(fp, "    %s  \n", p[num]);
            break;
        }
        case TASK_FSTDIN: {
            char *line = NULL;
            size_t len = 0, bytes = 0;
            FILE *fin = fopen((char *)task->input, "r");
            if (fin != NULL) {
                while ((bytes = getline(&line, &len, fin)) != -1) {
                    line[bytes - 1] = '\0';
                    fprintf(fp, "    %s  \n", line);
                }
            } else {
                fprintf(fp, "ERROR");
                ERROR("open %s failed - %s\n", (char *)task->input, strerror(errno));
            }
            free(line);
            fclose(fin);
            break;
        }
    }
    fprintf(fp, "\n");
}

static int md_out_attr(struct task *task, char *path)
{
    int ret = -1;
    FILE *fp = NULL;
    fp = fopen(path, "w");
    if (fp == NULL) {
        ERROR("open %s failed - %s\n", path, strerror(errno));
        goto err;
    }

    // title
    fprintf(fp, "## %s\n", task->keypath);

    // result
    md_out_attr_overview(task, fp);
    // attr
    md_out_attr_attribute(task, fp);
    // resource
    if (task->env.info.resource == true
            && task->res != NULL) {
        md_out_attr_resource(task, fp);
    }
    // input
    if (task->input_t != TASK_NO_INPUT) {
        md_out_attr_input(task, fp);
    }

    ret = 0;
err:
    fclose(fp);
    return ret;
}

static inline void md_del_char(char *s, int c)
{
    int j,k;
    for(j=k=0; s[j]!='\0'; j++)
        if(s[j]!=(char )c)
            s[k++]=s[j];
    s[k]= '\0';
}

static int md_report_detail(struct list_head *TASK_LIST, FILE *fp)
{
    int ret = -1;
    char *outlog = NULL, *outattr = NULL, *path = NULL;
    int outlen = 1000;
    int len = strlen(outdir) + outlen;
    int from = -1, to = fileno(fp);

    outlog = calloc(1, outlen);
    outattr = calloc(1, outlen);
    path = calloc(1, len);
    if (outlog == NULL || outattr == NULL || path == NULL) {
        ERROR("malloc failed - %s\n", strerror(errno));
        goto err;
    }

    fprintf(fp, "# DETAIL\n");
    struct task *task = NULL;
    list_for_each_entry(task, TASK_LIST, lnode) {
        if (md_get_outname(task->keypath, outlog, outlen, outattr, outlen) < 0)
            continue;

        // outattr
        snprintf(path, len, "%s/%s", outdir, outattr);
        if (is_existed(path) == false) {
            ERROR("not found %s\n", path);
            goto err;
        }
        from = open(path, O_RDONLY);
        if (from < 0) {
            ERROR("open %s failed - %s\n", path, strerror(errno));
            goto err;
        }

        fprintf(fp, "\n");
        fflush(fp);
        cp_fd(from, to);
        close(from);

        // outlog
        snprintf(path, len, "%s/%s", outdir, outlog);
        if (is_existed(path) == false) {
            ERROR("not found %s\n", path);
            goto err;
        }
        from = open(path, O_RDONLY);
        if (from < 0) {
            ERROR("open %s failed - %s\n", path, strerror(errno));
            goto err;
        }

        fprintf(fp, "\n");
        fflush(fp);
        cp_fd(from, to);
        close(from);

        from = -1;
    }

    ret = 0;
err:
    free(path);
    free(outlog);
    free(outattr);
    close(from);
    return ret;
}

static int md_report_summary(struct list_head *TASK_LIST, FILE *fp)
{
    int result = 0;
    int cnt = 0;
    int ret = -1;
    int len = 1000;
    char *suffix = NULL, *link = NULL;

    suffix = malloc(len);
    link = malloc(len);
    if (suffix == NULL || link == NULL) {
        ERROR("malloc failed - %s\n", strerror(errno));
        goto err;
    }

    fprintf(fp, "# REPORT\n\n");
    fprintf(fp, "| testcase | result | overview | attribute | resource | input | log |\n");
    fprintf(fp, "| :--- | :---: | :---: | :---: | :---: | :---: | :---: |\n");

    struct task *task = NULL;
    list_for_each_entry(task, TASK_LIST, lnode) {
        // get result
        // we can't use task->result because it was reset after rebooting
        result = md_get_result(task->keypath);
        DEBUG(DATA, "%s: result is %d\n", task->keypath, result);

        // init suffix
        if (cnt == 0)
            *suffix = '\0';
        else
            snprintf(suffix, len, "-%d", cnt);

        // init link
        snprintf(link, len, "%s", task->keypath);
        md_del_char(link, '/');

        // testcase && result && overview && attribute
        fprintf(fp, "| [%s](#%s) | %s | [view](#%s%s) | [attr](#%s%s) |",
                task->keypath, link, result == 0 ? RESULT_PASS : RESULT_FAILED,
                TITLE_OVERVIEW, suffix,
                TITLE_ATTRIBUTE, suffix);

        // resource
        if (task->env.info.resource == true
                && task->res != NULL)
            fprintf(fp, " [res](#%s%s) |", TITLE_RESOURCE, suffix);
        else
            fprintf(fp, " none |");

        // input
        if (task->input_t != TASK_NO_INPUT)
            fprintf(fp, " [input](#%s%s) |", TITLE_INPUT, suffix);
        else
            fprintf(fp, " none |");

        // log
        fprintf(fp, " [log](#%s%s) |", TITLE_LOG, suffix);

        fprintf(fp, "\n");
        cnt++;
    }

    fprintf(fp, "\n");
    ret = 0;
err:
    free(link);
    free(suffix);
    return ret;
}

static int md_report(struct list_head *TASK_LIST, char *path)
{
    int ret = -1;
    FILE *fp = NULL;
    fp = fopen(path, "w");
    if (fp == NULL) {
        ERROR("open %s failed - %s\n", path, strerror(errno));
        goto err;
    }

    // summary
    if (md_report_summary(TASK_LIST, fp) < 0)
        ERROR("markdown: report summary failed\n");
    // detail
    if (md_report_detail(TASK_LIST, fp) < 0)
        ERROR("markdown: report detail failed\n");

    ret = 0;
err:
    fclose(fp);
    return ret;
}


int md_after_one(struct task *task)
{
    int ret = -1;
    char *path = NULL;
    char *outlog = NULL, *outattr = NULL;
    if (task == NULL) {
        ERROR("no task infomations\n");
        goto err;
    }
    int outlen = strlen(task->keypath) + 30;
    outlog = calloc(1, outlen);
    outattr = calloc(1, outlen);
    if (outlog == NULL || outattr == NULL) {
        ERROR("malloc failed - %s\n", strerror(errno));
        goto err;
    }

    DEBUG(BASE, "markdown: after one : %s\n", task->keypath);
    // init outlog outattr
    if (md_get_outname(task->keypath, outlog, outlen, outattr, outlen) < 0)
        goto err;
    DEBUG(DATA, "%s: outlog set to %s/%s\n", task->keypath, outdir, outlog);
    DEBUG(DATA, "%s: outattr set to %s/%s\n", task->keypath, outdir, outattr);

    int len = strlen(outdir) + 10 +  strlen(task->keypath) +
        (sizeof(PREFIX_LOG) > sizeof(PREFIX_ATTR) ? sizeof(PREFIX_LOG) : sizeof(PREFIX_ATTR));
    path = malloc(len);
    if (path == NULL) {
        ERROR("%s: malloc failed - %s\n", task->keypath, strerror(errno));
        goto err;
    }

    // get outattr
    snprintf(path, len, "%s/%s", outdir, outattr);
    DEBUG(BASE, "%s: print attrtbutes to %s\n", task->keypath, path);
    if (md_out_attr(task, path) < 0) {
        ERROR("%s: print attrtbutes failed\n", task->keypath);
        goto err;
    }

    // get outlog
    snprintf(path, len, "%s/%s", outdir, outlog);
    DEBUG(BASE, "%s: print log to %s\n", task->keypath, path);
    if (md_out_log(task, path) < 0) {
        ERROR("%s: print log failed\n", task->keypath);
        goto err;
    }

    DEBUG(BASE, "markdown: after one : %s exit\n", task->keypath);

    ret = 0;
err:
    free(path);
    free(outlog);
    free(outattr);
    return ret;
}

int md_after_all(struct list_head *TASK_LIST)
{
    int ret = -1;
    char *path = NULL;
    if (TASK_LIST == NULL) {
        ERROR("TASK_LIST NULL\n");
        goto err;
    }

    DEBUG(BASE, "markdown: after all\n");
    int len = strlen(outdir) + sizeof(DEFAULT_REPORT_NAME) + 5;
    path = malloc(len);
    if (path == NULL) {
        ERROR("malloc failed - %s\n", strerror(errno));
        goto err;
    }
    snprintf(path, len, "%s/%s", outdir, DEFAULT_REPORT_NAME);

    if (md_report(TASK_LIST, path) < 0) {
        ERROR("print report failed\n");
        goto err;
    }
    DEBUG(BASE, "markdown: after all exit\n");

    ret = 0;
err:
    remove(DEFAULT_RESULT);
    free(path);
    return ret;
}

void module_init(void) {
    outdir = md_get_outdir();
    DEBUG(DATA, "outlog_markdown: outdir is %s\n", outdir);
    if (mkdir_p(outdir) < 0) {
        ERROR("mkdir %s failed \n", outdir);
        ERROR("init outlog module markdown failed\n");
        return;
    }

    outlog_register(
            NULL,
            NULL,
            md_after_one,
            md_after_all);
}