#include "ncmerge.h"
#include "mars.h"


static boolean netcdf_same_attribute(netcdf_attribute* a, netcdf_attribute* b) {

    if (a->cleared || b->cleared) {
        return false;
    }

    if (a->name == b->name) {
        if (a->type == b->type) {

            if (a->long_value != b->long_value) {
                return false;
            }

            if (a->char_value != b->char_value) {
                return false;
            }

            if (a->float_value != b->float_value) {
                return false;
            }

            if (a->double_value != b->double_value) {
                return false;
            }
            return true;
        }
        else {
            marslog(LOG_WARN, "Attributes %s:%s and %s:%s have different types (%s and %s), currently considered as a mismatch.",
                    a->owner, a->name, b->owner, b->name, netcdf_type_name(a->type), netcdf_type_name(b->type));
            return false;
        }
    }
    return false;
}

static boolean netcdf_same_dimension(netcdf_dimension* a, netcdf_dimension* b) {

    if (a->cleared || b->cleared) {
        return false;
    }
    return a->name == b->name;
}

static boolean netcdf_same_variable(netcdf_variable* a, netcdf_variable* b) {
    if (a->cleared || b->cleared) {
        return false;
    }
    return a->name == b->name;
}

static err netcdf_attribute_merge(netcdf_attribute_list* result, netcdf_attribute_list* list) {
    netcdf_attribute* c = list->first;
    int e;

    while (c) {
        netcdf_attribute* match = NULL;
        netcdf_attribute* r     = result->first;

        while (r) {
            if (r->name == c->name) {
                match = r;
                break;
            }
            r = r->next;
        }

        if (match) {
            if (netcdf_same_attribute(c, match)) {
            }
            else {
                if (!match->cleared) {
                    marslog(LOG_WARN, "Discarding attribute %s:%s", c->owner, c->name);
                    match->cleared = true;
                }
            }
        }
        else {
            netcdf_attribute_clone(result, c);
        }

        c = c->next;
    }

    return 0;
}

static err netcdf_dimension_merge(netcdf_field* target, netcdf_dimension_list* result, netcdf_field* source, netcdf_dimension_list* list) {
    netcdf_dimension* c = list->first;
    int e;
    netcdf_variable* variables[20140];
    size_t count = 0;
    int i;

    while (c) {
        netcdf_dimension* match = NULL;
        netcdf_dimension* r     = result->first;

        while (r) {
            if (r->name == c->name) {
                match = r;
                break;
            }
            r = r->next;
        }

        if (match) {

            marslog(LOG_INFO, "Dimension %s matches", match->name);

            count = NUMBER(variables);
            netcdf_variable_by_dimension(&source->variables, c, variables, &count);
            for (i = 0; i < count; i++) {
                marslog(LOG_INFO, "        var: %s", variables[i]->name);
            }
        }
        else {
            netcdf_dimension_clone(target, result, c);
        }

        c = c->next;
    }

    return 0;
}

static err netcdf_variable_merge_content(netcdf_variable* target, netcdf_variable* source) {
    int e = 0;
    int i;
    netcdf_variable* dim_var;

    if (target->type != source->type) {
        marslog(LOG_WARN, "netcdf_variable_merge_content: target type is %ld, source type is %ld", netcdf_type_name(target->type), netcdf_type_name(source->type));
        return -2;
    }


    printf("netcdf_variable_merge_content source %s (%ld) [", source->name, source->cube.ndims);
    for (i = 0; i < source->cube.ndims; i++) {
        netcdf_dimension* d = source->cube.dims[i];
        if (i)
            printf(", ");
        printf(" %s(%d)", d->name, d->len);
    }
    printf("]\n");

    printf("netcdf_variable_merge_content target %s [", target->name);
    for (i = 0; i < target->cube.ndims; i++) {
        netcdf_dimension* d = target->cube.dims[i];
        if (i)
            printf(", ");
        printf(" %s(%d)", d->name, d->len);
    }
    printf("]\n");


    size_t target_value_count = netcdf_variable_number_of_values(target);
    size_t source_value_count = netcdf_variable_number_of_values(source);

    if (target_value_count != source_value_count) {
        marslog(LOG_WARN, "netcdf_variable_merge_content: target value count is %ld, source value count is %ld", target_value_count, source_value_count);
        return -2;
    }

    if (target->type != source->type) {
        marslog(LOG_WARN, "netcdf_variable_merge_content: target type is %ld, source type is %ld", netcdf_type_name(target->type), netcdf_type_name(source->type));
        return -2;
    }

    void* source_values = netcdf_variable_get_values(source, &source_value_count);
    void* target_values = netcdf_variable_get_values(target, &target_value_count);

    if (memcmp(source_values, target_values, source_value_count * netcdf_type_size(source->type)) != 0) {
        marslog(LOG_WARN, "netcdf_variable_merge_content: content change for variable %s", target->name);
        marslog(LOG_WARN, "============================================== ==============================================");


        // netcdf_attribute *source_coordinates = netcdf_attribute_by_name(&source->attributes, COORDINATES);
        // netcdf_attribute *target_coordinates = netcdf_attribute_by_name(&target->attributes, COORDINATES);

        // if (source_coordinates != NULL) {
        //     marslog(LOG_INFO, "Variable %s has coordinates [%s]", source->name, source_coordinates->char_value );
        // }
        // else {
        //     marslog(LOG_INFO, "Variable %s does not have coordinates", target->name);
        //     for (i = 0; i < source->cube.ndims; i++) {
        //         netcdf_dimension *d = source->cube.dims[i];
        //         if (i) printf(", ");
        //         printf(" %s(%d)", d->name, d->len);
        //     }
        // }
    }

    FREE(source_values);
    FREE(target_values);

    return 0;
}

static err netcdf_variable_merge(netcdf_field* target, netcdf_variable_list* result, netcdf_variable_list* list) {

    netcdf_variable* c = list->first;
    int e;

    while (c) {
        netcdf_variable* match = NULL;
        netcdf_variable* r     = result->first;

        while (r) {
            if (r->name == c->name) {
                match = r;
                break;
            }
            r = r->next;
        }

        if (match) {
            if (netcdf_same_variable(c, match)) {
                if ((e = netcdf_attribute_merge(&match->attributes, &c->attributes)) != 0) {
                    return e;
                }
                if ((e = netcdf_variable_merge_content(match, c)) != 0) {
                    return e;
                }
            }
            else {
                if (!match->cleared) {
                    marslog(LOG_WARN, "Discarding variable %s", c->name);
                    match->cleared = true;
                }
            }
        }
        else {
            netcdf_variable_clone(target, result, c);
        }

        // if( (e = netcdf_attribute_merge(&target->global_attributes, &c->global_attributes)) != 0) {
        //     return e;
        // }

        // netcdf_variable_merge(&c->variables, depth + 1);
        // netcdf_dimension_merge(&c->dimensions, depth + 1);
        c = c->next;
    }

    return 0;
}


static err netcdf_field_merge(const char* target_path, netcdf_field_list* result, netcdf_field_list* list) {

    netcdf_field* target = netcdf_field_new(result, NULL /*target_path*/, 0);
    netcdf_field* c      = list->first;
    int e;

    while (c) {

        if ((e = netcdf_attribute_merge(&target->global_attributes, &c->global_attributes)) != 0) {
            return e;
        }

        if ((e = netcdf_dimension_merge(target, &target->dimensions, c, &c->dimensions)) != 0) {
            return e;
        }

        if ((e = netcdf_variable_merge(target, &target->variables, &c->variables)) != 0) {
            return e;
        }
        c = c->next;
    }

    return 0;
}

err netcdf_merge(const char* target, netcdf_field_list* result, netcdf_field_list* fields) {
    /* Only one field */
    if (fields->first == fields->last) {

        result->first = result->last = fields->first;
        fields->first = fields->last = NULL;
        return 0;
    }

    return netcdf_field_merge(target, result, fields);
}
