#include "stats_file.hpp"
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>


typedef struct {
    msec_t last;
    stats_t last_stats;
    char* map;
} stats_file_ctx_t;

const int stats_len = stats_num * (20 + 1); // decimal 64bit unsigned can be 20 chars long


static bool stats_file_config(void*& ctx, char* value) {
    // open or create
    int fd = open(value, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); // PROT_WRITE needs O_RDWR ?
    if (fd == -1) {
        log_errno(error, "open(%s)", value);
        return false;
    }

    // truncate to correct size in any case
    if (ftruncate(fd, stats_len*2) == -1) {
        log_errno(error, "ftruncate(%s)", value);
        EINTR_RETRY(close(fd));
        return false; // TODO: unlink, too?
    }

    // map fd to mem
    void* map = mmap(NULL, stats_len*2, PROT_WRITE, MAP_SHARED, fd, 0);
    if (map == MAP_FAILED) {
        log_errno(error, "mmap(%s)", value);
        EINTR_RETRY(close(fd));
        return false;
    }

    // reset to zeroes
    memset(map, '\0', stats_len*2);
    if (msync(map, stats_len*2, MS_SYNC) == -1) {
        log_errno(error, "msync(%s)", value);
    }

    // done
    EINTR_RETRY(close(fd)); // fd not needed anymore
    log(info, "writing stats to %s", value);
    stats_file_ctx_t* c = (stats_file_ctx_t*)malloc(sizeof(stats_file_ctx_t));
    memcpy(&c->last_stats, &stats, sizeof(stats));
    c->last = NOW_MSEC;
    c->map = (char*)map;
    ctx = c;
    return true;
}
static void stats_file_unconfig(void*& ctx) {
    if (ctx) {
        if (((stats_file_ctx_t*)ctx)->map) {
            if (munmap(((stats_file_ctx_t*)ctx)->map, stats_len*2) == -1) {
                log_errno(error, "munmap()");
            }
        }
        free(ctx);
        ctx = NULL;
    }
}


CONF_DEF(config) {
    ConfigKey stats_file;
};
CONF_INIT(config) {
    CONF_KEY_INIT(stats_file, true, false, stats_file_config, stats_file_unconfig);
}


//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//


/** formats all counters into mapped memory **/
static bool write_stats() {
    stats_file_ctx_t* ctx = config? config->stats_file.ctx_as<stats_file_ctx_t>(): NULL;
    if (!ctx || !ctx->map) return true; // but try on as we could run before config was switched

    const uint64_t* s = (uint64_t*)&stats;

    // write absolute values
    char* p = ctx->map;
    for (int i=0; i<stats_num; ++i) {
        p += sprintf(p, "%" PRIu64 " ", s[i]);
    }
    p[-1] = '\n';

    // compute rates
    msec_t now = NOW_MSEC;
    msec_t delta = now - ctx->last;
    if (delta <= 0) delta = 1;
    for (int i=0; i<stats_num; ++i) {
        uint64_t d = 0;
        if (s[i] > ((uint64_t*)&ctx->last_stats)[i]) { // ofl?
            d = s[i] - ((uint64_t*)&ctx->last_stats)[i];
            d *= 1000; // per second (here, as should be no ofl and leaves more precision)
            d /= delta;
        }
        p += sprintf(p, "%" PRIu64 " ", d);
    }
    p[-1] = '\n';

    // zero out rest
    memset(p, ' ', (ctx->map + (stats_len*2)) - p);

    // backup current values for next run
    memcpy(&ctx->last_stats, &stats, sizeof(stats));
    ctx->last = now;

    // ensure it's written
    #if 1
        if (msync(((stats_file_ctx_t*)ctx)->map, stats_len*2, MS_SYNC) == -1) { // TODO: needed?
            log_errno(error, "msync()");
        }
    #endif

    return true;
}


HOOK(POST_CONFIG, HOOK_PRIO_MID) {
    if (config && config->stats_file.ctx_as<char>()) {
        Periodic::getInst()->add(&write_stats, 5);
    } else if (Periodic::hasInst()) { // don't create it if POST_IO
        Periodic::getInst()->del(&write_stats);
    }
}