#include "mars.h"
#include "ncschema.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;
}
