/***************************** LICENSE START ***********************************

 Copyright 2012 ECMWF and INPE. This software is distributed under the terms
 of the Apache License version 2.0. In applying this license, ECMWF does not
 waive the privileges and immunities granted to it by virtue of its status as
 an Intergovernmental Organization or submit itself to any jurisdiction.

 ***************************** LICENSE END *************************************/

/*

B.Raoult
ECMWF Oct-93

*/

#include "mars.h"
#include <time.h>
#include "MvApplication.h"
#include "MvDebugPrintControl.h"

struct charl
{
    struct charl* next;
    char* name;
};

struct cache
{
    struct cache* next;
    char* name;
    char* pclass;
    charl* deps;
    time_t lastuse;
    request* r;
};

struct args
{
    int timeout;
};


option opts[] = {
    {"timeout", nullptr, "-timeout", "0", t_int, sizeof(int), OFFSET(args, timeout)},
};

static cache* head = nullptr;
static svc* s = nullptr;
static args setup;
static int retention;

static void destroy(const char*, cache*);

static void send_status(char* pclass, char* name, char* state)
{
    static request* r = nullptr;
    if (r == nullptr)
        r = empty_request("STATUS");
    if (pclass)
        set_value(r, "CLASS", "%s", pclass);
    set_value(r, "NAME", "%s", name);
    set_value(r, "STATUS", "%s", state);
    send_message(s, r);
}

/*
static void birth(svcid *id,request *r,void *data)
{
    cache *c = head;
    while(c)
    {
        send_status(c->pclass,c->name,"READY");
        c = c->next;
    }
}
*/

static void panic(int /*code*/, void* /*data*/)
{
    cache* c = head;
    while (c) {
        cache* p = c->next;
        send_status(c->pclass, c->name, "MODIFIED");
        destroy(progname(), c);
        c = p;
    }
}

static void clean()
{
    time_t now = time(0);
    cache* c = head;
    while (c) {
        cache* thiscache = c;
        c = c->next; /* do this in advance because we might delete c */
        if (difftime(now, thiscache->lastuse) > retention)
            destroy("pool: file not used", thiscache);
    }
}

static cache* find(const char* name, const char* pclass)
{
    cache* c = head;
    while (c) {
        if (c->name == name && (c->pclass == pclass || pclass == nullptr || c->pclass == nullptr))
            return c;
        c = c->next;
    }
    return nullptr;
}

static void subname(char* name)
{
    cache* c = head;
    int len = strlen(name);
    while (c) {
        if (strncmp(name, c->name, len) == 0)
            if (c->name[len] == '/')
                send_status(c->pclass, c->name, "MODIFIED");
        c = c->next;
    }
}

static void destroy(const char* serv, cache* c)
{
    cache* p = head;
    cache* q = nullptr;
    request* r;

    if (c == nullptr)
        return;

    while (p) {
        if (p == c) {
            if (q)
                q->next = c->next;
            else
                head = c->next;
            break;
        }
        q = p;
        p = p->next;
    }

    marslog(LOG_INFO, "[%-10s] - Deleting %s", serv, c->name);

    r = c->r;
    while (r) {
        const char* p = get_value(r, "PATH", 0);
        const char* t = get_value(r, "TEMPORARY", 0);
        int tmp = t ? atoi(t) : 0;
        if (p) {
            if (tmp) {
                unlink(p);
                marslog(LOG_INFO, "File %s deleted", p);
            }
            else
                marslog(LOG_INFO, "File %s not deleted", p);
        }
        r = r->next;
    }

    subname(c->name);

    free_all_requests(c->r);
    strfree(c->name);
    strfree(c->pclass);

    while (c->deps) {
        charl* p = c->deps->next;
        cache* d = find(c->deps->name, nullptr);
        if (d)
            send_status(d->pclass, d->name, "MODIFIED");
        strfree(c->deps->name);
        free(c->deps);
        c->deps = p;
    }

    free(c);
}

static void newstat(svcid* id, request* r, void* /*data*/)
{
    const char* state = get_value(r, "STATUS", 0);
    const char* name = get_value(r, "NAME", 0);
    cache* c;

    if (name && state) {
        if (
            EQ(state, "FAILED") ||
            EQ(state, "MODIFIED") ||
            EQ(state, "DELETED"))
            while ((c = find(name, nullptr)))
                destroy(get_svc_source(id), c);
    }
}

static void store(svcid* id, request* r, void* /*data*/)
{
    const char* name = get_value(r, "NAME", 0);
    const char* pclass = get_value(r, "CLASS", 0);
    if (name && r && r->next) {
        cache* c;

#ifdef NO_DUPS
        destroy(get_svc_source(id), find(name, pclass));
#else
        if (find(name, pclass))
            marslog(LOG_WARN, "[%-10s] - Name %s is duplicated",
                    get_svc_source(id), name);
#endif

        c = NEW_CLEAR(cache);
        c->name = strcache(name);
        c->pclass = strcache(pclass);
        c->r = clone_all_requests(r->next);
        c->lastuse = time(0);
        c->next = head;
        head = c;
        marslog(LOG_INFO, "[%-10s] - Caching  %s", get_svc_source(id),
                c->name);
    }
    send_reply(id, nullptr);
}

static void fetchcb(svcid* id, request* r, void* /*data*/)
{
    const char* name = get_value(r, "NAME", 0);
    const char* pclass = get_value(r, "CLASS", 0);
    cache* c = find(name, pclass);

    if (!name)
        c = nullptr;
    marslog(LOG_INFO, "[%-10s] - Object %s [%s] %s",
            get_svc_source(id), name, pclass, c ? "returned" : "not cached");
    if (c) {
        c->lastuse = time(0);
        send_reply(id, c->r);
    }
    else
        send_reply(id, nullptr);
}

static void depend(svcid* id, request* r, void* /*data*/)
{
    const char* name1 = get_value(r, "NAME1", 0);
    const char* name2 = get_value(r, "NAME2", 0);
    cache* c = find(name2, nullptr);

    if (c == nullptr)
        marslog(LOG_INFO, "[%-10s] - Link %s not found",
                get_svc_source(id), name1);
    else {
        auto* p = NEW_CLEAR(charl);
        marslog(LOG_INFO, "[%-10s] - Link %s and %s", get_svc_source(id),
                name1, name2);
        p->next = c->deps;
        c->deps = p;
        p->name = strcache(name1);
    }
    send_reply(id, nullptr);
}

int main(int argc, char** argv)
{
    // time_t now,then;
    // int day = 60*60*24;
    // int sec;
    MvRequest pref;
    const char* r;

    marsinit(&argc, argv, &setup, NUMBER(opts), opts);
    mvSetMarslogLevel();  //-- if "quiet log"

    s = create_service("pool");

    pref = MvApplication::getPreferences();
    r = get_value(pref, "DATA_CACHE_RETENTION_PERIOD", 0);
    retention = (r) ? atoi(r) : setup.timeout;
    s->timeout = retention * 60;


#if 0
	now  = time(0);
	then = ((now/day)+1)*day;
	/* marslog(LOG_INFO,"It is now  %s",ctime(&now)); */
	/* marslog(LOG_INFO,"Suicide at %s",ctime(&then)); */

	if( (r = getenv("METVIEW_MODE")) && strcmp(r,"batch") != 0)
	{
		alarm(sec = then-now);
		marslog(LOG_INFO,"Suicide in %d hour(s) %d minutes(s) %d second(s)",
			sec/(60*60) , (sec % (60*60)) / 60 , sec % 60);
	}
#endif

    add_service_callback(s, "STORE", store, nullptr);
    add_service_callback(s, "FETCH", fetchcb, nullptr);
    add_service_callback(s, "LINK", depend, nullptr);

    add_message_callback(s, "STATUS", newstat, nullptr);
    /* add_message_callback(s,"BIRTH", birth  ,nullptr); */

    install_exit_proc(panic, nullptr);
    while (1) {
        clean();
        service_run(s);
    }
}
