#include <mars.h>
#include "ncmerge.h"
#include "ncschema.h"
#include "nctypes.h"


/* Same names as nccopy */
static struct {
    const char* name;
    int flags;

} nc_formats[] = {
    {"classic", NC_FORMAT_CLASSIC},
    {"64-bit-offset", NC_FORMAT_64BIT},
    {"64-bit offset", NC_FORMAT_64BIT},
    {"hdf5", NC_FORMAT_NETCDF4},
    {"netCDF-4", NC_FORMAT_NETCDF4},
    {"netCDF4", NC_FORMAT_NETCDF4},
    {"enhanced", NC_FORMAT_NETCDF4},
    {"hdf5-nc3", NC_FORMAT_NETCDF4_CLASSIC},
    {"netCDF-4 classic model", NC_FORMAT_NETCDF4_CLASSIC},
    {"netCDF4_classic", NC_FORMAT_NETCDF4_CLASSIC},
    {"enhanced-nc3", NC_FORMAT_NETCDF4_CLASSIC},
};

netcdf_target* netcdf_target_new(const char* path, const char* format, int more_flags) {
    netcdf_target* t = 0;
    int e;
    int flags = -1;
    int nc;
    int i;

    if (format == NULL) {
        format = "netCDF4_classic";
    }

    for (i = 0; i < NUMBER(nc_formats); i++) {
        if (strcmp(format, nc_formats[i].name) == 0) {
            flags = nc_formats[i].flags;
        }
    }


    switch (flags) {
        case NC_FORMAT_CLASSIC:
            flags = 0;
            break;

        case NC_FORMAT_64BIT:
            flags = NC_64BIT_OFFSET;
            break;

        case NC_FORMAT_NETCDF4:
            flags = NC_NETCDF4;
            break;

        case NC_FORMAT_NETCDF4_CLASSIC:
            flags = NC_NETCDF4 | NC_CLASSIC_MODEL;
            break;
    }

    if (flags == -1) {
        marslog(LOG_EROR, "netcdf_target_new: invalid format: [%s]", format);
        return NULL;
    }

    marslog(LOG_INFO, "Create NetCDF target '%s' with format '%s' and flags = %x", path, format, flags);

    if ((e = nc_create(path, flags | NC_WRITE, &nc)) != NC_NOERR) {
        marslog(LOG_EROR, "nc_create(%s): %s", path, nc_strerror(e));
        return NULL;
    }

    if ((e = nc_set_fill(nc, NC_NOFILL, NULL)) != NC_NOERR) {
        marslog(LOG_EROR, "nc_set_fill(%s): %s", path, nc_strerror(e));
        return NULL;
    }

    t       = NEW_CLEAR(netcdf_target);
    t->path = strcache(path);
    t->nc   = nc;


    marslog(LOG_DBUG, "NetCDF file '%s' created", path);
    return t;
}

/*=========================================================================================================*/

/*=========================================================================================================*/
err netcdf_target_add_buffer(netcdf_target* target, const void* message, size_t length) {
    char* tmp = marstmp();
    FILE* f   = fopen(tmp, "w");
    int ret;

    if (!f) {
        marslog(LOG_EROR | LOG_PERR, "Cannot create %s", tmp);
        return -2;
    }

    if (fwrite(message, 1, length, f) != length) {
        marslog(LOG_EROR | LOG_PERR, "Write error on %s", tmp);
        return -2;
    }

    if (fclose(f)) {
        marslog(LOG_EROR | LOG_PERR, "Write error on %s", tmp);
        return -2;
    }

    ret = netcdf_field_add_path(&target->fields, tmp, 1);

    return ret;
}


err netcdf_target_add_file(netcdf_target* target, const char* path) {
    return netcdf_field_add_path(&target->fields, path, 0);
}


/*=========================================================================================================*/

static err netcdf_dimension_save(const char* path, int nc, netcdf_dimension_list* list) {
    netcdf_dimension* c = list->first;
    int e;

    while (c) {
        if (!c->cleared) {
            if ((e = nc_def_dim(nc, c->name, c->len, &c->write_id)) != NC_NOERR) {
                marslog(LOG_EROR, "nc_def_dim(%s): %s", path, nc_strerror(e));
                return -2;
            }
        }
        c = c->next;
    }
    return 0;
}

static err netcdf_attribute_save(const char* path, int nc, int var_id, netcdf_attribute_list* list) {
    netcdf_attribute* c = list->first;
    int e               = 0;

    while (c) {
        if (!c->cleared) {
            switch (c->type) {
                    // case NC_BYTE:
                    //      if ( (e = nc_put_att_byte(nc, var_id, c->name, c->type, c->len, &c->float_value)) != NC_NOERR) {
                    //         marslog(LOG_EROR, "nc_put_att_float(%s): %s", path, nc_strerror(e));
                    //         return -2;
                    //     }
                    //     break;

                case NC_SHORT:
                    if ((e = nc_put_att_short(nc, var_id, c->name, c->type, c->len, &c->short_value)) != NC_NOERR) {
                        marslog(LOG_EROR, "nc_put_att_float(%s): %s", path, nc_strerror(e));
                        return -2;
                    }
                    break;

                case NC_LONG:
                    if ((e = nc_put_att_long(nc, var_id, c->name, c->type, c->len, &c->long_value)) != NC_NOERR) {
                        marslog(LOG_EROR, "nc_put_att_float(%s): %s", path, nc_strerror(e));
                        return -2;
                    }
                    break;

                case NC_CHAR:

                    if ((e = nc_put_att_text(nc, var_id, c->name, strlen(c->char_value), c->char_value)) != NC_NOERR) {
                        marslog(LOG_EROR, "nc_put_att_text(%s): %s", path, nc_strerror(e));
                        return -2;
                    }
                    break;

                case NC_FLOAT:
                    if ((e = nc_put_att_float(nc, var_id, c->name, c->type, c->len, &c->float_value)) != NC_NOERR) {
                        marslog(LOG_EROR, "nc_put_att_float(%s): %s", path, nc_strerror(e));
                        return -2;
                    }
                    break;

                case NC_DOUBLE:
                    if ((e = nc_put_att_double(nc, var_id, c->name, c->type, c->len, &c->double_value)) != NC_NOERR) {
                        marslog(LOG_EROR, "nc_put_att_double(%s): %s", path, nc_strerror(e));
                        return -2;
                    }
                    break;

                default:
                    marslog(LOG_EROR, "Unknow netcdf type(%s) %d %s", path, c->type, netcdf_type_name(c->type));
                    return -2;
            }
        }
        c = c->next;
    }

    return 0;
}

static err netcdf_variable_create(const char* path, int nc, netcdf_variable_list* list) {
    netcdf_variable* c = list->first;
    int e;

    while (c) {
        if (!c->cleared) {
            int i;
            int dims[NC_MAX_VAR_DIMS];

            for (i = 0; i < c->cube.ndims; i++) {
                dims[i] = c->cube.dims[i]->write_id;
            }


            if ((e = nc_def_var(nc, c->name, c->type, c->cube.ndims, dims, &c->write_id)) != NC_NOERR) {
                marslog(LOG_EROR, "nc_def_var(%s): %s", path, nc_strerror(e));
                return -2;
            }

            if ((e = netcdf_attribute_save(path, nc, c->write_id, &c->attributes)) != 0) {
                return e;
            }
        }
        c = c->next;
    }
    return 0;
}


static err netcdf_variable_save(const char* path, int nc, netcdf_variable_list* list) {
    netcdf_variable* c = list->first;
    int e;
    double* double_buffer = 0;
    float* float_buffer   = 0;
    long* long_buffer     = 0;
    short* short_buffer   = 0;

    const char* last_path = NULL;
    int in                = -1;

    while (c) {
        int len = netcdf_variable_number_of_values(c);

        if (c->owner->path == NULL) {
            marslog(LOG_EROR, "Missing path %s", c->name);
        }

        if (c->owner->path != last_path) {
            if (last_path) {
                if ((e = nc_close(in)) != NC_NOERR) {
                    marslog(LOG_EROR, "nc_close(%s): %s", last_path, nc_strerror(e));
                    return -2;
                };
            }
            if ((e = nc_open(c->owner->path, NC_NOWRITE, &in)) != NC_NOERR) {
                marslog(LOG_EROR, "nc_open(%s): %s", c->owner->path, nc_strerror(e));
                return -2;
            }
            last_path = c->owner->path;
        }

        switch (c->type) {

                // case NC_BYTE:
                //     break;

            case NC_SHORT:
                short_buffer = NEW_ARRAY(short, len);

                if ((e = nc_get_var_short(in, c->id, short_buffer)) != NC_NOERR) {
                    marslog(LOG_EROR, "nc_get_var_short(%s): %s", path, nc_strerror(e));
                    FREE(short_buffer);
                    return -2;
                }
                if ((e = nc_put_var_short(nc, c->write_id, short_buffer)) != NC_NOERR) {
                    marslog(LOG_EROR, "nc_put_var_short(%s): %s", path, nc_strerror(e));
                    FREE(short_buffer);
                    return -2;
                }

                FREE(short_buffer);
                break;

            case NC_LONG:
                long_buffer = NEW_ARRAY(long, len);

                if ((e = nc_get_var_long(in, c->id, long_buffer)) != NC_NOERR) {
                    marslog(LOG_EROR, "nc_get_var_long(%s): %s", path, nc_strerror(e));
                    FREE(long_buffer);
                    return -2;
                }
                if ((e = nc_put_var_long(nc, c->write_id, long_buffer)) != NC_NOERR) {
                    marslog(LOG_EROR, "nc_put_var_long(%s): %s", path, nc_strerror(e));
                    FREE(long_buffer);
                    return -2;
                }

                FREE(long_buffer);
                break;

                // case NC_CHAR:
                //     break;

            case NC_FLOAT:
                float_buffer = NEW_ARRAY(float, len);

                if ((e = nc_get_var_float(in, c->id, float_buffer)) != NC_NOERR) {
                    marslog(LOG_EROR, "nc_get_var_float(%s): %s", path, nc_strerror(e));
                    FREE(float_buffer);
                    return -2;
                }
                if ((e = nc_put_var_float(nc, c->write_id, float_buffer)) != NC_NOERR) {
                    marslog(LOG_EROR, "nc_put_var_float(%s): %s", path, nc_strerror(e));
                    FREE(float_buffer);
                    return -2;
                }

                FREE(float_buffer);
                break;

            case NC_DOUBLE:

                double_buffer = NEW_ARRAY(double, len);

                if ((e = nc_get_var_double(in, c->id, double_buffer)) != NC_NOERR) {
                    marslog(LOG_EROR, "nc_get_var_double(%s): %s", path, nc_strerror(e));
                    FREE(double_buffer);
                    return -2;
                }
                if ((e = nc_put_var_double(nc, c->write_id, double_buffer)) != NC_NOERR) {
                    marslog(LOG_EROR, "nc_put_var_double(%s): %s", path, nc_strerror(e));
                    FREE(double_buffer);
                    return -2;
                }

                FREE(double_buffer);
                break;

            default:
                marslog(LOG_EROR, "Unknow netcdf type(%s) %d %s", path, c->type, netcdf_type_name(c->type));
                return -2;
        }
        c = c->next;
    }


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

    return 0;
}

static err netcdf_field_save(const char* path, int nc, netcdf_field* c) {
    err e = 0;
    int in;
    int ret = 0;

    if ((e = netcdf_dimension_save(path, nc, &c->dimensions)) != 0) {
        return e;
    }

    if ((e = netcdf_attribute_save(path, nc, NC_GLOBAL, &c->global_attributes)) != 0) {
        return e;
    }

    if ((e = netcdf_variable_create(path, nc, &c->variables)) != 0) {
        return e;
    }

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

    if ((e = netcdf_variable_save(path, nc, &c->variables)) != 0) {
        ret = -2;
    }

    return ret;
}


err netcdf_target_close(netcdf_target* target) {
    int e;
    int ret = 0;

    netcdf_field_list combined = {
        0,
    };

    marslog(LOG_DBUG, "Closing NetCDF file '%s'", target->path);
    // netcdf_field_print(&target->fields, 1);

    ret = netcdf_merge(target->path, &combined, &target->fields);

    if (ret == 0) {
        netcdf_field_save(target->path, target->nc, combined.first);
    }

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

    if (ret != 0) {
        unlink(target->path);
    }

    strfree(target->path);
    netcdf_field_delete(&target->fields);
    netcdf_field_delete(&combined);

    FREE(target);
    return ret;
}
