
/***************************************************************************
*
* MODULE:       r.info
*
* AUTHOR(S):    Michael O'Shea
*
* PURPOSE:      Outputs basic information about a user-specified raster map layer.
*
* COPYRIGHT:    (C) 2005-2011 by the GRASS Development Team
*
*               This program is free software under the GNU General Public
*               License (>=v2). Read the file COPYING that comes with GRASS
*               for details.
*
*****************************************************************************/

#include <math.h>
#include <sys/types.h>
#include <grass/config.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <inttypes.h>
#include <grass/gis.h>
#include <grass/raster.h>
#include <grass/glocale.h>
#include "local_proto.h"

#define printline(x) fprintf (out," | %-74.74s |\n",x)
#define divider(x) \
    fprintf (out," %c",x);\
    for (i = 0; i < 76; i++)\
        fprintf(out,"-");\
    fprintf (out,"%c\n",x)

/* local prototypes */
static void format_double(const double, char *);
static void compose_line(FILE *, const char *, ...);


int main(int argc, char **argv)
{
    const char *name, *mapset;
    const char *title;
    char tmp1[100], tmp2[100], tmp3[100];
    char timebuff[256];
    char *units, *vdatum, *semantic_label;
    int i;
    CELL mincat = 0, maxcat = 0, cat;
    FILE *out;
    struct Range crange;
    struct FPRange range;
    struct R_stats rstats;
    struct Cell_head cellhd;
    struct Categories cats;
    struct History hist;
    struct TimeStamp ts;
    int time_ok = 0, first_time_ok = 0, second_time_ok = 0;
    int cats_ok, hist_ok;
    int is_reclass;
    RASTER_MAP_TYPE data_type;
    struct Reclass reclass;
    struct GModule *module;
    struct Option *opt1;
    struct Flag *gflag, *rflag, *eflag, *hflag, *sflag;

    /* Initialize GIS Engine */
    G_gisinit(argv[0]);

    module = G_define_module();
    G_add_keyword(_("raster"));
    G_add_keyword(_("metadata"));
    G_add_keyword(_("extent"));
    G_add_keyword(_("history"));
    module->description =
	_("Outputs basic information about a raster map.");

    opt1 = G_define_standard_option(G_OPT_R_MAP);

    gflag = G_define_flag();
    gflag->key = 'g';
    gflag->description = _("Print raster array information in shell script style");

    rflag = G_define_flag();
    rflag->key = 'r';
    rflag->description = _("Print range in shell script style");

    sflag = G_define_flag();
    sflag->key = 's';
    sflag->description = _("Print stats in shell script style");

    eflag = G_define_flag();
    eflag->key = 'e';
    eflag->description = _("Print extended metadata information in shell script style");

    hflag = G_define_flag();
    hflag->key = 'h';
    hflag->description = _("Print raster history instead of info");

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

    if (hflag->answer && (gflag->answer || rflag->answer || sflag->answer || eflag->answer))
        G_fatal_error(_("Flags -%c and -%c/%c/%c are mutually exclusive"),
                      hflag->key, gflag->key, rflag->key, eflag->key);

    name = G_store(opt1->answer);
    if ((mapset = G_find_raster2(name, "")) == NULL)
	G_fatal_error(_("Raster map <%s> not found"), name);

    Rast_get_cellhd(name, "", &cellhd);
    cats_ok = Rast_read_cats(name, "", &cats) >= 0;
    title = Rast_get_cats_title(&cats);
    hist_ok = Rast_read_history(name, "", &hist) >= 0;
    is_reclass = Rast_get_reclass(name, "", &reclass);
    data_type = Rast_map_type(name, "");

    units = Rast_read_units(name, "");

    vdatum = Rast_read_vdatum(name, "");

    semantic_label = Rast_read_semantic_label(name, "");

    /*Check the Timestamp */
    time_ok = G_read_raster_timestamp(name, "", &ts) > 0;
    /*Check for valid entries, show none if no timestamp available */
    if (time_ok) {
	if (ts.count > 0)
	    first_time_ok = 1;
	if (ts.count > 1)
	    second_time_ok = 1;
    }

    out = stdout;

    if (eflag->answer || (!gflag->answer && !rflag->answer && !sflag->answer && !hflag->answer)) {
	title = "";
	/* empty title by default */
	/* use title from category file as the primary (and only) title */
	if (cats_ok)
	    title = cats.title;
	/* only use hist file title if there is none in the category file */
	if ((!title || title[0] == '\0') && hist_ok) {
	    title = Rast_get_history(&hist, HIST_TITLE);
	    /* if the title is the same as name of the map, don't use it */
	    if (strcmp(title, name) == 0)
		title = "";
	}
    }

    if (!gflag->answer && !rflag->answer && !sflag->answer &&
	!eflag->answer && !hflag->answer) {
	divider('+');

	compose_line(out, "Map:      %-29.29s  Date: %s", name,
		     hist_ok ? Rast_get_history(&hist, HIST_MAPID) : "??");
	compose_line(out, "Mapset:   %-29.29s  Login of Creator: %s",
		     mapset, hist_ok ? Rast_get_history(&hist, HIST_CREATOR) : "??");
	compose_line(out, "Location: %s", G_location());
	compose_line(out, "DataBase: %s", G_gisdbase());
	compose_line(out, "Title:    %s", title);

	/* This shows the TimeStamp */
	if (time_ok && (first_time_ok || second_time_ok)) {
	    G_format_timestamp(&ts, timebuff);
	    compose_line(out, "Timestamp: %s", timebuff);
	}
	else {
	    compose_line(out, "Timestamp: none");
	}

	divider('|');
	printline("");

	if (cats_ok)
	    format_double((double)cats.num, tmp1);

	compose_line(out,
		     "  Type of Map:  %-20.20s Number of Categories: %-9s",
		     hist_ok ? Rast_get_history(&hist, HIST_MAPTYPE) : "??", cats_ok ? tmp1 : "??");

	compose_line(out, "  Data Type:    %-20.20s Semantic label: %s ",
		     (data_type == CELL_TYPE ? "CELL" :
		      (data_type == DCELL_TYPE ? "DCELL" :
		       (data_type == FCELL_TYPE ? "FCELL" : "??"))),
             (semantic_label ? semantic_label : "(none)"));

	/* For now hide these unless they exist to keep the noise low. In
	 *   future when the two are used more widely they can be printed
	 *   along with the standard set. */
	if (units || vdatum)
	    compose_line(out, "  Data Units:   %-20.20s Vertical datum: %s",
			 units ? units : "(none)", vdatum ? vdatum : "(none)");

	{
	    compose_line(out, "  Rows:         %d", cellhd.rows);
	    compose_line(out, "  Columns:      %d", cellhd.cols);
	    compose_line(out, "  Total Cells:  %jd",
			 (grass_int64)cellhd.rows * cellhd.cols);

	    /* This is printed as a guide to what the following eastings and
	     * northings are printed in. This data is NOT from the values
	     * stored in the map's Cell_head */
	    if (G_projection() == PROJECTION_UTM) {
		compose_line(out, "       Projection: %s (zone %d)",
			     G_database_projection_name(), G_zone());
	    }
	    else {
		compose_line(out, "       Projection: %s",
			     G_database_projection_name());
	    }

	    G_format_northing(cellhd.north, tmp1, cellhd.proj);
	    G_format_northing(cellhd.south, tmp2, cellhd.proj);
	    G_format_resolution(cellhd.ns_res, tmp3, cellhd.proj);
	    compose_line(out, "           N: %10s    S: %10s   Res: %5s",
			 tmp1, tmp2, tmp3);

	    G_format_easting(cellhd.east, tmp1, cellhd.proj);
	    G_format_easting(cellhd.west, tmp2, cellhd.proj);
	    G_format_resolution(cellhd.ew_res, tmp3, cellhd.proj);
	    compose_line(out, "           E: %10s    W: %10s   Res: %5s",
			 tmp1, tmp2, tmp3);

	    if (data_type == CELL_TYPE) {
		int ret;
		CELL min, max;

		/* print range only if available */
		ret = Rast_read_range(name, "", &crange);
		if (ret == 2)
		    compose_line(out,
				 "  Range of data:    min = NULL  max = NULL");
		else if (ret > 0) {
		    Rast_get_range_min_max(&crange, &min, &max);

		    if (Rast_is_c_null_value(&min)) {
			compose_line(out,
				     "  Range of data:    min = NULL  max = NULL");
		    }
		    else {
			compose_line(out,
				     "  Range of data:    min = %i  max = %i",
				     min, max);
		    }
		}
	    }
	    else {
		int ret;
		DCELL min, max;

		/* print range only if available */
		ret = Rast_read_fp_range(name, "", &range);
		if (ret == 2) {
		    compose_line(out,
				 "  Range of data:    min = NULL  max = NULL");
		}
		else if (ret > 0) {
		    Rast_get_fp_range_min_max(&range, &min, &max);

		    if (Rast_is_d_null_value(&min)) {
			compose_line(out,
				     "  Range of data:    min = NULL  max = NULL");
		    }
		    else {
			if (data_type == FCELL_TYPE) {
			    compose_line(out, "  Range of data:    min = %.7g  max = %.7g",
					 min, max);
			}
			else {
			    compose_line(out, "  Range of data:    min = %.15g  max = %.15g",
					 min, max);
			}
		    }
		}
	    }
	}

	printline("");

	if (hist_ok) {
	    if (Rast_get_history(&hist, HIST_DATSRC_1)[0] != '\0' ||
		Rast_get_history(&hist, HIST_DATSRC_2)[0] != '\0') {
		printline("  Data Source:");
		compose_line(out, "   %s", Rast_get_history(&hist, HIST_DATSRC_1));
		compose_line(out, "   %s", Rast_get_history(&hist, HIST_DATSRC_2));
		printline("");
	    }

	    printline("  Data Description:");
	    compose_line(out, "   %s", Rast_get_history(&hist, HIST_KEYWRD));

	    printline("");
	    if (Rast_history_length(&hist)) {
		printline("  Comments:  ");

		for (i = 0; i < Rast_history_length(&hist); i++)
		    compose_line(out, "   %s", Rast_history_line(&hist, i));
	    }

	    printline("");
	}

	if (is_reclass > 0) {
	    int first = 1;

	    divider('|');

	    compose_line(out, "  Reclassification of [%s] in mapset [%s]",
			 reclass.name, reclass.mapset);

	    printline("");
	    printline("        Category        Original categories");
	    printline("");

	    for (i = 0; i < reclass.num; i++) {
		CELL x = reclass.table[i];

		if (Rast_is_c_null_value(&x))
		    continue;
		if (first || x < mincat)
		    mincat = x;
		if (first || x > maxcat)
		    maxcat = x;
		first = 0;
	    }

	    if (!first)
		for (cat = mincat; cat <= maxcat; cat++) {
		    char text[80];
		    char *num;
		    int next;

		    if (cat == 0)
			continue;
		    if (G_asprintf(&num, "%5ld", (long)cat) < 1)
			G_fatal_error(_("Cannot allocate memory for string"));

		    next = 0;
		    do {
			next = reclass_text(text, cat, &reclass, next);
			compose_line(out, "     %5s              %s", num,
				     text);
			*num = 0;
		    }
		    while (next >= 0);
		}
	}
	divider('+');

	fprintf(out, "\n");
    }
    else {	/* g, r, s, e, or h flags */
	int need_range, have_range, need_stats, have_stats;
	
	need_range = rflag->answer;
	need_stats = sflag->answer;
	if (need_stats)
	    need_range = 1;

	have_range = have_stats = 0;
	if (need_range) {
	    if (data_type == CELL_TYPE) {
		if (Rast_read_range(name, "", &crange) > 0)
		    have_range = 1;
	    }
	    else {
		if (Rast_read_fp_range(name, "", &range) > 0)
		    have_range = 1;
	    }
	}
	if (need_stats) {
	    if (Rast_read_rstats(name, mapset, &rstats) > 0)
		have_stats = 1;
	}
	
	if ((need_stats && !have_stats) || (need_range && !have_range)) {
	    DCELL *dbuf, val, min, max;
	    int fd, r, c;
	    int first = 1;

	    Rast_set_input_window(&cellhd);
	    dbuf = Rast_allocate_d_input_buf();
	    fd = Rast_open_old(name, mapset);
	    min = max = 0;

	    for (r = 0; r < cellhd.rows; r++) {
		Rast_get_d_row_nomask(fd, dbuf, r);
		for (c = 0; c < cellhd.cols; c++) {
		    val = dbuf[c];
		    if (Rast_is_d_null_value(&val))
			continue;
		    if (first) {
			rstats.sum = val;
			rstats.sumsq = (DCELL) val * val;
			rstats.count = 1;
			min = max = val;

			first = 0;
		    }
		    else {
			rstats.sum += val;
			rstats.sumsq += (DCELL) val * val;
			rstats.count += 1;
			if (min > val)
			    min = val;
			if (max < val)
			    max = val;
		    }
		}
	    }
	    Rast_close(fd);
	    G_free(dbuf);

	    if (data_type == CELL_TYPE) {
		Rast_init_range(&crange);
		if (rstats.count > 0) {
		    Rast_update_range((CELL) min, &crange);
		    Rast_update_range((CELL) max, &crange);
		}
	    }
	    else {
		Rast_init_fp_range(&range);
		if (rstats.count > 0) {
		    Rast_update_fp_range(min, &range);
		    Rast_update_fp_range(max, &range);
		}
	    }
	}

	if (gflag->answer) {
	    G_format_northing(cellhd.north, tmp1, -1);
	    G_format_northing(cellhd.south, tmp2, -1);
	    fprintf(out, "north=%s\n", tmp1);
	    fprintf(out, "south=%s\n", tmp2);

	    G_format_easting(cellhd.east, tmp1, -1);
	    G_format_easting(cellhd.west, tmp2, -1);
	    fprintf(out, "east=%s\n", tmp1);
	    fprintf(out, "west=%s\n", tmp2);

	    G_format_resolution(cellhd.ns_res, tmp3, -1);
	    fprintf(out, "nsres=%s\n", tmp3);

	    G_format_resolution(cellhd.ew_res, tmp3, -1);
	    fprintf(out, "ewres=%s\n", tmp3);

            fprintf(out, "rows=%d\n", cellhd.rows);
            fprintf(out, "cols=%d\n", cellhd.cols);
            
            fprintf(out, "cells=%" PRId64 "\n",
                    (grass_int64)cellhd.rows * cellhd.cols);
            
	    fprintf(out, "datatype=%s\n",
		    (data_type == CELL_TYPE ? "CELL" :
		     (data_type == DCELL_TYPE ? "DCELL" :
		      (data_type == FCELL_TYPE ? "FCELL" : "??"))));
            if (cats_ok)
                format_double((double)cats.num, tmp1);
	    fprintf(out, "ncats=%s\n",
                    cats_ok ? tmp1 : "??");
	}

	if (rflag->answer || sflag->answer) {
	    if (data_type == CELL_TYPE) {
		CELL min, max;
		
		Rast_get_range_min_max(&crange, &min, &max);
		if (Rast_is_c_null_value(&min)) {
		    fprintf(out, "min=NULL\n");
		    fprintf(out, "max=NULL\n");
		}
		else {
		    fprintf(out, "min=%i\n", min);
		    fprintf(out, "max=%i\n", max);
		}
	    }
	    else {
		DCELL min, max;

		Rast_get_fp_range_min_max(&range, &min, &max);
		if (Rast_is_d_null_value(&min)) {
		    fprintf(out, "min=NULL\n");
		    fprintf(out, "max=NULL\n");
		}
		else {
		    if (data_type == FCELL_TYPE) {
			fprintf(out, "min=%.7g\n", min);
			fprintf(out, "max=%.7g\n", max);
		    }
		    else {
			fprintf(out, "min=%.15g\n", min);
			fprintf(out, "max=%.15g\n", max);
		    }
		}
	    }
	}

	if (sflag->answer) {

	    if (!gflag->answer) {
		/* always report total number of cells */
		fprintf(out, "cells=%" PRId64 "\n",
			(grass_int64)cellhd.rows * cellhd.cols);
	    }

	    if (rstats.count > 0) {
		double mean, sd;
		
		mean = (double)(rstats.sum / rstats.count);
		sd = sqrt(rstats.sumsq / rstats.count - (mean * mean));


		if (data_type == CELL_TYPE) {
		    CELL min, max;
		
		    Rast_get_range_min_max(&crange, &min, &max);
		    if (min == max) {
			mean = min;
			sd = 0;
		    }
		}
		else {
		    DCELL min, max;

		    Rast_get_fp_range_min_max(&range, &min, &max);
		    if (min == max) {
			mean = min;
			sd = 0;
		    }
		}

		fprintf(out, "n=%" PRId64 "\n", rstats.count);
		fprintf(out, "mean=%.15g\n", mean);
		fprintf(out, "stddev=%.15g\n", sd);
		fprintf(out, "sum=%.15g\n", rstats.sum);
	    }
	    else {
		fprintf(out, "n=0\n");
		fprintf(out, "mean=NULL\n");
		fprintf(out, "stddev=NULL\n");
		fprintf(out, "sum=NULL\n");
	    }
	}

	if (eflag->answer) {
            char xname[GNAME_MAX], xmapset[GMAPSET_MAX];
            G_unqualified_name(name, mapset, xname, xmapset);

            fprintf(out, "map=%s\n", xname);
	    fprintf(out, "maptype=%s\n", hist_ok ? Rast_get_history(&hist, HIST_MAPTYPE) : "??");
            fprintf(out, "mapset=%s\n", mapset);
            fprintf(out, "location=%s\n", G_location());
            fprintf(out, "database=%s\n", G_gisdbase());
            fprintf(out, "date=\"%s\"\n", hist_ok ? Rast_get_history(&hist, HIST_MAPID) : "??");
            fprintf(out, "creator=\"%s\"\n", hist_ok ? Rast_get_history(&hist, HIST_CREATOR) : "??");
	    fprintf(out, "title=\"%s\"\n", title);
	    if (time_ok && (first_time_ok || second_time_ok)) {

		G_format_timestamp(&ts, timebuff);

		/*Create the r.info timestamp string */
		fprintf(out, "timestamp=\"%s\"\n", timebuff);

	    }
	    else {
		fprintf(out, "timestamp=\"none\"\n");
	    }
	    fprintf(out, "units=%s\n", units ? units : "\"none\"");
	    fprintf(out, "vdatum=%s\n", vdatum ? vdatum : "\"none\"");
        fprintf(out, "semantic_label=%s\n", semantic_label ? semantic_label : "\"none\"");
	    fprintf(out, "source1=\"%s\"\n", hist_ok ? Rast_get_history(&hist, HIST_DATSRC_1) : "\"none\"");
	    fprintf(out, "source2=\"%s\"\n", hist_ok ? Rast_get_history(&hist, HIST_DATSRC_2) : "\"none\"");
	    fprintf(out, "description=\"%s\"\n", hist_ok ? Rast_get_history(&hist, HIST_KEYWRD) : "\"none\"");
	    if (Rast_history_length(&hist)) {
		fprintf(out, "comments=\"");
		for (i = 0; i < Rast_history_length(&hist); i++)
		    fprintf(out, "%s", Rast_history_line(&hist, i));
                fprintf(out, "\"\n");
	    }
	}

	if (hflag->answer) {
	    if (hist_ok) {
		fprintf(out, "Data Source:\n");
		fprintf(out, "   %s\n", Rast_get_history(&hist, HIST_DATSRC_1));
		fprintf(out, "   %s\n", Rast_get_history(&hist, HIST_DATSRC_2));
		fprintf(out, "Data Description:\n");
		fprintf(out, "   %s\n", Rast_get_history(&hist, HIST_KEYWRD));
		if (Rast_history_length(&hist)) {
		    fprintf(out, "Comments:\n");
		    for (i = 0; i < Rast_history_length(&hist); i++)
			fprintf(out, "   %s\n", Rast_history_line(&hist, i));
		}
	    }
	}
    }				/* else rflag or sflag or tflag or gflag or hflag or mflag */

    return EXIT_SUCCESS;
}


static void format_double(const double value, char *buf)
{
    sprintf(buf, "%.8f", value);
    G_trim_decimal(buf);
}


static void compose_line(FILE * out, const char *fmt, ...)
{
    char *line = NULL;
    va_list ap;

    va_start(ap, fmt);

    if (G_vasprintf(&line, fmt, ap) <= 0)
	G_fatal_error(_("Cannot allocate memory for string"));

    va_end(ap);

    printline(line);
    G_free(line);
}
