#include "ncschema.h"
#include "mars.h"

typedef struct netcdf_variable_score {
    netcdf_variable* var;
    size_t score;
} netcdf_variable_score;

static err netcdf_dimension_finalize(netcdf_field* f, netcdf_dimension_list* list) {
    netcdf_variable* variables[20140];
    netcdf_variable_score stack[20140];

    size_t count        = 0;
    size_t stack_size   = 0;
    netcdf_dimension* c = list->first;
    int i, j, k;

    while (c) {
        count = NUMBER(variables);
        netcdf_variable_by_dimension(&f->variables, c, variables, &count);

        for (j = 0; j < count; j++) {
            boolean found = false;
            for (i = 0; i < stack_size; i++) {
                if (stack[i].var == variables[j]) {
                    found = true;
                    stack[i].score++;
                    break;
                }
            }
            if (!found) {
                if (stack_size == NUMBER(stack)) {
                    marslog(LOG_EXIT, "Internal error: stack too small");
                }
                stack[stack_size].score = 1;
                stack[stack_size].var   = variables[j];
                stack_size++;
            }
        }


        c = c->next;
    }


    // marslog(LOG_INFO, "Variable stack:");
    // for (i = 0; i < stack_size; i++) {
    //     marslog(LOG_INFO, "        var: %s %ld", stack[i].var->name, stack[i].score);
    // }


    return 0;
}

static int compar_vars(const void* pa, const void* pb) {
    netcdf_variable** a = (netcdf_variable**)pa;
    netcdf_variable** b = (netcdf_variable**)pb;
    return ((int)(*a)->cube.ndims) - ((int)(*b)->cube.ndims);
}

static err netcdf_variable_finalize(netcdf_field* f, netcdf_variable_list* list) {
    netcdf_variable* c = list->first;
    int i;
    while (c) {
        f->nvars++;
        c = c->next;
    }

    f->variables_by_natural_order = NEW_ARRAY(netcdf_variable*, f->nvars);
    f->variables_by_dimensions    = NEW_ARRAY(netcdf_variable*, f->nvars);

    c = list->first;
    i = 0;
    while (c) {
        f->variables_by_natural_order[i] = c;
        f->variables_by_dimensions[i++]  = c;
        c                                = c->next;
    }


    qsort(f->variables_by_dimensions, f->nvars, sizeof(netcdf_variable*), &compar_vars);

    // marslog(LOG_INFO, "Variables by dimensions: %s", f->path);
    list->first = list->last = NULL;
    for (i = 0; i < f->nvars; i++) {
        c       = f->variables_by_dimensions[i];
        c->next = NULL;

        if (list->last) {
            list->last->next = c;
            list->last       = c;
        }
        else {
            list->last = list->first = c;
        }


        // marslog(LOG_INFO, "   var %s %ld", c->name, c->cube.ndims);
    }


    return 0;
}


static err netcdf_field_finalize(netcdf_field* f) {
    err e;

    if ((e = netcdf_variable_finalize(f, &f->variables)) != 0) {
        return e;
    }
    // if ( (e = netcdf_dimension_finalize(f, &f->dimensions)) != 0) {
    //     return e;
    // }
    return 0;
}


static err get_attr(netcdf_field* field, netcdf_attribute_list* list, const char* owner, int nc, int i, int nattr) {
    int j;
    int e;
    char value[10480];
    char tmp[10480];
    float f;
    double d;
    int n;
    long l;
    unsigned char b;
    short s;

    for (j = 0; j < nattr; j++) {
        char attribute[NC_MAX_NAME + 1];
        int type;
        size_t len;
        netcdf_attribute* a;


        memset(attribute, 0, sizeof(attribute));
        memset(value, 0, sizeof(value));

        if ((e = nc_inq_attname(nc, i, j, attribute)) != NC_NOERR) {
            marslog(LOG_EROR, "nc_inq_attname(%s): %s", field->path, nc_strerror(e));
            return -2;
        };


        if ((e = nc_inq_att(nc, i, attribute, &type, &len)) != NC_NOERR) {
            marslog(LOG_EROR, "nc_inq_att(%s): %s", field->path, nc_strerror(e));
            return -2;
        };

        a = netcdf_attribute_new(list, owner, attribute, j, type, len);

        if (len >= sizeof(value)) {
            marslog(LOG_EROR, "Attribute is too large (%d)", len);
            return -2;
        }

        switch (type) {


                // case NC_BYTE:
                // break;

            case NC_SHORT:
                if ((e = nc_get_att_short(nc, i, attribute, &a->short_value)) != NC_NOERR) {
                    marslog(LOG_EROR, "nc_get_att_short (%s): %s", field->path, nc_strerror(e));
                    return -2;
                }
                break;

            case NC_LONG:
                if ((e = nc_get_att_long(nc, i, attribute, &a->long_value)) != NC_NOERR) {
                    marslog(LOG_EROR, "nc_get_att_long (%s): %s", field->path, nc_strerror(e));
                    return -2;
                }
                break;

            case NC_CHAR:
                memset(value, 0, sizeof(value));
                if ((e = nc_get_att_text(nc, i, attribute, value)) != NC_NOERR) {
                    marslog(LOG_EROR, "nc_get_att_text(%s): %s", field->path, nc_strerror(e));
                    return -2;
                }
                a->char_value = strcache(value);
                break;

            case NC_FLOAT:
                if ((e = nc_get_att_float(nc, i, attribute, &a->float_value)) != NC_NOERR) {
                    marslog(LOG_EROR, "nc_get_att_float (%s): %s", field->path, nc_strerror(e));
                    return -2;
                };
                break;

            case NC_DOUBLE:
                if ((e = nc_get_att_double(nc, i, attribute, &a->double_value)) != NC_NOERR) {
                    marslog(LOG_EROR, "nc_get_att_double (%s): %s", field->path, nc_strerror(e));
                    return -2;
                };
                break;

            default:
                marslog(LOG_EROR, "Unknow attribute type(%s) %d %s", field->path, type, netcdf_type_name(type));
                return -2;
        }
    }

    return 0;
}


err netcdf_field_add_path(netcdf_field_list* fields, const char* path, int tmp) {

    size_t len;
    char name[NC_MAX_NAME + 1];

    int nc;
    int e;
    int i, j;

    netcdf_field* f = netcdf_field_new(fields, path, tmp);

    if ((e = nc_open(path, NC_NOWRITE, &nc)) != NC_NOERR) {
        marslog(LOG_EROR, "nc_open(%s): %s", path, nc_strerror(e));
        return -2;
    };

    if ((e = nc_inq(nc, &f->number_of_dimensions, &f->number_of_variables,
                    &f->number_of_global_attributes, &f->id_of_unlimited_dimension))
        != NC_NOERR) {
        marslog(LOG_EROR, "nc_inq(%s): %s", path, nc_strerror(e));
        return -2;
    };

    if ((e = nc_inq_format(nc, &f->format)) != NC_NOERR) {
        marslog(LOG_EROR, "nc_inq_format(%s): %s", path, nc_strerror(e));
        return -2;
    };

    marslog(LOG_DBUG, "%s: dimensions %d", path, f->number_of_dimensions);

    for (i = 0; i < f->number_of_dimensions; i++) {
        len = sizeof(name);
        if ((e = nc_inq_dim(nc, i, name, &len)) != NC_NOERR) {
            marslog(LOG_EROR, "nc_inq_dim(%s): %s", path, nc_strerror(e));
            return -2;
        };
        marslog(LOG_DBUG, "%s: dimension %d is %s", path, i, name);

        netcdf_dimension_new(&f->dimensions, f, name, i, len);
    }

    marslog(LOG_DBUG, "%s: variables %d", path, f->number_of_variables);

    for (i = 0; i < f->number_of_variables; i++) {
        int type;
        int ndims, nattr;
        int dims[NC_MAX_VAR_DIMS];

        len = sizeof(name);
        if ((e = nc_inq_var(nc, i, name, &type, &ndims, dims, &nattr)) != NC_NOERR) {
            marslog(LOG_EROR, "nc_inq_var(%s): %s", path, nc_strerror(e));
            return -2;
        }

        netcdf_variable* v = netcdf_variable_new(&f->variables, f, &f->dimensions, name, i, type, ndims, dims, nattr);

        marslog(LOG_DBUG, "%s: variable %d is %s", path, i, name, type, ndims, dims, nattr);
        if (get_attr(f, &v->attributes, name, nc, i, nattr) != 0) {
            return -2;
        }
    }

    marslog(LOG_DBUG, "%s: globals %d", path, f->number_of_global_attributes);

    if (get_attr(f, &f->global_attributes, "", nc, NC_GLOBAL, f->number_of_global_attributes) != 0) {
        return -2;
    }

    if ((e = nc_close(nc)) != NC_NOERR) {
        marslog(LOG_EROR, "nc_close(%s): %s", path, nc_strerror(e));
        return -2;
    };


    return netcdf_field_finalize(f);
}


netcdf_schema* netcdf_schema_new(const char* path) {
    netcdf_schema* s = NEW_CLEAR(netcdf_schema);
    if (netcdf_field_add_path(&s->fields, path, 0) != 0) {
        FREE(s);
        return NULL;
    }
    return s;
}

void netcdf_schema_delete(netcdf_schema* s) {
    netcdf_field_delete(&s->fields);
    FREE(s);
}

static request* netcdf_attribute_to_request(netcdf_attribute_list* list, mars_field_index* idx, err* e) {

    netcdf_attribute* c = list->first;

    if (!c) {
        *e = -2;
        marslog(LOG_EROR, "NetCDF: no mars attributes defined in mars_keywords");
        return NULL;
    }

    request* req = empty_request("netcdf");

    while (c) {
        boolean s_ok = false;
        const char* s;
        char buf[80];

        long l;
        boolean l_ok = false;

        double d;
        boolean d_ok = false;

        switch (c->type) {

            // case NC_BYTE:
            //     return "NC_BYTE";
            //     break;

            marslog(LOG_DBUG, "NetCDF: attribute type %d", c->type);

            case NC_SHORT:

                marslog(LOG_DBUG, "NetCDF: Request add_value (NC_SHORT) %s : %ld", c->name, (long)c->short_value);

                add_value(req, c->name, "%ld", (long)c->short_value);

                if (idx) {

                    l_ok = true;
                    l    = c->short_value;

                    sprintf(buf, "%ld", (long)c->short_value);
                    s_ok = true;

                    mars_field_index_add(idx, c->name, s_ok, buf, l_ok, l, d_ok, d);
                }
                break;

            case NC_LONG:

                marslog(LOG_DBUG, "NetCDF: Request add_value (NC_LONG) %s : %ld", c->name, (long)c->long_value);

                add_value(req, c->name, "%ld", (long)c->long_value);

                if (idx) {

                    l_ok = true;
                    l    = c->long_value;

                    sprintf(buf, "%ld", (long)c->long_value);
                    s_ok = true;

                    mars_field_index_add(idx, c->name, s_ok, buf, l_ok, l, d_ok, d);
                }
                break;

            case NC_CHAR:
                marslog(LOG_DBUG, "NetCDF: Request add_value (NC_CHAR) %s : %s", c->name, (long)c->char_value);

                add_value(req, c->name, "%s", c->char_value);

                if (idx) {

                    char* endptr;

                    s_ok = true;
                    s    = c->char_value;

                    d    = strtod(s, &endptr);
                    d_ok = (endptr == s + strlen(s));

                    l    = strtol(s, &endptr, 10);
                    l_ok = (endptr == s + strlen(s));

                    mars_field_index_add(idx, c->name, s_ok, s, l_ok, l, d_ok, d);
                }
                break;

            case NC_FLOAT:
                marslog(LOG_DBUG, "NetCDF: Request add_value (NC_FLOAT) %s : %g", c->name, (long)c->float_value);

                add_value(req, c->name, "%g", (double)c->float_value);

                if (idx) {

                    d_ok = true;
                    d    = c->float_value;

                    sprintf(buf, "%g", (double)c->float_value);
                    s_ok = true;

                    l    = c->float_value;
                    l_ok = (d == l);

                    mars_field_index_add(idx, c->name, s_ok, buf, l_ok, l, d_ok, d);
                }
                break;

            case NC_DOUBLE:
                marslog(LOG_DBUG, "NetCDF: Request add_value (NC_DOUBLE) %s : %g", c->name, (long)c->double_value);

                add_value(req, c->name, "%g", (double)c->double_value);

                if (idx) {

                    d_ok = true;
                    d    = c->double_value;

                    sprintf(buf, "%g", (double)c->float_value);
                    s_ok = true;

                    l    = c->double_value;
                    l_ok = (d == l);

                    mars_field_index_add(idx, c->name, s_ok, buf, l_ok, l, d_ok, d);
                }
                break;

            default:
                marslog(LOG_EROR, "NetCDF: unknow attribute type %d", c->type);
                *e = -2;
                free_all_requests(req);
                return NULL;
                break;
        }


        c = c->next;
    }

    return req;
}

request* netcdf_schema_to_request(netcdf_schema* s, int merge, mars_field_index* idx, err* e) {
    netcdf_field* f = s->fields.first;
    request* first  = NULL;
    request* last   = NULL;
    request* r;

    if (f->next) {
        marslog(LOG_EROR, "Multi-file netcdf fields not supported");
        *e = -2;
        free_all_requests(first);
        return NULL;
    }

    while (f) {

        netcdf_variable* v = f->variables.first;
        while (v) {
            netcdf_attribute* a = v->attributes.first;
            while (a) {
                if (strcmp(a->name, "mars_keywords") == 0) {
                    marslog(LOG_INFO, "NetCDF: mars_keywords found in variable [%s]", v->name);
                    if (a->type != NC_CHAR) {
                        marslog(LOG_EROR, "NetCDF: mars_keywords not NC_CHAR");
                        *e = -2;
                        free_all_requests(first);
                        return NULL;
                    }
                    marslog(LOG_INFO, "NetCDF: mars_keywords for field [%s] in [%s]", v->name, a->char_value);

                    netcdf_variable* w    = f->variables.first;
                    netcdf_variable* mars = NULL;
                    while (w) {
                        if (strcmp(w->name, a->char_value) == 0) {
                            mars = w;
                            break;
                        }
                        w = w->next;
                    }

                    if (!mars) {
                        marslog(LOG_EROR, "NetCDF: mars_keywords for field [%s] is [%s], not found", v->name, a->char_value);
                        *e = -2;
                        free_all_requests(first);
                        return NULL;
                    }

                    r = netcdf_attribute_to_request(&mars->attributes, idx, e);

                    if (!r) {
                        marslog(LOG_EROR, "NetCDF: failed to extract request from mars_keywords for field [%s] defined in [%s]", v->name, a->char_value);
                        free_all_requests(first);
                        return NULL;
                    }

                    if (first == NULL) {
                        first = last = r;
                    }
                    else {
                        if (merge) {
                            reqmerge(first, r);
                            free_all_requests(r);
                        }
                        else {
                            last->next = r;
                            last       = r;
                        }
                    }

                    break;
                }
                a = a->next;
            }
            v = v->next;
        }

        f = f->next;
    }
    return first;
}
