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

typedef struct flatfiledata {

    char* source;
    boolean obs;
    wind* u_v;
    hypercube* h;

    FILE* s;
    void* buffer;

} flatfiledata;

static void flatfile_init(void);
static err flatfile_open(void* data, request*, request*, int);
static err flatfile_close(void* data);
static err flatfile_read(void* data, request* r, void* buffer, long* length);
static err flatfile_write(void* data, request* r, void* buffer, long* length);


static base_class _flatfilebase = {

    NULL,           /* parent class */
    "flatfilebase", /* name         */

    false, /* inited       */

    sizeof(flatfiledata), /* private size */
    0,                    /* option count */
    NULL,                 /* options      */


    flatfile_init, /* init         */

    flatfile_open,  /* open         */
    flatfile_close, /* close        */

    flatfile_read,  /* read         */
    flatfile_write, /* write        */

};

/* the only 'public' variable ... */

base_class* flatfilebase = &_flatfilebase;


static void flatfile_init(void) {
}

static err flatfile_open(void* data, request* r, request* e, int mode) {
    flatfiledata* g = (flatfiledata*)data;

    g->obs = observation(r) || track(r);

    if (g->source == NULL && r != NULL)
        g->source = strcache(no_quotes(get_value(r, "SOURCE", 0)));

    g->h = new_hypercube_from_mars_request(r);

    if (g->source != NULL) {
        marslog(LOG_DBUG, "Trying to open flatfile %s", g->source);

        if ((g->s = fopen(g->source, "r"))) {
            marslog(LOG_INFO, "Opening file '%s'", g->source);
            /* Modify disk I/O buffer, if application buffer allows for that */
            if (mars.readdisk_buffer > 0) {
                if (g->buffer == NULL)
                    g->buffer = reserve_mem(mars.readdisk_buffer);
                marslog(LOG_DBUG, "Setting I/O read buffer to %d bytes", mars.readdisk_buffer);
                if (setvbuf(g->s, g->buffer, _IOFBF, mars.readdisk_buffer))
                    marslog(LOG_WARN | LOG_PERR, "setvbuf failed");
            }
        }
        else
            marslog(LOG_EROR | LOG_PERR, "Error opening file '%s'", g->source);
    }

    if (!g->obs)
        g->u_v = wind_new(r, 0, 1);

    return g->s == NULL ? -1 : 0;
}

static err flatfile_close(void* data) {
    flatfiledata* g = (flatfiledata*)data;
    if (g->s)
        fclose(g->s);
    strfree(g->source);

    if (g->u_v)
        wind_free(g->u_v);
    if (g->h)
        free_hypercube(g->h);

    if (mars.readdisk_buffer)
        if (g->buffer != NULL) {
            release_mem(g->buffer);
            g->buffer = NULL;
        }

    return 0;
}

static err readmany(struct flatfiledata* data, char* buff, long* len) {
    int count = 0;
    long a    = *len;
    long b    = *len;
    long n;
    int pad = sizeof(long);
    int rnd;
    off_t pos = ftell(data->s);

    err e = 0;
    *len  = 0;

    n = a - pad;
    while (a > 10240 && (e = _readbufr(data->s, buff, &n)) == 0) {
        a   = n;
        rnd = ((a + pad - 1) / pad) * pad - a;
        while (rnd-- > 0)
            buff[a++] = 0;

        buff += a; /* should round to 8 ... */
        b -= a;
        *len += a;
        a = b;
        count++;
        n   = a - pad;
        pos = ftell(data->s);
    }

    if (count == 0 && (e == -3 || e == -4))
        *len = a;

    if (count && (e == -1 || e == -3 || e == -4)) {
        fseek(data->s, pos, SEEK_SET);
        return 0;
    }

    return e;
}

/* Reads next field contained in request 'r' */
static err next_field(flatfiledata* g, request* r, void* buffer, long* length) {
    err ret              = 0;
    boolean match        = false;
    long len             = *length;
    timer* fileread_time = get_timer("Read from file", "readfiletime", true);

    while ((ret == 0) && !match) {
        *length = len;
        ret     = wind_next(g->u_v, g->s, buffer, length, fileread_time);
        if (ret == NOERR) {
            request* u = empty_request(0);

            grib_to_request(u, buffer, *length);

            if (cube_order(g->h, u) != -1)
                match = true;
            free_all_requests(u);
        }
    }

    return ret;
}


static err flatfile_read(void* data, request* r, void* buffer, long* length) {
    flatfiledata* g = (flatfiledata*)data;
    err ret;

    if (g->obs)
        ret = _readany(g->s, buffer, length);
    else
        ret = next_field(g, r, buffer, length);

    if (ret != 0 && ret != -3)
        *length = 0;

    if (ret == 0 && r) {
        if (g->obs)
            bufr_to_request(r, buffer, *length);
        else
            grib_to_request(r, buffer, *length);
    }
    return ret;
}

static err flatfile_write(void* data, request* r, void* buffer, long* length) {
    return -1;
}
