/*
 * © 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 <sys/time.h>
#include <time.h>
#include "mars.h"

static void get_date_for_schedule(long* year, long* month, long* day, long* hour, long* minute, long* second) {
    char* schedule_fake_now = getenv("SCHEDULE_FAKE_NOW");
    const char* who         = user(NULL);
    time_t now;
    struct tm* t;
    time(&now);
    t = gmtime(&now);

    marslog(LOG_DBUG, "-> get_date_for_schedule");
    /* Only 'max' can fake current time in order to test the schedule */
    if ((schedule_fake_now != NULL) && EQ(who, "max")) {
        request* rnow = read_request_file(schedule_fake_now);
        *year         = atol(get_value(rnow, "year", 0));
        *month        = atol(get_value(rnow, "month", 0));
        *day          = atol(get_value(rnow, "day", 0));
        *hour         = atol(get_value(rnow, "hour", 0));
        *minute       = atol(get_value(rnow, "minute", 0));
        *second       = atol(get_value(rnow, "second", 0));
        marslog(LOG_WARN, "Date has been changed to: %d%02d%02d %02d:%02d:%02d", *year, *month, *day, *hour, *minute, *second);
        free_all_requests(rnow);
    }
    else {
        *year   = (t->tm_year + 1900);
        *month  = (t->tm_mon + 1);
        *day    = t->tm_mday;
        *hour   = t->tm_hour;
        *minute = t->tm_min;
        *second = t->tm_sec;
    }
    marslog(LOG_DBUG, "<- get_date_for_schedule");
}


static boolean user_category_allowed(request* allow, request* env) {
    request* r                   = env;
    int i                        = 0;
    const char* allowed_category = NULL;
    boolean ok                   = false;

    while ((allowed_category = get_value(allow, "category", i++)) && !ok) {
        int j           = 0;
        const char* cat = NULL;

        while ((cat = get_value(env, "category", j++)) && !ok)
            ok = EQ(cat, allowed_category);

        if (!ok)
            marslog(LOG_DBUG, "user_category_allowed did not match category '%s'", allowed_category);
        else
            marslog(LOG_DBUG, "User category '%s' allowed to retrieve products before schedule", allowed_category);
    }

    return ok;
}


static void debug_schedule() {

    if (mars.dissemination_schedule & (SCHEDULE_INFORM & SCHEDULE_INFORM_FUTURE_CHANGE)) {
        marslog(LOG_WARN, "MARS internal error: SCHEDULE_INFORM & SCHEDULE_INFORM_FUTURE_CHANGE set");
    }

    if (mars.debug) {
        if (mars.dissemination_schedule & SCHEDULE_INFORM)
            marslog(LOG_INFO, "mars.dissemination_schedule & SCHEDULE_INFORM set");
        if (mars.dissemination_schedule & SCHEDULE_INFORM_FUTURE_CHANGE)
            marslog(LOG_INFO, "mars.dissemination_schedule & SCHEDULE_INFORM_FUTURE_CHANGE set");
        if (mars.dissemination_schedule & SCHEDULE_FAIL)
            marslog(LOG_INFO, "mars.dissemination_schedule & SCHEDULE_FAIL set");
        if (mars.dissemination_schedule & SCHEDULE_LOG)
            marslog(LOG_INFO, "mars.dissemination_schedule & SCHEDULE_LOG set");
        if (mars.dissemination_schedule & SCHEDULE_MAIL)
            marslog(LOG_INFO, "mars.dissemination_schedule & SCHEDULE_MAIL set");
    }
}


boolean check_dissemination_schedule(request* user, request* env, boolean logstat) {
    static request* r                              = 0;
    request* schedule                              = NULL;
    err e                                          = NOERR;
    int i                                          = 0;
    hypercube* cube                                = new_hypercube_from_mars_request(user);
    long release_seconds                           = -1;
    request* max_schedule                          = NULL;
    request* allow                                 = NULL;
    boolean allowed                                = false;
    boolean dont_log_users_product_before_schedule = (getenv("MARS_DONT_LOG_USERS_PRODUCT_BEFORE_SCHEDULE") != NULL);
    char buf[80];


    start_timer();
    marslog(LOG_DBUG, "Enter 'check_dissemination_schedule'");

    debug_schedule();

    if (!r)
        r = read_request_file(mars.dissemination_schedule_file);

    allow = empty_request("allow");
    set_value(allow, "category", "product_before_schedule");

    if (!r) {
        FILE* f = mail_open(mars.authmail, "Error while reading MARS schedule");
        mail_msg(f, "Error while reading MARS schedule from '%s'", mars.dissemination_schedule_file);
        mail_request(f, "User:", env);
        mail_request(f, "User request:", user);
        mail_close(f);

        marslog(LOG_WARN, "Error while reading MARS schedule from '%s'", mars.dissemination_schedule_file);
        marslog(LOG_WARN, "Please, inform mars@ecmwf.int");

        /* Allowed users will carry on*/
        if ((allowed = user_category_allowed(allow, env))) {
            marslog(LOG_WARN, "MARS schedule ignored");
            return 0;
        }
        return -1;
    }
    else {
        schedule = r;
    }

    if (EQ("allow", r->name)) {
        free_all_requests(allow);
        allow    = r;
        schedule = r->next;
    }

    if ((allowed = user_category_allowed(allow, env)) && dont_log_users_product_before_schedule)
        return 0;

    while (schedule) {
        const request* schedule_request = get_subrequest(schedule, "request", 0);
        const request* release_request  = get_subrequest(schedule, "release", 0);
        int release_delta_day           = -1;

        if (cube_contains(cube, schedule_request)) {
            long t = atol(get_value(release_request, "release_seconds", 0));
            long d = atol(get_value(release_request, "release_delta_day", 0));

            t += d * 24 * 3600;

            if (t > release_seconds) {
                release_seconds = t;
                max_schedule    = schedule;
            }
            marslog(LOG_DBUG, "check_dissemination_schedule: cube_order %d", cube_contains(cube, schedule_request));

            if (mars.debug) {
                marslog(LOG_DBUG, "schedule request is:");
                print_all_requests(schedule_request);

                marslog(LOG_DBUG, "release request is:");
                print_all_requests(release_request);
            }
        }

        schedule = schedule->next;
    }
    marslog(LOG_DBUG, "Release seconds: %ld", release_seconds);

    stop_timer(buf);
    if (*buf)
        marslog(LOG_INFO, "Verify schedule: %s", buf);

    if (release_seconds != -1) {
        double jnow, judate, delta;
        long year, month, day, hour, minute, second;
        long user_date = 0;

        get_date_for_schedule(&year, &month, &day, &hour, &minute, &second);

        jnow = mars_date_to_julian(year * 10000 + month * 100 + day) + hour / 24.0 + minute / 24.0 / 60.0 + second / 24.0 / 60.0 / 60.0;

        /* This causes problems with Climatology, Monthly means and
           the like, although they should not match any dissemination schedule */
        for (i = 0; i < count_values(user, "DATE"); ++i) {
            const char* p = get_value(user, "DATE", i);
            long d        = 0;
            if (is_number(p))
                d = atol(p);
            else {
                long julian = 0, second = 0;
                boolean isjul;
                parsedate(p, &julian, &second, &isjul);
                d = mars_julian_to_date(julian, mars.y2k);
            }
            if (d > user_date)
                user_date = d;
        }
        /* For Hindcasts, date to block is REFDATE */
        for (i = 0; i < count_values(user, "REFDATE"); ++i) {
            const char* p = get_value(user, "REFDATE", i);
            long d        = 0;
            if (is_number(p))
                d = atol(p);
            else {
                long julian = 0, second = 0;
                boolean isjul;
                parsedate(p, &julian, &second, &isjul);
                d = mars_julian_to_date(julian, mars.y2k);
            }
            if (d > user_date)
                user_date = d;
        }

        judate = mars_date_to_julian(user_date) + release_seconds / 24.0 / 60.0 / 60.0;
        delta  = (judate - jnow) * 24 * 60 * 60;

        marslog(LOG_DBUG, "jnow: %0.30f judate : %0.30f", jnow, judate);
        if (judate <= jnow) {
            delta = -delta;
            /* printf("OK since %ld sec=%ld min=%ld hour\n",delta,delta/60,delta/60/60); */
            e = 0;
        }
        else {
            struct {
                long second;
                long minute;
                long hour;
                long days;
            } release;
            const request* schedule_request = get_subrequest(max_schedule, "request", 0);
            const request* release_request  = get_subrequest(max_schedule, "release", 0);
            long release_second             = delta + hour * 60 * 60 + minute * 60 + second + 0.5;
            long release_minute             = 0;
            long release_hour               = 0;
            long release_days               = 0;
            char msg[64];

            if (logstat) {
                const char* trigger = getenv("MSJ_PATH");
                boolean suite       = (trigger != NULL);

                if (allowed) {
                    log_statistics("schedule", "allowed");
                }
                else {
                    if (suite)
                        log_statistics("schedule", "%s", trigger);
                    else
                        log_statistics("schedule", "user");
                }
            }

            if (allowed) {
                /* We could print a message to tell allowed users that this data
                   should not be passed on externally, if HO wants */
                return 0;
            }

            release.second = delta + hour * 60 * 60 + minute * 60 + second + 0.5;
            release.minute = 0;
            release.hour   = 0;
            release.days   = 0;

            release.hour = release.second / 60 / 60;
            release.second -= release.hour * 60 * 60;
            if (release.hour >= 24) {
                release.days = (int)(release.hour / 24);
                release.hour -= release.days * 24;
                sprintf(msg, "after %ld day%s at", release.days, (release.days > 1) ? "s" : "");
            }
            else
                sprintf(msg, "for after");

            release.minute = release.second / 60;
            release.second -= release.minute * 60;

            if ((mars.dissemination_schedule & SCHEDULE_INFORM_FUTURE_CHANGE) && !allowed) {
                marslog(LOG_WARN, "On 1st February 2006, MARS access will be");
                marslog(LOG_WARN, "harmonised with the dissemination schedule");
                marslog(LOG_WARN, "Executing this request after that day will");
                marslog(LOG_WARN, "fail and show the following warning:");
            }

            if ((mars.dissemination_schedule & SCHEDULE_INFORM || mars.dissemination_schedule & SCHEDULE_INFORM_FUTURE_CHANGE) && !allowed) {
                int loglevel = LOG_WARN;

                if (mars.dissemination_schedule & SCHEDULE_FAIL)
                    loglevel = LOG_EROR;
                marslog(loglevel, "Data not yet available. Scheduled %s %02ld:%02ld:%02ld, (%s)",
                        msg, release.hour, release.minute, release.second,
                        get_value(release_request, "release_time", 0));

                marslog(loglevel, "User request matches the following schedule rule:");
                marslog(loglevel, "      DATE = %ld", user_date);
                marslog(loglevel, "      TIME = %s", get_value(schedule_request, "TIME", 0));
                if (get_value(schedule_request, "STEP", 0) != 0) {
                    if (count_values(schedule_request, "STEP") > 1)
                        marslog(loglevel, "      STEP = %s/...", get_value(schedule_request, "STEP", 0));
                    else
                        marslog(loglevel, "      STEP = %s", get_value(schedule_request, "STEP", 0));
                }
                marslog(loglevel, "      RELEASE = %s", get_value(release_request, "release_time", 0));
                /* printf("NOT OK %ld sec=%ld min=%ld hour\n",delta,delta/60,delta/60/60); */
            }

            if ((mars.dissemination_schedule & SCHEDULE_INFORM_FUTURE_CHANGE) && !allowed)
                marslog(LOG_WARN, "Continue with the request execution");

            if (mars.dissemination_schedule & SCHEDULE_MAIL) {
                FILE* f = mail_open(mars.authmail, "MARS request issued before schedule");
                marslog(LOG_DBUG, "check_dissemination_schedule: send email to '%s'", mars.authmail);
                mail_msg(f, "Request issued on %d%02d%02d at %02d:%02d:%02d", year, month, day, hour, minute, second);
                mail_request(f, "User:", env);
                mail_request(f, "Matching schedule:", schedule_request);
                mail_request(f, "User request:", user);
                mail_close(f);
            }
            e = 1;
        }
    }

    marslog(LOG_DBUG, "Exit 'check_dissemination_schedule'");

    free_hypercube(cube);

    return e;
}
