
/****************************************************************************
 *
 * MODULE:       v.in.ascii
 *               
 * AUTHOR(S):    Original authors Michael Higgins, James Westervelt (CERL)
 *               Updated to GRASS 5.7 Radim Blazek, ITC-Irst, Trento, Italy
 * PURPOSE:      Converts a vector map in ASCII format to a vector map
 *               in binary format
 *
 * COPYRIGHT:    (C) 2000-2014 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 <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <grass/gis.h>
#include <grass/dbmi.h>
#include <grass/vector.h>
#include <grass/glocale.h>
#include "local_proto.h"

#define	A_DIR	"dig_ascii"

static char *get_td(const struct Option *option)
{
    char* sep;

    if (option->answer == NULL)
        return NULL;

    if (strcmp(option->answer, "doublequote") == 0)
        sep = G_store("\"");
    else if (strcmp(option->answer, "singlequote") == 0)
        sep = G_store("\'");
    else if (strcmp(option->answer, "none") == 0)
        sep = NULL;
    else
        sep = G_store(option->answer);

    return sep;
}


int main(int argc, char *argv[])
{
    FILE *ascii;
    struct GModule *module;
    struct Option *old, *new, *delim_opt, *tdelim_opt, *columns_opt, 
	          *xcol_opt, *ycol_opt, *zcol_opt, *catcol_opt,
		  *format_opt, *skip_opt;
    int xcol, ycol, zcol, catcol, format, skip_lines;
    struct Flag *zcoorf, *t_flag, *e_flag, *noheader_flag, *notopol_flag,
	*region_flag, *ignore_flag;
    char *table;
    char *fs, *td;
    char *desc;
    int zcoor = WITHOUT_Z, make_table;

    struct Map_info Map;

    G_gisinit(argv[0]);

    module = G_define_module();
    G_add_keyword(_("vector"));
    G_add_keyword(_("import"));
    G_add_keyword("ASCII");
    G_add_keyword(_("level1"));
    G_add_keyword(_("area"));
    G_add_keyword(_("line"));
    G_add_keyword(_("point"));

    module->description =
	_("Creates a vector map from an ASCII points file or ASCII vector file.");

    /************************** Command Parser ************************************/
    old = G_define_standard_option(G_OPT_F_INPUT);
    old->label = _("Name of input file to be imported");
    old->description = ("'-' for standard input");
    
    new = G_define_standard_option(G_OPT_V_OUTPUT);

    format_opt = G_define_option();
    format_opt->key = "format";
    format_opt->type = TYPE_STRING;
    format_opt->required = NO;
    format_opt->multiple = NO;
    format_opt->options = "point,standard";
    desc = NULL;
    G_asprintf(&desc,
	       "point;%s;standard;%s",
	       _("simple x,y[,z] list"),
	       _("GRASS vector ASCII format"));
    format_opt->descriptions = desc;
    format_opt->answer = "point";
    format_opt->description = _("Input file format");
    format_opt->guisection = _("Input format");

    delim_opt = G_define_standard_option(G_OPT_F_SEP);
    delim_opt->guisection = _("Input format");
    
    tdelim_opt = G_define_standard_option(G_OPT_F_SEP);
    tdelim_opt->key = "text";
    tdelim_opt->label = "Text delimiter";
    tdelim_opt->answer = NULL;
    tdelim_opt->description = _("Special characters: doublequote, singlequote, none");
    tdelim_opt->guisection = _("Input format");

    skip_opt = G_define_option();
    skip_opt->key = "skip";
    skip_opt->type = TYPE_INTEGER;
    skip_opt->required = NO;
    skip_opt->multiple = NO;
    skip_opt->answer = "0";
    skip_opt->description =
	_("Number of header lines to skip at top of input file (points mode)");
    skip_opt->guisection = _("Points");

    columns_opt = G_define_option();
    columns_opt->key = "columns";
    columns_opt->type = TYPE_STRING;
    columns_opt->required = NO;
    columns_opt->multiple = NO;
    columns_opt->guisection = _("Points");
    columns_opt->label = _("Column definition in SQL style (points mode)");
    columns_opt->description = _("For example: "
				 "'x double precision, y double precision, cat int, "
				 "name varchar(10)'");
    
    xcol_opt = G_define_option();
    xcol_opt->key = "x";
    xcol_opt->type = TYPE_INTEGER;
    xcol_opt->required = NO;
    xcol_opt->multiple = NO;
    xcol_opt->answer = "1";
    xcol_opt->guisection = _("Points");
    xcol_opt->label = ("Number of column used as x coordinate (points mode)");
    xcol_opt->description = _("First column is 1");

    ycol_opt = G_define_option();
    ycol_opt->key = "y";
    ycol_opt->type = TYPE_INTEGER;
    ycol_opt->required = NO;
    ycol_opt->multiple = NO;
    ycol_opt->answer = "2";
    ycol_opt->guisection = _("Points");
    ycol_opt->label = _("Number of column used as y coordinate (points mode)");
    ycol_opt->description = _("First column is 1");

    zcol_opt = G_define_option();
    zcol_opt->key = "z";
    zcol_opt->type = TYPE_INTEGER;
    zcol_opt->required = NO;
    zcol_opt->multiple = NO;
    zcol_opt->answer = "0";
    zcol_opt->guisection = _("Points");
    zcol_opt->label = _("Number of column used as z coordinate (points mode)");
    zcol_opt->description = _("First column is 1. If 0, z coordinate is not used");

    catcol_opt = G_define_option();
    catcol_opt->key = "cat";
    catcol_opt->type = TYPE_INTEGER;
    catcol_opt->required = NO;
    catcol_opt->multiple = NO;
    catcol_opt->answer = "0";
    catcol_opt->guisection = _("Points");
    catcol_opt->label =
	_("Number of column used as category (points mode)");
    catcol_opt->description =
	_("First column is 1. If 0, unique category is assigned to each row and written to new column 'cat'");
    
    zcoorf = G_define_flag();
    zcoorf->key = 'z';
    zcoorf->description = _("Create 3D vector map");

    e_flag = G_define_flag();
    e_flag->key = 'e';
    e_flag->description =
	_("Create a new empty vector map and exit. Nothing is read from input.");

    noheader_flag = G_define_flag();
    noheader_flag->key = 'n';
    noheader_flag->description =
	_("Do not expect a header when reading in standard format");
    noheader_flag->guisection = _("Input format");

    t_flag = G_define_flag();
    t_flag->key = 't';
    t_flag->description = _("Do not create table in points mode");
    t_flag->guisection = _("Points");

    notopol_flag = G_define_standard_flag(G_FLG_V_TOPO);
    notopol_flag->description = _("Do not build topology in points mode");
    notopol_flag->guisection = _("Points");

    region_flag = G_define_flag();
    region_flag->key = 'r';
    region_flag->description =
	_("Only import points falling within current region (points mode)");
    region_flag->guisection = _("Points");

    ignore_flag = G_define_flag();
    ignore_flag->key = 'i';
    ignore_flag->description = _("Ignore broken line(s) in points mode");
    ignore_flag->guisection = _("Points");

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


    if (format_opt->answer[0] == 'p')
	format = GV_ASCII_FORMAT_POINT;
    else
	format = GV_ASCII_FORMAT_STD;

    skip_lines = atoi(skip_opt->answer);
    if (skip_lines < 0)
	G_fatal_error(_("Please specify reasonable number of lines to skip"));

    if (zcoorf->answer && format == GV_ASCII_FORMAT_POINT && !zcol_opt->answer)
	G_fatal_error(_("Please specify z column"));

    xcol = atoi(xcol_opt->answer) - 1;
    ycol = atoi(ycol_opt->answer) - 1;
    zcol = atoi(zcol_opt->answer) - 1;

    /* specifying zcol= implies that a 3D map is needed */
    if (zcol >= 0 && !zcoorf->answer)
	zcoorf->answer = TRUE;

    if (zcoorf->answer && format == GV_ASCII_FORMAT_POINT && zcol < 0)
	G_fatal_error(_("Please specify reasonable z column"));

    catcol = atoi(catcol_opt->answer) - 1;

    if (xcol < 0 || ycol < 0 || zcol + 1 < 0 || catcol + 1 < 0)
	G_fatal_error(_("Column numbers must not be negative"));

    ascii = G_open_option_file(old);
    fs = G_option_to_separator(delim_opt);
    td = get_td(tdelim_opt);

    /* check dimension */
    if (zcoorf->answer) {
	zcoor = 1;
    }

    if (Vect_open_new(&Map, new->answer, zcoor) < 0)
	G_fatal_error(_("Unable to create vector map <%s>"), new->answer);

    Vect_set_error_handler_io(NULL, &Map);
    Vect_hist_command(&Map);

    if (e_flag->answer) {
	Vect_build(&Map);
	Vect_close(&Map);
	exit(EXIT_SUCCESS);
    }

    if (format == GV_ASCII_FORMAT_POINT) {
	int i, rowlen, ncols, minncols, *coltype, *coltype2, *collen, nrows;
	char **colsample;
	int n_int = 0, n_double = 0, n_string = 0;
	char buf[1000];
	struct field_info *Fi;
	char *tmp, *key;
	dbDriver *driver;
	dbString sql;
	FILE *tmpascii;

	/* Open temporary file */
	tmp = G_tempfile();
	if (NULL == (tmpascii = fopen(tmp, "w+"))) {
	    G_fatal_error(_("Unable to open temporary file <%s>"), tmp);
	}
	unlink(tmp);

	points_analyse(ascii, tmpascii, fs, td, &rowlen, &ncols, &minncols,
		       &nrows, &coltype, &colsample, &collen, skip_lines, xcol, ycol,
		       zcol, catcol, region_flag->answer, ignore_flag->answer);

	G_verbose_message(_("Maximum input row length: %d"), rowlen);
        if (ncols != minncols) {
            G_message(_("Maximum number of columns: %d"), ncols);
            G_message(_("Minimum number of columns: %d"), minncols);
        }
        else {
	   G_message(_("Number of columns: %d"), ncols);
        }

        G_message(_("Number of data rows: %d"), nrows - skip_lines);
        
	/* check column numbers */
	if (xcol >= minncols) {
	    G_fatal_error(_("'%s' column number > minimum last column number "
                            "(incorrect field separator or format?)"), "x");
	}
	if (ycol >= minncols) {
	    G_fatal_error(_("'%s' column number > minimum last column number "
                            "(incorrect field separator or format?)"), "y");

	}
	if (zcol >= minncols) {
	    G_fatal_error(_("'%s' column number > minimum last column number "
                            "(incorrect field separator or format?)"), "z");

	}
	if (catcol >= minncols) {
	    G_fatal_error(_("'%s' column number > minimum last column number "
                            "(incorrect field separator or format?)"), "cat");
	}

	if (coltype[xcol] == DB_C_TYPE_STRING) {
	    G_fatal_error(_("'%s' column is not of number type, "
	                    "encountered: '%s'"), "x", colsample[xcol]);
	}
	if (coltype[ycol] == DB_C_TYPE_STRING) {
	    G_fatal_error(_("'%s' column is not of number type, "
	                    "encountered: '%s'"), "y", colsample[ycol]);
	}
	if (zcol >= 0 && coltype[zcol] == DB_C_TYPE_STRING) {
	    G_fatal_error(_("'%s' column is not of number type, "
	                    "encountered: '%s'"), "z", colsample[zcol]);
	}
	if (catcol >= 0 && coltype[catcol] == DB_C_TYPE_STRING) {
	    G_fatal_error(_("'%s' column is not of number type, "
	                    "encountered: '%s'"), "cat", colsample[catcol]);
	}

	/* Create table */
	make_table = FALSE;
	for (i = 0; i < ncols; i++) {
	    if (xcol != i && ycol != i && zcol != i && catcol != i) {
		make_table = TRUE;
		break;
	    }
	}
	if (t_flag->answer) {
	    make_table = FALSE;
	}

	coltype2 = NULL;

	if (make_table) {
	    Fi = Vect_default_field_info(&Map, 1, NULL, GV_1TABLE);
	    driver =
		db_start_driver_open_database(Fi->driver,
					      Vect_subst_var(Fi->database,
							     &Map));
	    if (driver == NULL) {
		G_fatal_error(_("Unable to open database <%s> by driver <%s>"),
			      Vect_subst_var(Fi->database, &Map), Fi->driver);
	    }
	    db_begin_transaction(driver);

	    db_init_string(&sql);
	    sprintf(buf, "create table %s ( ", Fi->table);
	    db_append_string(&sql, buf);

	    if (catcol < 0) {
		db_append_string(&sql, "cat integer, ");
	    }

	    for (i = 0; i < ncols; i++) {
		if (i > 0 && !columns_opt->answer) {
		    db_append_string(&sql, ", ");
		}
		if (catcol == i && coltype[i] != DB_C_TYPE_INT) {
		    G_fatal_error(_("Category column is not of integer type"));
		}

		switch (coltype[i]) {
		case DB_C_TYPE_INT:
		    G_verbose_message("Column: %d  type: integer", i + 1);
		    if (!columns_opt->answer) {
			sprintf(buf, "int_%d integer", n_int + 1);
			db_append_string(&sql, buf);
			if (catcol == i) {
			    sprintf(buf, "int_%d", n_int + 1);
			    key = G_store(buf);
			}
		    }
		    n_int++;
		    break;
		case DB_C_TYPE_DOUBLE:
		    G_verbose_message("Column: %d  type: double", i + 1);
		    if (!columns_opt->answer) {
			sprintf(buf, "dbl_%d double precision", n_double + 1);
			db_append_string(&sql, buf);
		    }
		    n_double++;
		    break;
		case DB_C_TYPE_STRING:
		    G_verbose_message("Column: %d  type: string length: %d",
				      i + 1, collen[i]);
		    if (!columns_opt->answer) {
			sprintf(buf, "str_%d varchar(%d)", n_string + 1,
				collen[i]);
			db_append_string(&sql, buf);
		    }
		    n_string++;
		    break;
		}
	    }
	    if (columns_opt->answer) {
		db_append_string(&sql, columns_opt->answer);
	    }
	    db_append_string(&sql, " )");

	    /* this link is added with default 'cat' key, later deleted and replaced by true key name,
	     * otherwise if module crashes when the table exists but link is not written it makes troubles */
	    Vect_map_add_dblink(&Map, 1, NULL, Fi->table, GV_KEY_COLUMN, Fi->database,
				Fi->driver);

	    /* Create table */
	    G_debug(3, "%s", db_get_string(&sql));
	    if (db_execute_immediate(driver, &sql) != DB_OK) {
		G_fatal_error(_("Unable to create table: %s"),
			      db_get_string(&sql));
	    }

	    /* Grant */
	    if (db_grant_on_table
		(driver, Fi->table, DB_PRIV_SELECT,
		 DB_GROUP | DB_PUBLIC) != DB_OK) {
		G_fatal_error(_("Unable to grant privileges on table <%s>"),
			      Fi->table);
	    }

	    /* Check column types */
	    if (columns_opt->answer) {
		int nc;
		dbTable *table;
		dbColumn *column;

		db_set_string(&sql, Fi->table);
		if (db_describe_table(driver, &sql, &table) != DB_OK) {
		    G_fatal_error(_("Unable to describe table <%s>"),
				  Fi->table);
		}

		nc = db_get_table_number_of_columns(table);

		if ((catcol >= 0 && nc != ncols) ||
		    (catcol < 0 && (nc - 1) != ncols)) {
		    G_fatal_error(_("Number of columns defined (%d) does not match number "
				   "of columns (%d) in input"),
				  catcol < 0 ? nc - 1 : nc, ncols);
		}

		coltype2 = (int *)G_malloc(ncols * sizeof(int));

		for (i = 0; i < ncols; i++) {
		    int dbcol, ctype, length;

		    if (catcol < 0)
			dbcol = i + 1;	/* first is category */
		    else
			dbcol = i;

		    column = db_get_table_column(table, dbcol);
		    ctype =
			db_sqltype_to_Ctype(db_get_column_sqltype(column));
		    length = db_get_column_length(column);
		    coltype2[i] = ctype;

		    if (catcol == i) {	/* if catcol == -1 it cannot be tru */
			key = G_store(db_get_column_name(column));
		    }

		    switch (coltype[i]) {
		    case DB_C_TYPE_INT:
			/* coltype=int and colsample=NULL indicate,
			 * an empty column, so we don't report anything. */
			if (ctype == DB_C_TYPE_DOUBLE && colsample[i]) {
			    G_warning(_("Column number %d <%s> defined as double "
				       "has only integer values"), i + 1,
				      db_get_column_name(column));
			}
			else if (ctype == DB_C_TYPE_STRING && colsample[i]) {
			    G_warning(_("Column number %d <%s> defined as string "
				       "has only integer values"), i + 1,
				      db_get_column_name(column));
			}
			break;
		    case DB_C_TYPE_DOUBLE:
			if (ctype == DB_C_TYPE_INT) {
			    G_fatal_error(_("Column number %d <%s> defined as integer "
					    "has double values, encountered: '%s'"),
					  i + 1,
					  db_get_column_name(column),
					  colsample[i]);
			}
			else if (ctype == DB_C_TYPE_STRING) {
			    G_warning(_("Column number %d <%s> defined as string "
				       "has double values"), i + 1,
				      db_get_column_name(column));
			}
			break;
		    case DB_C_TYPE_STRING:
			if (ctype == DB_C_TYPE_INT) {
			    G_fatal_error(_("Column number %d <%s> defined as integer "
					    "has string values, encountered: '%s'"),
					  i + 1, db_get_column_name(column),
					  colsample[i]);
			}
			else if (ctype == DB_C_TYPE_DOUBLE) {
			    G_fatal_error(_("Column number %d <%s> defined as double "
					    "has string values, encountered: '%s'"),
					  i + 1, db_get_column_name(column),
					  colsample[i]);
			}
			if (length < collen[i]) {
			    G_fatal_error(_("Length of column %d <%s> (%d) is less than "
					   "maximum value " "length (%d)"),
					  i + 1, db_get_column_name(column),
					  length, collen[i]);
			}
			break;
		    }
		}
	    }
	    else {
		coltype2 = coltype;
	    }

	    if (catcol < 0) {
		key = GV_KEY_COLUMN;
	    }

	    if (db_create_index2(driver, Fi->table, key) != DB_OK)
		G_warning(_("Unable to create index for table <%s>, key <%s>"),
			  Fi->table, key);

	    Vect_map_del_dblink(&Map, 1);
	    Vect_map_add_dblink(&Map, 1, NULL, Fi->table, key, Fi->database,
				Fi->driver);

	    table = Fi->table;
	}
	else {
	    driver = NULL;
	    table = NULL;
	}

	points_to_bin(tmpascii, rowlen, &Map, driver, table, fs, td, nrows,
		      coltype2, xcol, ycol, zcol, catcol, skip_lines);

	if (driver) {
	    G_message(_("Populating table..."));
	    db_commit_transaction(driver);
	    if (db_close_database_shutdown_driver(driver) == DB_FAILED)
		G_fatal_error(_("Unable to close attribute table. The DBMI driver did not accept all attributes"));
	}
	fclose(tmpascii);
    }

    else {			/* FORMAT_ALL (standard mode) */
	if (!noheader_flag->answer)
            if (Vect_read_ascii_head(ascii, &Map) == -1)
                G_fatal_error(_("Import failed"));

	if (Vect_read_ascii(ascii, &Map) == -1)
            G_fatal_error(_("Import failed"));
    }

    G_close_option_file(ascii);

    if (notopol_flag->answer) {
	Vect_close(&Map);
    }
    else {
	Vect_build(&Map);
	Vect_close(&Map);
    }

    exit(EXIT_SUCCESS);
}
