#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <grass/gis.h>
#include <grass/dbmi.h>
#include <grass/vector.h>
#include <grass/glocale.h>
#include "local_proto.h"

/* Determine if the string is integer, e.g. 123, +123, -123, 
 * return 1 if integer, 0 otherwise */
static int is_int(char *str)
{
    char *tail;

    if (strtol(str, &tail, 10), tail == str || *tail != '\0') {
	/* doesn't look like a number,
	   or has extra characters after what looks to be a number */
	return 0;
    }

    return 1;
}


/* Determine if the string is double, e.g. 123.456, +123.456, -123.456, 1.23456e2
 * return 1 if double, 0 otherwise */
static int is_double(char *str)
{
    char *tail;

    if (strtod(str, &tail), tail == str || *tail != '\0') {
	/* doesn't look like a number,
	   or has extra characters after what looks to be a number */
	return 0;
    }

    return 1;
}



/* Analyse points ascii file. Determine number of columns and column types.
 * ascii_tmp: write copy of tempfile to ascii_tmp:
 * rowlength: maximum row length
 * ncolumns: number of columns
 * minncolumns: minimum number of columns
 * nrows: number of rows
 * column_type: column types
 * column_sample: values which was used to decide the type or NULLs
 * column_length: column lengths (string only)
 *
 * If the who whole column is empty, column_sample will contain NULL
 * for that given column.
 */

int points_analyse(FILE * ascii_in, FILE * ascii, char *fs, char *td,
		   int *rowlength, int *ncolumns, int *minncolumns,
		   int *nrows, int **column_type, char ***column_sample,
		   int **column_length,
		   int skip_lines, int xcol, int ycol, int zcol, int catcol, 
		   int region_flag, int ignore_flag)
{
    int i;
    int buflen;			/* buffer length */
    char *buf;			/* buffer */
    int row = 1;		/* line number, first is 1 */
    int ncols = 0;		/* number of columns */
    int minncols = -1;
    int *coltype = NULL;	/* column types */
    char **colsample = NULL;	/* column samples */
    int *collen = NULL;		/* column lengths */
    char **tokens;
    int ntokens;		/* number of tokens */
    int len, rowlen = 0;	/* maximum row length */
    struct Cell_head window;
    double northing = .0;
    double easting = .0;
    char *xtoken, *ytoken, *sav_buf;
    int skip = FALSE, skipped = 0;

    buflen = 4000;
    buf = (char *)G_malloc(buflen);
    xtoken = (char *)G_malloc(256);
    ytoken = (char *)G_malloc(256);

    G_message(_("Scanning input for column types..."));
    /* fetch projection for LatLong test */
    G_get_window(&window);

    /* points_to_bin() would be faster if we would write out 
     * clean data to ascii
     * points_to_bin() would then not need G_chop() and 
     * for latlon not G_scan_[easting|northing]() */

    while (1) {
	len = 0;		/* not really needed, but what the heck */
	skip = FALSE;		/* reset out-of-region check */
	sav_buf = NULL;

	if (G_getl2(buf, buflen - 1, ascii_in) == 0)
	    break;		/* EOF */

	if (row <= skip_lines) {
	    G_debug(3, "skipping header row %d : %d chars", row,
		    (int)strlen(buf));
	    /* this fn is read-only, write to hist with points_to_bin() */
	    fprintf(ascii, "%s\n", buf);
	    len = strlen(buf) + 1;
	    if (len > rowlen)
		rowlen = len;

	    row++;
	    continue;
	}

	if ((buf[0] == '#') || (buf[0] == '\0')) {
	    G_debug(3, "skipping comment row %d : %d chars", row,
		    (int)strlen(buf));
	    continue;
	}

	/* no G_chop() as first/last column may be empty fs=tab value */
	G_debug(3, "row %d : %d chars", row, (int)strlen(buf));

	tokens = G_tokenize2(buf, fs, td);
	ntokens = G_number_of_tokens(tokens);
	if (ntokens == 0) {
	    continue;
	}

	if (ncols > 0 && ntokens != ncols) {
	    /* these rows can not be imported into the attribute table */
	    if (ignore_flag) {
		G_warning(_("Row %d: '%s' can not be imported into the attribute table"),
		          row, buf);
	    }
	    else {
		G_warning(_("Expected %d columns, found %d columns"), ncols, ntokens);
		G_fatal_error(_("Broken row %d: '%s'"), row, buf);
	    }
	}
	if (xcol >= ntokens || ycol >= ntokens || zcol >= ntokens || 
	    catcol >= ntokens) {
	    if (ignore_flag) {
		G_debug(3, "Skipping broken row %d: '%s'", row, buf);
		continue;
	    }
	    else {
		G_warning(_("ntokens: %d, xcol: %d, ycol: %d, zcol: %d"), ntokens, xcol, ycol, zcol);
		G_fatal_error(_("Broken row %d: '%s'"), row, buf);
	    }
	}

	len = strlen(buf) + 1;
	if (len > rowlen)
	    rowlen = len;

	if (ntokens > ncols) {
	    coltype = (int *)G_realloc(coltype, ntokens * sizeof(int));
	    colsample = (char **)G_realloc(colsample, ntokens * sizeof(char *));
	    collen = (int *)G_realloc(collen, ntokens * sizeof(int));
	    for (i = ncols; i < ntokens; i++) {
		coltype[i] = DB_C_TYPE_INT;	/* default type */
		/* We store a value later if column is not empty. */
		colsample[i] = NULL;
		collen[i] = 0;
	    }
	    ncols = ntokens;
	}

	if (minncols == -1 || minncols > ntokens)
	    minncols = ntokens;

	/* Determine column types */
	for (i = 0; i < ntokens; i++) {
	    G_chop(tokens[i]);
	    if ((G_projection() == PROJECTION_LL)) {
		if (i == xcol || i == ycol) {
		    if (i == 0) {	/* Save position of original internal token buffer */
			/* Prevent memory leaks */
			sav_buf = tokens[0];
		    }
		    /* check if coordinates are DMS or decimal or not latlong at all */
		    if (i == xcol) {
			if (G_scan_easting(tokens[i], &easting, window.proj)) {
			    G_debug(4, "is_latlong east: %g", easting);
			    sprintf(xtoken, "%.15g", easting);
			    /* replace current DMS token by decimal degree */
			    tokens[i] = xtoken;
			    if (region_flag) {
				if ((window.east < easting) ||
				    (window.west > easting))
				    skip = TRUE;
			    }
			}
			else {
                            fprintf(stderr, _("Current row %d:\n%s\n"), row, buf);
			    G_fatal_error(_("Unparsable longitude value in column %d: %s"),
					  i + 1, tokens[i]);
			}
		    }

		    if (i == ycol) {
			if (G_scan_northing(tokens[i], &northing, window.proj)) {
			    G_debug(4, "is_latlong north: %g", northing);
			    sprintf(ytoken, "%.15g", northing);
			    /* replace current DMS token by decimal degree */
			    tokens[i] = ytoken;
			    if (region_flag) {
				if ((window.north < northing) ||
				    (window.south > northing))
				    skip = TRUE;
			    }
			}
			else {
			    fprintf(stderr, _("Current row %d:\n%s\n"), row, buf);
			    G_fatal_error(_("Unparsable latitude value in column %d: %s"),
					  i + 1, tokens[i]);
			}
		    }
		}		/* if (x or y) */
	    }			/* PROJECTION_LL */
	    else {
		if (strlen(tokens[i]) == 0) {
		    if (i == xcol) {
			G_fatal_error(_("Unparsable longitude value in column %d: %s"),
				      i + 1, tokens[i]);
		    }
		    if (i == ycol) {
			G_fatal_error(_("Unparsable latitude value in column %d: %s"),
				      i + 1, tokens[i]);
		    }
		}
		if (region_flag) {
		    /* consider z range if -z flag is used? */
		    /* change to if(>= east,north){skip=1;} to allow correct tiling */
		    /* don't "continue;" so multiple passes will have the
		       same column types and length for patching */
		    if (i == xcol) {
			easting = atof(tokens[i]);
			if ((window.east < easting) ||
			    (window.west > easting))
			    skip = TRUE;
		    }
		    if (i == ycol) {
			northing = atof(tokens[i]);
			if ((window.north < northing) ||
			    (window.south > northing))
			    skip = TRUE;
		    }
		}
	    }

	    len = strlen(tokens[i]); 
	    /* do not guess column type for missing values */ 
	    /* continue here ensures that we preserve NULLs in
	     * colsample for (completely) empty columns (which, however,
	     * should probably default to string rather than int). */
	    if (len == 0) 
		continue;

	    G_debug(4, "row %d col %d: '%s' is_int = %d is_double = %d",
		    row + 1, i + 1, tokens[i], is_int(tokens[i]),
		    is_double(tokens[i]));

	    if (is_int(tokens[i])) {
		/* We store the first encountered value for integers.
		 * Rest is for consistency. */
		if (!colsample[i] || coltype[i] != DB_C_TYPE_INT) {
		    G_free(colsample[i]);
		    colsample[i] = G_store(tokens[i]);
		}
		continue;	/* integer */
	    }
	    if (is_double(tokens[i])) {	/* double */
		if (coltype[i] == DB_C_TYPE_INT) {
		    coltype[i] = DB_C_TYPE_DOUBLE;
		    G_free(colsample[i]);
		    colsample[i] = G_store(tokens[i]);
		}
		continue;
	    }
	    /* string */
	    if (coltype[i] != DB_C_TYPE_STRING) {
		/* Only set type if not already set to store the field
		 * only once and to show the first encountered item. */
		coltype[i] = DB_C_TYPE_STRING;
		G_free(colsample[i]);
		colsample[i] = G_store(tokens[i]);
	    }
	    if (len > collen[i])
		collen[i] = len;
	}

	/* write dataline to tmp file */
	if (!skip)
	    fprintf(ascii, "%s\n", buf);
	else
	    skipped++;

	if (sav_buf != NULL) {
	    /* Restore original token buffer so free_tokens works */
	    /* Only do this if tokens[0] was re-assigned */
	    tokens[0] = sav_buf;
	    sav_buf = NULL;
	}

	G_free_tokens(tokens);
	row++;
    }

    *rowlength = rowlen;
    *ncolumns = ncols;
    *minncolumns = minncols;
    *column_type = coltype;
    *column_sample = colsample;
    *column_length = collen;
    *nrows = row - 1;		/* including skipped lines */

    G_free(buf);
    G_free(xtoken);
    G_free(ytoken);

    if (region_flag)
	G_message(n_("Skipping %d of %d row falling outside of current region",
                     "Skipping %d of %d rows falling outside of current region",
                     row - 1),
		  skipped, row - 1);

    return 0;
}


/* Import points from ascii file.
 *
 * fs: field separator
 * xcol, ycol, zcol, catcol: x,y,z,cat column in input file, first column is 1, 
 *                            zcol and catcol may be 0 (do not use)
 * rowlen: maximum row length
 * Note: column types (both in header or coldef) must be supported by driver
 */
int points_to_bin(FILE * ascii, int rowlen, struct Map_info *Map,
		  dbDriver * driver, char *table, char *fs, char *td,
		  int nrows, int *coltype, int xcol, int ycol, int zcol,
		  int catcol, int skip_lines)
{
    char *buf, buf2[4000];
    int cat = 0;
    int row = 0;
    struct line_pnts *Points;
    struct line_cats *Cats;
    dbString sql, val;
    struct Cell_head window;

    G_message(_("Importing points..."));
    /* fetch projection for LatLong test */
    G_get_window(&window);

    rewind(ascii);
    Points = Vect_new_line_struct();
    Cats = Vect_new_cats_struct();

    /* actually last 2 characters won't be read */
    buf = (char *)G_malloc(rowlen + 2);
    db_init_string(&sql);
    db_init_string(&val);

    if (skip_lines > 0) {
	sprintf(buf2, "HEADER: (%d lines)\n", skip_lines);
	Vect_hist_write(Map, buf2);
    }

    /* rowlen + 2 to read till the end of line on both UNIX and Windows */
    while (G_getl2(buf, rowlen + 2, ascii) != 0) {
	int i, len;
	double x, y, z;
	char **tokens;
	int ntokens;		/* number of tokens */

	G_percent(row, nrows, 2);
	row++;

	if (row <= skip_lines) {
	    G_debug(4, "writing skip line %d to hist : %d chars", row,
		    (int)strlen(buf));
	    Vect_hist_write(Map, buf);
	    Vect_hist_write(Map, "\n");
	    continue;
	}

	len = strlen(buf);
	if (len == 0)
	    continue;		/* should not happen */

	G_debug(4, "row: %s", buf);

	tokens = G_tokenize2(buf, fs, td);
	ntokens = G_number_of_tokens(tokens);

	G_chop(tokens[xcol]);
	G_chop(tokens[ycol]);

	if ((G_projection() == PROJECTION_LL)) {
	    G_scan_easting(tokens[xcol], &x, window.proj);
	    G_scan_northing(tokens[ycol], &y, window.proj);
	}
	else {
	    x = atof(tokens[xcol]);
	    y = atof(tokens[ycol]);
	}
	G_debug(4, "x: %f, y: %f", x, y);

	if (zcol >= 0) {
	    G_chop(tokens[zcol]);
	    z = atof(tokens[zcol]);
	}
	else
	    z = 0.0;

	if (catcol >= 0) {
	    G_chop(tokens[catcol]);
	    cat = atof(tokens[catcol]);
	}
	else
	    cat++;

	Vect_reset_line(Points);
	Vect_reset_cats(Cats);

	Vect_append_point(Points, x, y, z);
	Vect_cat_set(Cats, 1, cat);

	Vect_write_line(Map, GV_POINT, Points, Cats);

	/* Attributes */
	if (driver) {
	    sprintf(buf2, "insert into %s values ( ", table);
	    db_set_string(&sql, buf2);

	    if (catcol < 0) {
		sprintf(buf2, "%d, ", cat);
		db_append_string(&sql, buf2);
	    }

	    for (i = 0; i < ntokens; i++) {
		G_chop(tokens[i]);
		if (i > 0)
		    db_append_string(&sql, ", ");

		if (strlen(tokens[i]) > 0) {
		    if (coltype[i] == DB_C_TYPE_INT ||
			coltype[i] == DB_C_TYPE_DOUBLE) {
			if (G_projection() == PROJECTION_LL &&
			    (i == xcol || i == ycol)) {
			    if (i == xcol)
				sprintf(buf2, "%.15g", x);
			    else
				sprintf(buf2, "%.15g", y);
			}
			else
			    sprintf(buf2, "%s", tokens[i]);
		    }
		    else {
			db_set_string(&val, tokens[i]);
			/* TODO: strip leading and trailing "quotes" from input string */
			db_double_quote_string(&val);
			sprintf(buf2, "'%s'", db_get_string(&val));
		    }
		}
		else {
		    sprintf(buf2, "null");
		}
		db_append_string(&sql, buf2);
	    }
	    db_append_string(&sql, ")");
	    G_debug(3, "%s", db_get_string(&sql));

	    if (db_execute_immediate(driver, &sql) != DB_OK) {
		G_fatal_error(_("Unable to insert new record: %s"),
			      db_get_string(&sql));
	    }
	}

	G_free_tokens(tokens);
    }
    G_percent(nrows, nrows, 2);

    return 0;
}
