#include <stdio.h>
#include <stdlib.h>
#include <grass/dbmi.h>
#include <grass/Vect.h>
#include "global.h"

/* Query database for qfield. */

int 
query (struct Map_info *Map )
{
    int i, j, idx, cat_no, nlines, type;
    register int line_num;
    struct line_pnts *Points;
    struct line_cats *Cats;
    struct field_info *Fi;
    dbString stmt, value_string;
    dbDriver *driver;

    /* Initialize the Point struct */
    Points = Vect_new_line_struct();
    Cats = Vect_new_cats_struct ();

    /* Cycle through all lines and make a list of categories of 'qfield' for each category given by 'field' */
    fprintf ( stderr, "Reading data trom the map ... " );
    nlines = Vect_get_num_lines ( Map );
    for (line_num = 1 ; line_num <= nlines; line_num++) {
	G_percent( line_num, nlines, 1 );
	
	type = Vect_read_line ( Map, Points, Cats, line_num);
	if ( !(type & options.type ) ) continue;
		
	for ( i = 0; i < Cats->n_cats; i++ ) {
	    if ( Cats->field[i] == options.field ) {
	
		cat_no = Cats->cat[i];

		idx = find_cat( cat_no);
		
		for ( j = 0; j < Cats->n_cats; j++ ) {
		    if ( Cats->field[j] == options.qfield ) { /* Add to list */
			if ( Values[idx].nqcats == Values[idx].aqcats ) { /* Alloc space */
			    Values[idx].aqcats += 2;
			    Values[idx].qcat = (int *) G_realloc ( Values[idx].qcat, Values[idx].aqcats * sizeof(int) );
			}
			Values[idx].qcat[Values[idx].nqcats] = Cats->cat[j];
			Values[idx].nqcats++;
		    }
		}
	    }
	}

	/* If there is no field cat add cat -1, values for cat -1 are reported at the end  */
	Vect_cat_get ( Cats, options.field, &cat_no );

	if  ( cat_no == -1 ) {
	    idx = find_cat( cat_no);
	    
	    for ( j = 0; j < Cats->n_cats; j++ ) {
		if ( Cats->field[j] == options.qfield ) { /* Add to list */
		    if ( Values[idx].nqcats == Values[idx].aqcats ) { /* Alloc space */
			Values[idx].aqcats += 2;
			Values[idx].qcat = (int *) G_realloc ( Values[idx].qcat, Values[idx].aqcats * sizeof(int) );
		    }
		    Values[idx].qcat[Values[idx].nqcats] = Cats->cat[j];
		    Values[idx].nqcats++;
		}
	    }
	}
    }

    db_init_string (&stmt);
    db_init_string (&value_string);

    if ( (Fi = Vect_get_field ( Map, options.qfield)) == NULL)
	G_fatal_error("Database connection not defined for layer <%d>. Use v.db.connect first.", options.qfield);

    /* Open driver */
    driver = db_start_driver_open_database ( Fi->driver, Fi->database );
    if ( driver == NULL )
	G_fatal_error ( "Cannot open database %s by driver %s", Fi->database, Fi->driver );
    
    /* Query the database for each category */
    fprintf ( stderr, "Querying database ... " );
    for ( i = 0; i < vstat.rcat; i++ ) {
	int  j, ctype, nrows, more;
        char buf[2000];
	dbCursor cursor;
	dbTable *table;
	dbColumn *column;
	dbValue *value;

	G_debug (3, "cat %d", Values[i].cat );
	G_percent( i+1, vstat.rcat, 1 );

	/* Skip if cat is zero and large number of query categories (many features without category).
	 * It would cause problems on server side and take long time. Postgres limit is 10000 */
	if ( Values[i].cat == 0 && Values[i].nqcats > 1000 ) {
	    G_warning ( "Query for category '0' (no category) was not executed because of too many "
	        "(%d) query categories. All later reported values for cat 0 are not valid.", 
		Values[i].nqcats ); 
	    continue;
	}
	
	if ( Values[i].nqcats > 0 ) {
	    sprintf ( buf, "SELECT %s FROM %s WHERE", options.qcol, Fi->table);
	    db_set_string(&stmt, buf); 

	    for ( j = 0; j < Values[i].nqcats; j++ ) {
		G_debug (4, "  qcat %d", Values[i].qcat[j] );

		if ( j > 0 ) db_append_string (&stmt, " OR");
		    
	        sprintf ( buf, " %s = %d", Fi->key, Values[i].qcat[j]);
		db_append_string (&stmt, buf);
	    }
	    G_debug (4, "  SQL: '%s'", db_get_string (&stmt));

	    if (db_open_select_cursor(driver, &stmt, &cursor, DB_SEQUENTIAL) != DB_OK)
		G_fatal_error("Cannot open cursor: '%s'", db_get_string (&stmt));

	    table = db_get_cursor_table (&cursor);
	    column = db_get_table_column(table, 0); /* first column */
	    value  = db_get_column_value(column);
	    ctype = db_sqltype_to_Ctype(db_get_column_sqltype(column));
            vstat.qtype = ctype;
	    nrows = db_get_num_rows ( &cursor );
	    
	    G_debug (4, "  nrows = %d, columnt type = %d", nrows, ctype );
	    
	    if ( nrows != 1 ) {
                if ( nrows > 1 ) {
		    G_warning ("Multiple query results, output value set to NULL (category %d)", 
			          Values[i].cat);
		}
		Values[i].null = 1;
	    } else {
		if(db_fetch (&cursor, DB_NEXT, &more) != DB_OK)
		    G_fatal_error("Cannot fetch record");

	        db_convert_column_value_to_string(column, &stmt);
	        G_debug (4, "  value = %s", db_get_string(&stmt) );

		if ( db_test_value_isnull(value) ) {
		    Values[i].null = 1;
		} else {
		    switch ( ctype ) {
			case ( DB_C_TYPE_INT ):
			    Values[i].i1 = db_get_value_int(value);
			    break;
			case ( DB_C_TYPE_DOUBLE ):
			    Values[i].d1 = db_get_value_double(value);
			    break;
			case ( DB_C_TYPE_STRING ):
			    Values[i].str1 = G_store( db_get_value_string(value) );
			    break;
			case ( DB_C_TYPE_DATETIME ):
			    db_convert_column_value_to_string (column, &value_string);
			    Values[i].str1 = G_store( db_get_string(&value_string) );
		    }
		    Values[i].null = 0;
		}
	    }
	    db_close_cursor (&cursor);
	} else {  /* no qcats -> upload NULL */
	    Values[i].null = 1;
	}
    }

    db_close_database_shutdown_driver ( driver );

    return 0;
}

