/*
 * © Copyright 1996-2012 ECMWF.
 *
 * This software is licensed under the terms of the Apache Licence Version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
 * In applying this licence, ECMWF does not waive the privileges and immunities
 * granted to it by virtue of its status as an intergovernmental organisation nor
 * does it submit to any jurisdiction.
 */

#include "mars.h"

static timer* timers = NULL;

timer* get_timer(const char* name, const char* statname, boolean elapsed) {
    timer* t = timers;

    while (t) {
        if (strcmp(name, t->name_) == 0)
            return t;
        t = t->next_;
    }

    t          = NEW_CLEAR(timer);
    t->name_   = strcache(name);
    t->active_ = false;
    t->count_  = 0;
    t->timer_  = 0;
    t->total_  = 0;

    t->elapsed_   = elapsed; /* Whether to print CPU usage */
    t->cpu_       = 0;
    t->total_cpu_ = 0;

    t->statname_ = 0;
    if (statname)
        t->statname_ = strcache(statname);

    t->next_ = timers;
    timers   = t;

    return t;
}

int timer_start(timer* t) {
    int e = 0;
    if (mars.notimers || (t == NULL))
        return 0;

    e = gettimeofday(&t->start_, NULL);
    if (e != 0)
        marslog(LOG_WARN | LOG_PERR, "Error starting timer '%s'", t->name_ ? t->name_ : "unnamed");
    t->active_ = true;
    t->cpu_    = proc_cpu();
    return e;
}

int timer_stop(timer* t, long64 total) {
    struct timeval stop, diff;
    int e;
    double c;

    if (mars.notimers || (t == NULL))
        return 0;

    e = gettimeofday(&stop, NULL);
    c = proc_cpu();

    if (e != 0)
        marslog(LOG_WARN | LOG_PERR, "Error stopping timer '%s'", t->name_ ? t->name_ : "unnamed");

    if (!t->active_) {
        marslog(LOG_WARN, "Stopping non-started timer '%s'", t->name_ ? t->name_ : "unnamed");
        return 1;
    }

    diff.tv_sec  = stop.tv_sec - t->start_.tv_sec;
    diff.tv_usec = stop.tv_usec - t->start_.tv_usec;
    if (diff.tv_usec < 0) {
        diff.tv_sec--;
        diff.tv_usec += 1000000;
    }

    t->timer_ += (double)diff.tv_sec + ((double)diff.tv_usec / 1000000.);
    t->total_ += total;
    t->total_cpu_ += (c - t->cpu_);

    t->active_ = false;
    t->count_++;

    return e;
}

double timer_value(timer* t) {
    if (mars.notimers || (t == NULL))
        return 0.0;

    return t->timer_;
}

void timer_print_to_file(timer* t, FILE* f) {
    fprintf(f, "%g:%d:%s:%d:%lld:%d:%g:%g\n",
            t->timer_,
            t->active_,
            t->name_,
            t->count_,
            t->total_,
            t->elapsed_,
            t->cpu_,
            t->total_cpu_);
    /* t->statname_ */ /* Not all stats have a name */
}

void timer_print(timer* t) {
    char* name;
    if (mars.notimers) {
        marslog(LOG_WARN, "Timers are disabled");
        return;
    }
    if (t == NULL) {
        marslog(LOG_WARN, "Internal error: timer_print() called with NULL timer");
        return;
    }
    name = t->name_ ? t->name_ : "";

    marslog(LOG_DBUG, "timer_=%g:active_=%d:name_=%s:count_=%d:total_=%lld:elapsed_=%d:cpu_=%g:total_cpu_=%g",
            t->timer_,
            t->active_,
            name,
            t->count_,
            t->total_,
            t->elapsed_,
            t->cpu_,
            t->total_cpu_);

    /* Print timers longer than 1 second or those that have a rate */
    if ((t->timer_ >= 1) || (t->total_ != 0 && t->timer_ > 0)) {
        char cpu[1024] = "";

        if (!t->elapsed_ && t->total_cpu_ >= 1.0)
            sprintf(cpu, "cpu: %s", timename(t->total_cpu_));

        if (t->total_ != 0) {
            double rate = (double)t->total_ / t->timer_;
            char bytes[80];
            sprintf(bytes, "%sbyte(s)", bytename(t->total_));
            marslog(LOG_INFO, "  %s: %s in %s [%sbyte/sec] %s",
                    name, bytes, (t->timer_ >= 1) ? timename(t->timer_) : "< 1 sec", bytename(rate), cpu);
        }
        else
            marslog(LOG_INFO, "  %s: wall: %s%s", name, timename(t->timer_), cpu);

        if (t->statname_)
            log_statistics(t->statname_, "%ld", (long)t->timer_);
    }
}

void timer_partial_rate(timer* t, double start, long64 total) {
    if (mars.notimers) {
        marslog(LOG_WARN, "Timers are disabled");
        return;
    }
    if (t == NULL) {
        marslog(LOG_WARN, "Internal error: timer_partial_rate() called with NULL timer");
        return;
    }

    {
        double ptime  = t->timer_ - start;
        long64 ptotal = total;
        char* name    = t->name_ ? t->name_ : "";
        if (ptime >= 1) {
            double rate = (double)ptotal / ptime;
            char bytes[80];
            sprintf(bytes, "%sbyte(s)", bytename(ptotal));
            marslog(LOG_INFO, "  %s: %s in %s [%sbyte/sec]",
                    name, bytes, timename(ptime), bytename(rate));
        }
    }
}

void print_all_timers() {
    timer* t = timers;
    FILE* f  = NULL;

    if (mars.timers_file) {
        if ((f = fopen(mars.timers_file, "a")) == NULL) {
            marslog(LOG_WARN | LOG_PERR, "Cannot open '%s'", mars.timers_file);
            marslog(LOG_WARN, "Disable printing timers to file");
            mars.timers_file = NULL;
        }
        else
            marslog(LOG_INFO, "Printing timers to file '%s'", mars.timers_file);
    }

    while (t) {
        timer_print(t);
        if (mars.timers_file)
            timer_print_to_file(t, f);
        t = t->next_;
    }

    if (mars.timers_file) {
        long long bytes = proc_mem();
        fprintf(f, "%lld:%s\n", bytes, "Memory used");
    }
}

void reset_all_timers() {
    timer* t = timers;
    while (t) {
        t->count_     = 0;
        t->timer_     = 0;
        t->active_    = 0;
        t->total_     = 0;
        t->total_cpu_ = 0;
        t->cpu_       = 0;
        t->elapsed_   = 0;
        t             = t->next_;
    }
}


/*************************************************
 * Timed functions
 **************************************************/

int timed_fread(char* buffer, int n, int length, FILE* f, timer* t) {
    int r        = 0;
    long64 total = 0;
    timer_start(t);
    if ((r = fread(buffer, n, length, f)) > 0)
        total = r * n;

    timer_stop(t, total);

    return r;
}

int timed_wind_next(wind* w, FILE* f, char* buffer, long* length, timer* t) {
    int r        = 0;
    long64 total = 0;
    if ((r = wind_next(w, f, buffer, length, t)) == 0)
        total = *length;

    return r;
}

int timed_fwrite(char* buffer, int n, int length, FILE* f, timer* t) {
    int r        = 0;
    long64 total = 0;
    timer_start(t);
    if ((r = fwrite(buffer, n, length, f)) > 0)
        total = r * n;
    timer_stop(t, total);

    return r;
}

int timed_fclose(FILE* f, timer* t) {
    int r = 0;
    timer_start(t);
    r = fclose(f);
    timer_stop(t, 0);

    return r;
}

int timed_writetcp(void* data, char* buffer, int n, timer* t) {
    int r        = 0;
    long64 total = 0;
    timer_start(t);
    if ((r = writetcp(data, buffer, n)) > 0)
        total = r;
    timer_stop(t, total);

    return r;
}

int timed_readtcp(void* data, char* buffer, int n, timer* t) {
    int r        = 0;
    long64 total = 0;
    timer_start(t);
    if ((r = readtcp(data, buffer, n)) > 0)
        total = r;
    timer_stop(t, total);
    return r;
}

int timed_readany(FILE* f, char* buffer, long* length, timer* t) {
    int e;
    long original = *length;
    long64 total  = 0;
    timer_start(t);
    if (((e = _readany(f, buffer, length)) == NOERR) || (e == BUF_TO_SMALL))
        total = (e == BUF_TO_SMALL) ? original : *length;
    timer_stop(t, total);

    if (e != NOERR && e != BUF_TO_SMALL && e != EOF) {
        if (e == NOT_FOUND_7777)
            marslog(LOG_WARN, "Group 7777 not found by readany", e);
        else
            marslog(LOG_WARN, "Error %d returned by readany", e);
    }

    return e;
}
