/*
 * © 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.
 */

/*

B.Raoult
ECMWF Oct-93

*/

#include "mars.h"

/* extern base_class *cachebase; */
extern base_class* netbase;
extern base_class* nfdbbase;
extern base_class* filebase;
extern base_class* fdb5base;
extern base_class* forwardbase;
extern base_class* targetbase;
extern base_class* nullbase;
extern base_class* dhsbase;
extern base_class* flatfilebase;
extern base_class* multibase;
extern base_class* apibase;

static base_class** bases[] = {
    &nullbase,
    &targetbase,
    &filebase,
#if defined(ECMWF) && !defined(NOFDB)
    &nfdbbase,
#endif
#if !defined(NOFDB5)
    &fdb5base,
#endif
#if mars_client_HAVE_RPC
    &netbase,
#endif
    /* &cachebase, */
    &dhsbase,
    &flatfilebase,
    &multibase,
    &forwardbase,
    &apibase,
};

static database* open_db = 0;

base_class* base_class_by_name(const char* name) {
    int i;

    if (name) {

        for (i = 0; i < NUMBER(bases); i++) {
            if (strcasecmp(name, (*bases[i])->name) == 0) {
                return *bases[i];
            }
        }
    }

    marslog(LOG_EROR, "Cannot find base class named '%s'", name ? name : "(null)");
    /* return NULL; */
    return nullbase; /* return nullbase, so request can progress */
}

static database* new_database(base_class* driver, const char* name) {
    database* b;

    if (driver == NULL)
        return NULL;

    b = NEW_CLEAR(database);

    if (!driver->inited) {
        if (driver->init)
            driver->init();
        driver->inited = true;
    }

    b->next   = open_db;
    b->driver = driver;
    b->name   = strcache(name);

    if (driver->private_size) {
        b->data = MALLOC(driver->private_size);
        bzero(b->data, (driver->private_size));

        get_options(driver->name, name,
                    b->data, driver->options_count, driver->options);
    }

    open_db = b;

    return b;
}

static void free_database(database* b) {
    database* p = open_db;
    database* q = 0;
    while (p) {
        if (p == b) {
            if (q)
                q->next = b->next;
            else
                open_db = b->next;
            break;
        }
        q = p;
        p = p->next;
    }

    if (b->data)
        FREE(b->data);
    strfree(b->name);
    FREE(b);
}

database* database_of(void* data) {
    database* d = open_db;
    while (d) {
        if (d->data == data)
            return d;
        d = d->next;
    }
    return 0;
}

const char* database_name(void* data) {
    database* d = database_of(data);
    return d ? d->name : "<unknown>";
}


err database_validate(base_class* driver, const char* name,
                      request* r, request* e, int mode) {
    int ret = 0;


    if (ret == 0) {
        database* b = new_database(driver, name);
        if (!b)
            return -1;
        if (driver->validate != NULL && driver->validate(b->data, r, e, mode) != NOERR)
            ret = -1;
        free_database(b);
    }
    return ret;
}

typedef struct validate_data {
    char* file;
    char* only;
} validate_data;

static option validate_opts[] = {
    {
        "rules",
        NULL,
        NULL,
        NULL,
        t_str,
        sizeof(char*),
        OFFSET(validate_data, file),
    },
    {
        "only",
        NULL,
        NULL,
        NULL,
        t_str,
        sizeof(char*),
        OFFSET(validate_data, only),
    },
};

database* database_open(base_class* driver, const char* name,
                        const request* r, const request* e, int mode) {
    database* b;
    validate_data data;
    err error = NOERR;

    do {

        error = NOERR;

        if (name) {
            get_options(driver->name, name, &data, NUMBER(validate_opts),
                        validate_opts);

            if (data.only) {
                char h[80];
                gethostname(h, sizeof(h));
                if (strncmp(data.only, h, strlen(data.only)) != 0)
                    return NULL;
            }

            if (data.file) {
                rule* rules = read_check_file(config_file(data.file));
                int e       = check_one_request(rules, r) ? NOERR : -1;
                free_rule(rules);
                if (e)
                    return NULL;
            }
        }

        b = new_database(driver, name);
        if (!b)
            return NULL;

        if (driver->validate != NULL && driver->validate(b->data, r, e, mode) != NOERR) {
            free_database(b);
            return NULL;
        }

        error = driver->open(b->data, r, e, mode);

        if (error == RETRY_SAME_DATABASE) {
            marslog(LOG_WARN, "Retrying database %s", name);
            database_close(b);
            sleep(5);
        }

    } while (error == RETRY_SAME_DATABASE);

    if (error != NOERR) {
        marslog(LOG_WARN, "Error opening database %s (%d)", name, error);
        database_close(b);
        return NULL;
    }

    return b;
}

err database_read(database* b, request* r, void* buffer, long* length) {
    return b->driver->read(b->data, r, buffer, length);
}

err database_write(database* b, request* r, void* buffer, long* length) {
    return b->driver->write(b->data, r, buffer, length);
}

err database_control(database* b, int code, void* param, int size) {
    if (b->driver->control)
        return b->driver->control(b->data, code, param, size);
    else
        return -1;
}

err database_archive(database* b, request* r) {
    if (b->driver->archive)
        return b->driver->archive(b->data, r);
    else
        return feed(b, r);
}


boolean database_check(database* b, request* r) {
    if (b->driver->check == NULL)
        return true;
    return b->driver->check(b->data, r);
}

err database_close(database* b) {
    err ret = b->driver->close(b->data);
    free_database(b);
    return ret;
}

void database_admin(base_class* driver) {
    if (driver->admin != NULL) {
        database* b = database_open(driver, NULL, NULL, NULL, ADMIN_MODE);
        if (b != 0) {
            driver->admin(b->data);
            database_close(b);
        }
    }
}
