/* r.in.mat
 *
 * Input a GRASS raster file from a MAT-File (version 4).
 *
 *   Copyright (C) 2004 by the GRASS Development Team
 *   Author: Hamish Bowman, University of Otago, New Zealand
 *
 *   This program is free software under the GPL (>=v2)
 *   Read the COPYING file that comes with GRASS for details.
 *
 *   Code follows r.out.mat, which follows r.out.bin to a certain
 *   extent, which in turn follows r.out.ascii, which is really old.
 *
 *
 *  ARRAY MUST CONTAIN THE FOLLOWING MATRIX :
 *    map_data             with the map data
 *
 *  AND OPTIONALLY :
 *    map_name             name for new map (max 64 chars, normal rules apply)
 *    map_title            contains map title
 *
 *  THESE MUST BE PRESENT UNLESS USING THE "XY" PROJECTION :
 *    map_northern_edge
 *    map_southern_edge    in decimal form (ie not DDD:MM:SS)
 *    map_eastern_edge
 *    map_western_edge
 *
 *  ALL OTHER MATRICES WILL BE PASSED OVER. (cleanly, I hope)
 *
 *   tip: Save a version 4 MAT-File with the command "save filename.mat map_*
 * -v4"
 */

/* #define DEBUG */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <grass/gis.h>
#include <grass/raster.h>
#include <grass/glocale.h>

/* typedef unsigned short uint16;
   typedef unsigned int uint32; */

/* is_nan():  fn to test if incoming data point is either a IEEE NaN or a GRASS
 * CELL null */
int is_nan(void *, RASTER_MAP_TYPE);

int main(int argc, char *argv[])
{

    int i, row, col; /* counters */

    int machine_endianness,
        file_endianness /*, endian_mismatch */; /* 0=little, 1=big */
    int data_format;  /* 0=double  1=float  2=32bit signed int  5=8bit unsigned
                         int (ie text) */
    int data_type;    /* 0=numbers  1=text */
    int format_block; /* combo of endianness, 0, data_format, and type */
    int realflag = 0; /* 0=only real values used */

    /* should type be specifically uint32 ??? */

    char array_name[65]; /* 65 = 64 + null-terminator */
    int name_len;
    int mrows, ncols; /* text/data/map array dimensions */

    int *pval_i;    /* for misc use */
    float *pval_f;  /* for misc use */
    double *pval_d; /* for misc use */
    char c;         /* for misc use */

    char map_name[65], map_title[1024]; /* 65 = 64 + null-terminator */
    double map_name_d[1024]; /* I'm not sure why you'd save char strings as
                                double, but whatever */

    int have_name, have_data, have_title, have_n, have_s, have_e, have_w;

    char *infile, *outfile;
    struct Cell_head region;
    void *raster, *rastline_ptr, *array_data, *array_ptr;
    RASTER_MAP_TYPE map_type;
    struct History history;

    struct Option *inputfile, *outputfile;
    struct GModule *module;

    int cf;
    FILE *fp1;

    G_gisinit(argv[0]);

    module = G_define_module();
    G_add_keyword(_("raster"));
    G_add_keyword(_("import"));
    module->description = _("Imports a binary MAT-File(v4) to a GRASS raster.");

    /* Define the different options */
    inputfile = G_define_standard_option(G_OPT_F_INPUT);
    inputfile->required = YES;
    inputfile->gisprompt = "old,mat,file";
    inputfile->description = _("Name of input MAT-File(v4)");

    outputfile = G_define_standard_option(G_OPT_R_OUTPUT);
    outputfile->required = NO;
    outputfile->description = _("Name for output raster map (override)");

    if (G_parser(argc, argv))
        exit(EXIT_FAILURE);

    /******  SETUP  ****************************************************/
    /* Check Endian State of Host Computer */
    if (G_is_little_endian())
        machine_endianness = 0; /* ie little endian */
    else
        machine_endianness = 1; /* ie big endian */
    G_debug(1, "Machine is %s endian.", machine_endianness ? "big" : "little");

    infile = inputfile->answer;
    outfile = outputfile->answer;

    /* open bin file for reading */
    fp1 = fopen(infile, "rb");
    if (NULL == fp1)
        G_fatal_error(_("Unable to open input file <%s>"), infile);

    have_name = have_data = have_title = 0;
    have_n = have_s = have_e = have_w = 0;

    /* Check Endian State of File */
    if (fread(&format_block, sizeof(int), 1, fp1) != 1)
        G_fatal_error(_("Error reading data"));
    G_fseek(fp1, 0, SEEK_SET); /* frewind() */

    file_endianness = format_block / 1000; /* 0=little, 1=big */
    /* if (file_endianness != machine_endianness)
       endian_mismatch = 1;
       else
       endian_mismatch = 0; */

    G_debug(1, "File is %s endian.\n", file_endianness ? "big" : "little");

    if (format_block > 51)
        G_warning("Only little endian MAT-File(v4) binaries have been tested "
                  "so far! Probably won't work.");

    /******  READ MAP  ****************************************************/
    G_verbose_message(_("Reading MAT-File..."));

    while (!feof(fp1)) {

        /* scan for needed array variables */
        if (fread(&format_block, sizeof(int), 1, fp1) != 1)
            G_fatal_error(_("Error reading data"));

        if (feof(fp1))
            break;

        /* 4 byte data format block = endianness*1000 + data_format*10 +
         * data_type */
        /*   0=double   1=float   2=32bit signed int   5=8bit unsigned int(text)
         */
        data_format = format_block / 10;
        if (data_format != 0 && data_format != 1 && data_format != 2 &&
            data_format != 5)
            G_fatal_error("format [%d]", data_format);

        data_type = format_block - data_format * 10; /* 0=numbers  1=text */
        if (data_type != 0 && data_type != 1)
            G_fatal_error("type [%d]", data_type);

        /* 4 byte number of rows & columns */
        if (fread(&mrows, sizeof(int), 1, fp1) != 1)
            G_fatal_error(_("Error reading data"));
        if (fread(&ncols, sizeof(int), 1, fp1) != 1)
            G_fatal_error(_("Error reading data"));
        if (mrows < 1 || ncols < 1)
            G_fatal_error(_("Array contains no data"));

        /* 4 byte real/imag flag   0=real vals only */
        if (fread(&realflag, sizeof(int), 1, fp1) != 1)
            G_fatal_error(_("Error reading data"));
        if (realflag != 0)
            G_fatal_error(_("Array contains imaginary data"));

        /* length of array_name+1 */
        if (fread(&name_len, sizeof(int), 1, fp1) != 1)
            G_fatal_error(_("Error reading data"));
        if (name_len < 1)
            G_fatal_error(_("Invalid array name"));

        /* array name */
        for (i = 0; i < 64; i++) {
            if (fread(&c, sizeof(char), 1, fp1) != 1)
                G_fatal_error(_("Error reading data"));
            array_name[i] = c;
            if (c == '\0')
                break;
        }

        G_debug(3, "array name     = [%s]", array_name);
        G_debug(3, "  format block = [%04d]", format_block);
        G_debug(3, "  data format  = [%d]", data_format);
        G_debug(3, "  data type    = [%d]", data_type);
        G_debug(3, "  rows         = [%d]", mrows);
        G_debug(3, "  cols         = [%d]", ncols);

        if (strcmp(array_name, "map_name") == 0) {
            have_name = 1;
            if (mrows != 1 || ncols > 64 || data_type != 1)
                G_fatal_error(_("Invalid 'map_name' array"));

            if (data_format == 5) {
                if (fread(&map_name, sizeof(char), ncols, fp1) != (size_t)ncols)
                    G_fatal_error(_("Error reading data"));
            }
            else if (data_format == 0) { /* sigh.. */
                if (fread(&map_name_d, sizeof(double), ncols, fp1) !=
                    (size_t)ncols)
                    G_fatal_error(_("Error reading data"));
                for (i = 0; i < ncols; i++)
                    map_name[i] = (char)map_name_d[i];
            }
            else
                G_fatal_error(_("Error reading 'map_name' array"));

            map_name[ncols] = '\0';
            G_strip(map_name); /* remove leading and trailing whitespace */
            G_debug(1, "map name= <%s>", map_name);
        }

        else if (strcmp(array_name, "map_northern_edge") == 0) {
            have_n = 1;
            if (mrows != 1 || ncols != 1 || data_format != 0 || data_type != 0)
                G_fatal_error(_("Invalid 'map_northern_edge' array"));
            if (fread(&region.north, sizeof(double), 1, fp1) != 1)
                G_fatal_error(_("Error reading data"));
            G_debug(1, "northern edge=%f", region.north);
        }

        else if (strcmp(array_name, "map_southern_edge") == 0) {
            have_s = 1;
            if (mrows != 1 || ncols != 1 || data_format != 0 || data_type != 0)
                G_fatal_error(_("Invalid 'map_southern_edge' array"));
            if (fread(&region.south, sizeof(double), 1, fp1) != 1)
                G_fatal_error(_("Error reading data"));
            G_debug(1, "southern edge=%f", region.south);
        }

        else if (strcmp(array_name, "map_eastern_edge") == 0) {
            have_e = 1;
            if (mrows != 1 || ncols != 1 || data_format != 0 || data_type != 0)
                G_fatal_error(_("Invalid 'map_eastern_edge' array"));
            if (fread(&region.east, sizeof(double), 1, fp1) != 1)
                G_fatal_error(_("Error reading data"));
            G_debug(1, "eastern edge=%f", region.east);
        }

        else if (strcmp(array_name, "map_western_edge") == 0) {
            have_w = 1;
            if (mrows != 1 || ncols != 1 || data_format != 0 || data_type != 0)
                G_fatal_error(_("Invalid 'map_western_edge' array"));
            if (fread(&region.west, sizeof(double), 1, fp1) != 1)
                G_fatal_error(_("Error reading data"));
            G_debug(1, "western edge=%f", region.west);
        }

        else if (strcmp(array_name, "map_title") == 0) {
            have_title = 1;
            if (mrows != 1 || ncols > 1023 || data_type != 1)
                G_fatal_error(_("Invalid 'map_title' array"));

            if (data_format == 5) {
                if (fread(&map_title, sizeof(char), ncols, fp1) !=
                    (size_t)ncols)
                    G_fatal_error(_("Error reading data"));
            }
            else if (data_format == 0) { /* sigh.. */
                if (fread(&map_name_d, sizeof(double), ncols, fp1) !=
                    (size_t)ncols) /* note reusing variable */
                    G_fatal_error(_("Error reading data"));
                for (i = 0; i < ncols; i++)
                    map_title[i] = (char)map_name_d[i];
            }
            else
                G_fatal_error(_("Error reading 'map_title' array"));

            map_title[ncols] = '\0';
            G_strip(map_title); /* remove leading and trailing whitespace */
            G_debug(1, "map title= [%s]", map_title);
        }

        else if (strcmp(array_name, "map_data") == 0) {
            have_data = 1;
            region.rows = (int)mrows;
            region.cols = (int)ncols;

            if (mrows < 1 || ncols < 1 || data_format > 2 || data_type != 0)
                G_fatal_error(_("Invalid 'map_data' array"));

            switch (data_format) {
                /*   0=double       1=float   2=32bit signed int   5=8bit
                 * unsigned int(text)   */
            case 0:
                G_debug(1, " double map");
                map_type = DCELL_TYPE;
                array_data =
                    G_calloc(mrows * (ncols + 1), Rast_cell_size(map_type));
                if (fread(array_data, sizeof(double), (size_t)mrows * ncols,
                          fp1) != (size_t)(mrows * ncols))
                    G_fatal_error(_("Error reading data"));
                break;
            case 1:
                G_debug(1, " float map");
                map_type = FCELL_TYPE;
                array_data =
                    G_calloc(mrows * (ncols + 1), Rast_cell_size(map_type));
                if (fread(array_data, sizeof(float), (size_t)mrows * ncols,
                          fp1) != (size_t)(mrows * ncols))
                    G_fatal_error(_("Error reading data"));
                break;
            case 2:
                G_debug(1, " int map");
                map_type = CELL_TYPE;
                array_data =
                    G_calloc(mrows * (ncols + 1), Rast_cell_size(map_type));
                if (fread(array_data, sizeof(int), (size_t)mrows * ncols,
                          fp1) != (size_t)(mrows * ncols))
                    G_fatal_error(_("Error reading data"));
                break;
            default:
                G_fatal_error(_("Please contact the GRASS development team"));
            }
        } /* endif map_data */

        else {
            G_important_message(_("Skipping unknown array '%s'"), array_name);
            switch (data_format) {
                /*   0=double       1=float   2=32bit signed int   5=8bit
                 * unsigned int(text)   */
            case 0:
                G_fseek(fp1, mrows * ncols * sizeof(double), SEEK_CUR);
                break;
            case 1:
                G_fseek(fp1, mrows * ncols * sizeof(float), SEEK_CUR);
                break;
            case 2:
                G_fseek(fp1, mrows * ncols * sizeof(int), SEEK_CUR);
                break;
            case 5:
                G_fseek(fp1, mrows * ncols * sizeof(char), SEEK_CUR);
                break;
            default:
                G_fatal_error("unusual array");
            }
        }

        G_debug(3, "Read array '%s' [%d,%d] format=%d type=%d\n", array_name,
                ncols, mrows, data_format, data_type);

    } /* while !EOF */

    /******  WRITE MAP  ****************************************************/
    if (0 == have_data)
        G_fatal_error(_("No 'map_data' array found in <%s>"), infile);

    /* set map name */
    if (have_name) {
        if (outfile) {
            if (0 != strcmp(outfile, map_name))
                G_message(_("Setting map name to <%s> which overrides <%s>"),
                          outfile, map_name);
            strncpy(map_name, outfile, 61);
        }
    }
    else {
        if (outfile) {
            G_verbose_message(_("Setting map name to <%s>"), outfile);
            strncpy(map_name, outfile, 61);
        }
        else {
            G_message(_("No 'map_name' array found; using <MatFile>"));
            strcpy(map_name, "MatFile");
        }
    }

    G_strip(map_name); /* remove leading and trailing whitespace */

    /* set region info */
    if (G_projection() != PROJECTION_XY) {
        if ((0 == have_n) || (0 == have_s) || (0 == have_e) || (0 == have_w))
            G_fatal_error(_("Missing bound"));
    }
    else {
        if ((0 == have_n) || (0 == have_s) || (0 == have_e) || (0 == have_w)) {
            G_warning(_("Using default bounds"));
            region.north = (double)region.rows;
            region.south = 0.;
            region.east = (double)region.cols;
            region.west = 0.;
        }
    }

    region.proj = G_projection();
    region.zone = G_zone();
    G_adjust_Cell_head(&region, 1, 1);
    Rast_set_window(&region);

    G_verbose_message(" ");
    G_verbose_message(_("Map <%s> bounds set to:"), map_name);
    G_verbose_message(_("northern edge=%f"), region.north);
    G_verbose_message(_("southern edge=%f"), region.south);
    G_verbose_message(_("eastern edge=%f"), region.east);
    G_verbose_message(_("western edge=%f"), region.west);
    G_verbose_message(_("nsres=%f"), region.ns_res);
    G_verbose_message(_("ewres=%f"), region.ew_res);
    G_verbose_message(_("rows=%d"), region.rows);
    G_verbose_message(_("cols=%d"), region.cols);
    G_verbose_message(" ");

    /* prep memory */
    raster = Rast_allocate_buf(map_type);

    cf = Rast_open_new(map_name, map_type);

    /* write new raster map */
    G_verbose_message(_("Writing new raster map..."));

    mrows = region.rows;
    ncols = region.cols;

    for (row = 0; row < mrows; row++) {
        rastline_ptr = raster;
        for (col = 0; col < ncols; col++) {
            array_ptr = array_data;
            array_ptr = G_incr_void_ptr(
                array_ptr, (row + col * mrows) * Rast_cell_size(map_type));

            if (is_nan(array_ptr, map_type))
                Rast_set_null_value(rastline_ptr, 1, map_type);
            else {
                switch (map_type) {
                case CELL_TYPE:
                    pval_i = (int *)array_ptr;
                    Rast_set_c_value(rastline_ptr, (CELL)pval_i[0], map_type);
                    break;
                case FCELL_TYPE:
                    pval_f = (float *)array_ptr;
                    Rast_set_f_value(rastline_ptr, (FCELL)pval_f[0], map_type);
                    break;
                case DCELL_TYPE:
                    pval_d = (double *)array_ptr;
                    Rast_set_d_value(rastline_ptr, (DCELL)pval_d[0], map_type);
                    break;
                default:
                    Rast_close(cf);
                    G_fatal_error(
                        _("Please contact the GRASS development team"));
                }
            }
            rastline_ptr =
                G_incr_void_ptr(rastline_ptr, Rast_cell_size(map_type));
        }

#ifdef DEBUG
        fprintf(stderr, "row[%d]=[", row);
        rastline_ptr = raster;
        for (col = 0; col < ncols; col++) {
            if (Rast_is_null_value(rastline_ptr, map_type))
                fprintf(stderr, "_");
            else
                fprintf(stderr, "+");
            rastline_ptr =
                G_incr_void_ptr(rastline_ptr, Rast_cell_size(map_type));
        }
        fprintf(stderr, "]\n");
#endif

        Rast_put_row(cf, raster, map_type);

        G_percent(row, mrows, 5);
    }

    G_percent(row, mrows, 5); /* finish it off */

    Rast_close(cf);

    G_free(array_data);
    G_free(raster);

    if (!have_title)
        strncpy(map_title, infile, 1023);

    Rast_put_cell_title(map_name, map_title);

    Rast_short_history(map_name, "raster", &history);
    Rast_command_history(&history);
    Rast_write_history(map_name, &history);

    G_done_msg(" ");

    exit(EXIT_SUCCESS);
}

int is_nan(void *p, RASTER_MAP_TYPE dtype)
{
    /* fn to test if incoming data point is either a IEEE NaN or a GRASS CELL
     * null
     *
     *   Takes a value of type RASTER_MAP_TYPE stored at memory address "p"
     *   Returns 1 if we think it is a null, 0 otherwise
     */

    /* please improve */

    float *pval_f;
    double *pval_d;

    switch (dtype) {
    case CELL_TYPE: /* int doesn't have a IEEE NaN value, but we'll accept
                       GRASS's version */
        if (Rast_is_null_value(p, dtype))
            return 1;
        break;
    case FCELL_TYPE:
        pval_f = (float *)p;
        if (pval_f[0] != pval_f[0])
            return 1;
        break;
    case DCELL_TYPE:
        pval_d = (double *)p;
        if (pval_d[0] != pval_d[0])
            return 1;
        break;
    default:
        G_fatal_error(_("Please contact the GRASS development team"));
    }

    /* otherwise */
    return 0;
}

#ifdef NOTYET
/* for endian swapping, sometime in the future .. */
static void SwabShort(uint16 *wp)
{
    register char *cp = (char *)wp;
    int t;

    t = cp[1];
    cp[1] = cp[0];
    cp[0] = t;
}

static void SwabLong(uint32 *lp)
{
    register char *cp = (char *)lp;
    int t;

    t = cp[3];
    cp[3] = cp[0];
    cp[0] = t;
    t = cp[2];
    cp[2] = cp[1];
    cp[1] = t;
}

static void SwabFloat(float *fp)
{
    SwabLong((uint32 *)fp);
}

static void SwabDouble(double *dp)
{
    register char *cp = (char *)dp;
    int t;

    t = cp[7];
    cp[7] = cp[0];
    cp[0] = t;
    t = cp[6];
    cp[6] = cp[1];
    cp[1] = t;
    t = cp[5];
    cp[5] = cp[2];
    cp[2] = t;
    t = cp[4];
    cp[4] = cp[3];
    cp[3] = t;
}
#endif
