#include "mars.h"
#include "mars_client_config.h"

#if mars_client_HAVE_NETCDF
#include <netcdf.h>


static err read_offset(const char* path, FILE* f, int length, unsigned long* v) {
    unsigned char buf[8];
    int i;

    *v = 0;

    if (fread(buf, 1, length, f) != length) {
        marslog(LOG_WARN, "check_hdf5_superblock: Cannot read enough bytes from %s", path);
        return -2;
    }

    for (i = length - 1; i >= 0; i--) {
        *v <<= 8;
        *v |= buf[i];
    }

    return 0;
}

struct super_block {
    unsigned long base_address;
    unsigned long end_off_file_address;
    unsigned char version;
};

err read_hdf5_superblock01(const char* path, FILE* f, struct super_block* sb) {

    unsigned char version_of_file_free_space, version_of_root_group_symbol_table, version_number_shared_header;

    unsigned char size_of_offsets, size_of_lengths;
    unsigned int group_node_k;
    unsigned int consistency_flags;
    unsigned long reserved;
    unsigned long file_free_space_info;

    // already read signature and version : 8 + 1 bytes = (9)

    char skip[4];

    // version_of_file_free_space : bytes +1 = (10)

    if (fread(&version_of_file_free_space, 1, 1, f) != 1) {
        marslog(LOG_WARN, "check_hdf5_superblock: Cannot read enough bytes from %s", path);
        return -2;
    }

    // version_of_root_group_symbol_table : bytes +1 = (11)

    if (fread(&version_of_root_group_symbol_table, 1, 1, f) != 1) {
        marslog(LOG_WARN, "check_hdf5_superblock: Cannot read enough bytes from %s", path);
        return -2;
    }

    // reserved 1 byte : bytes +1 = (12)

    if (fread(&reserved, 1, 1, f) != 1) {
        marslog(LOG_WARN, "check_hdf5_superblock: Cannot read enough bytes from %s", path);
        return -2;
    }

    // version_number_shared_header : bytes +1 = (13)

    if (fread(&version_number_shared_header, 1, 1, f) != 1) {
        marslog(LOG_WARN, "check_hdf5_superblock: Cannot read enough bytes from %s", path);
        return -2;
    }

    // size_of_offsets : bytes +1 = (14)

    if (fread(&size_of_offsets, 1, 1, f) != 1) {
        marslog(LOG_WARN, "check_hdf5_superblock: Cannot read enough bytes from %s", path);
        return -2;
    }

    // size_of_lengths : bytes +1 = (15)

    if (fread(&size_of_lengths, 1, 1, f) != 1) {
        marslog(LOG_WARN, "check_hdf5_superblock: Cannot read enough bytes from %s", path);
        return -2;
    }

    // reserved : bytes +1 = (16)

    if (fread(&reserved, 1, 1, f) != 1) {
        marslog(LOG_WARN, "check_hdf5_superblock: Cannot read enough bytes from %s", path);
        return -2;
    }

    // group_node_k : bytes +4 = (20)

    if (fread(&skip, 1, 4, f) != 4) {
        marslog(LOG_WARN, "check_hdf5_superblock: Cannot read enough bytes from %s", path);
        return -2;
    }

    // consistency_flags : bytes +4 = (24)

    if (fread(&skip, 1, 4, f) != 4) {
        marslog(LOG_WARN, "check_hdf5_superblock: Cannot read enough bytes from %s", path);
        return -2;
    }

    if (sb->version == 1) {
        if (fread(&skip, 1, 4, f) != 4) {
            marslog(LOG_WARN, "check_hdf5_superblock: Cannot read enough bytes from %s", path);
            return -2;
        }
    }

    if (read_offset(path, f, size_of_offsets, &(sb->base_address))) {
        marslog(LOG_WARN, "check_hdf5_superblock: Cannot read enough bytes from %s", path);
        return -2;
    }

    if (read_offset(path, f, size_of_offsets, &file_free_space_info)) {
        marslog(LOG_WARN, "check_hdf5_superblock: Cannot read enough bytes from %s", path);
        return -2;
    }

    if (read_offset(path, f, size_of_offsets, &(sb->end_off_file_address))) {
        marslog(LOG_WARN, "check_hdf5_superblock: Cannot read enough bytes from %s", path);
        return -2;
    }

    return 0;
}

err read_hdf5_superblock23(const char* path, FILE* f, struct super_block* sb) {

    unsigned char size_of_offsets, size_of_lengths, consistency_flags;
    unsigned long superblock_extension_address;

    // already read signature and version : 8 + 1 bytes = (9)

    // size_of_offsets : bytes +1 = (10)

    if (fread(&size_of_offsets, 1, 1, f) != 1) {
        marslog(LOG_WARN, "check_hdf5_superblock: Cannot read enough bytes from %s", path);
        return -2;
    }

    // size_of_lengths : bytes +1 = (11)

    if (fread(&size_of_lengths, 1, 1, f) != 1) {
        marslog(LOG_WARN, "check_hdf5_superblock: Cannot read enough bytes from %s", path);
        return -2;
    }

    // consistency_flags : bytes +1 = (12)

    if (fread(&consistency_flags, 1, 1, f) != 1) {
        marslog(LOG_WARN, "check_hdf5_superblock: Cannot read enough bytes from %s", path);
        return -2;
    }

    if (read_offset(path, f, size_of_offsets, &(sb->base_address))) {
        marslog(LOG_WARN, "check_hdf5_superblock: Cannot read enough bytes from %s", path);
        return -2;
    }

    if (read_offset(path, f, size_of_offsets, &superblock_extension_address)) {
        marslog(LOG_WARN, "check_hdf5_superblock: Cannot read enough bytes from %s", path);
        return -2;
    }

    if (read_offset(path, f, size_of_offsets, &(sb->end_off_file_address))) {
        marslog(LOG_WARN, "check_hdf5_superblock: Cannot read enough bytes from %s", path);
        return -2;
    }

    return 0;
}

err check_hdf5_superblock(const char* path) {

    /* http://www.hdfgroup.org/HDF5/doc/H5.format.html#Superblock */

    FILE* f = fopen(path, "r");

    int e, nc, format, number_of_groups, number_of_types;

    err ecode = 0;

    struct super_block sb;
    struct stat st;

    unsigned char signature[8];

    marslog(LOG_INFO, "Check if %s is supported NetCDF format", path);

    if (!f) {
        marslog(LOG_EROR | LOG_PERR, "check_hdf5_superblock: cannot open %s", path);
        return -2;
    }

    if (fread(signature, 1, 8, f) != 8) {
        marslog(LOG_WARN, "check_hdf5_superblock: Cannot read enough bytes from %s", path);
        fclose(f);
        return -2;
    }

    /* http://www.hdfgroup.org/HDF5/doc/H5.format.html#FileMetaData */

    if (!(signature[0] == 137 && signature[1] == 'H' && signature[2] == 'D' && signature[3] == 'F' && signature[4] == '\r' && signature[5] == '\n' && signature[6] == '\032' && signature[7] == '\n')) {
        marslog(LOG_WARN, "check_hdf5_superblock: wrong magic %s", path);
        fclose(f);
    }


    if (fread(&(sb.version), 1, 1, f) != 1) {
        marslog(LOG_WARN, "check_hdf5_superblock: Cannot read enough bytes from %s", path);
        fclose(f);
        return -2;
    }

    marslog(LOG_DBUG, "check_hdf5_superblock: path %s has superblock %d", path, sb.version);

    switch (sb.version) {
        case 0:
            ecode = read_hdf5_superblock01(path, f, &sb);
            break;

        case 1:
            ecode = read_hdf5_superblock01(path, f, &sb);
            break;

        case 2:
            ecode = read_hdf5_superblock23(path, f, &sb);
            break;

        case 3:
            ecode = read_hdf5_superblock23(path, f, &sb);
            break;

        default:
            marslog(LOG_WARN, "check_hdf5_superblock: Unsupported superblock version %d", sb.version);
    }

    fclose(f);

    if (ecode)
        return ecode;

    if (stat(path, &st) < 0) {
        marslog(LOG_EROR | LOG_PERR, "Cannot stat %s", path);
        return -2;
    }

    if (st.st_size != sb.end_off_file_address) {
        marslog(LOG_EROR, "%p: file is %ld bytes long, HDF5 header says it is %ld bytes", path, st.st_size, sb.end_off_file_address);
        return -2;
    }

    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_format(nc, &format)) != NC_NOERR) {
        marslog(LOG_EROR, "nc_inq_format(%s): %s", path, nc_strerror(e));
        return -2;
    }

    switch (format) {

        case NC_FORMAT_CLASSIC:
            marslog(LOG_DBUG, "%s is NC_FORMAT_CLASSIC", path);
            break;

        case NC_FORMAT_64BIT:
            marslog(LOG_DBUG, "%s is NC_FORMAT_64BIT", path);
            break;

        case NC_FORMAT_NETCDF4:
            marslog(LOG_DBUG, "%s is NC_FORMAT_NETCDF4", path);
            break;

        case NC_FORMAT_NETCDF4_CLASSIC:
            marslog(LOG_DBUG, "%s is NC_FORMAT_NETCDF4_CLASSIC", path);
            break;

        default:
            marslog(LOG_EROR, "Unknow netcdf format: %s (%d)", path, format);
            return -2;
    }
    /* Check that NetCDF is 'classic' */

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

    if (number_of_groups > 1) {
        marslog(LOG_EROR, "NetCDF is not 'classic' (%s): Number of groups is %d", path, number_of_groups);
        return -2;
    }

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

    if (number_of_types > 0) {
        marslog(LOG_EROR, "NetCDF is not 'classic' (%s): Number of types is %d", path, number_of_types);
        return -2;
    }

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

    marslog(LOG_INFO, "%s is supported NetCDF format", path);

    return 0;
}

#endif
