
/****************************************************************************
 *
 * MODULE:       v.perturb
 * AUTHOR(S):    James Darrell McCauley darrell@mccauley-usa.com
 * 	         http://mccauley-usa.com/
 * PURPOSE:      Random location perturbations of vector points
 *
 * COPYRIGHT:    (C) 1994-2009 by James Darrell McCauley and the GRASS Development Team
 *
 * Modification History:
 * 3/2006              added min and seed MN/SM ITC-irst
 * 2005                updated to GRASS 6 RB ITC-irst
 * 0.1B <18 Feb 1994>  first version (jdm)
 * 0.2B <02 Jan 1995>  clean man page, added html page (jdm)
 * 0.3B <25 Feb 1995>  cleaned up 'gcc -Wall' warnings (jdm)
 * <13 Sept 2000>      released under GPL
 *
 *               This program is free software under the GNU General
 *               Public License (>=v2). Read the file COPYING that
 *               comes with GRASS for details.
 *
 *****************************************************************************/

#include <stdlib.h>
#include <math.h>

#include <grass/gis.h>
#include <grass/dbmi.h>
#include <grass/vector.h>
#include <grass/glocale.h>

#include "perturb.h"
#include "zufall.h"

int main(int argc, char **argv)
{
    double p1, p2, numbers[1000], numbers2[1000];
    int (*rng) ();
    int i;
    int line, nlines, ttype, n, ret, seed, field;
    struct field_info *Fi, *Fin;
    int out_is_3d;
    double min = 0.;
    int debuglevel = 3;

    struct Map_info In, Out;

    struct line_pnts *Points;
    struct line_cats *Cats;

    struct Cell_head window;
    struct GModule *module;
    struct
    {
	struct Option *in, *out, *dist, *pars, *min, *seed, *field;
	struct Flag *gen_seed, *no_topo;
    } parm;

    G_gisinit(argv[0]);

    module = G_define_module();
    G_add_keyword(_("vector"));
    G_add_keyword(_("geometry"));
    G_add_keyword(_("statistics"));
    G_add_keyword(_("random"));
    G_add_keyword(_("point pattern"));
    G_add_keyword(_("level1"));
 
    module->description =
	_("Random location perturbations of vector points.");

    parm.in = G_define_standard_option(G_OPT_V_INPUT);

    parm.field = G_define_standard_option(G_OPT_V_FIELD_ALL);
    
    parm.out = G_define_standard_option(G_OPT_V_OUTPUT);

    parm.dist = G_define_option();
    parm.dist->key = "distribution";
    parm.dist->type = TYPE_STRING;
    parm.dist->required = NO;
    parm.dist->options = "uniform,normal";
    parm.dist->answer = "uniform";
    parm.dist->description = _("Distribution of perturbation");

    parm.pars = G_define_option();
    parm.pars->key = "parameters";
    parm.pars->type = TYPE_DOUBLE;
    parm.pars->required = YES;
    parm.pars->multiple = YES;
    parm.pars->label = _("Parameter(s) of distribution");
    parm.pars->description = _("If the distribution "
			       "is uniform, only one parameter, the maximum, is needed. "
			       "For a normal distribution, two parameters, the mean and "
			       "standard deviation, are required.");

    parm.min = G_define_option();
    parm.min->key = "minimum";
    parm.min->type = TYPE_DOUBLE;
    parm.min->required = NO;
    parm.min->answer = "0.0";
    parm.min->description = _("Minimum deviation in map units");

    parm.seed = G_define_option();
    parm.seed->key = "seed";
    parm.seed->type = TYPE_INTEGER;
    parm.seed->required = NO;
    parm.seed->description = _("Seed for random number generation");

    parm.gen_seed = G_define_flag();
    parm.gen_seed->key = 's';
    parm.gen_seed->description = _("Generate random seed (result is non-deterministic)");

    parm.no_topo = G_define_standard_flag(G_FLG_V_TOPO);

    /* Either explicit seed or explicitly generate seed, but not both. */
    G_option_exclusive(parm.seed, parm.gen_seed, NULL);
    G_option_required(parm.seed, parm.gen_seed, NULL);

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

    min = atof(parm.min->answer);
    if (parm.seed->answer) {
        seed = atol(parm.seed->answer);
        G_debug(3, "Read random seed from seed=: %d", seed);
    }
    else {
        seed = G_srand48_auto();
        G_debug(3, "Generated random seed (-s): %d", seed);
    }

    switch (parm.dist->answer[0]) {
    case 'u':
	rng = zufall;
	break;
    case 'n':
	rng = normalen;
	break;
    }
    if (rng == zufall) {
	i = sscanf(parm.pars->answer, "%lf", &p1);
	if (i != 1) {
	    G_fatal_error(_("Error scanning arguments"));
	}
	else if (p1 <= 0)
	    G_fatal_error(_("Maximum of uniform distribution must be >= zero"));
    }
    else {
	if ((i = sscanf(parm.pars->answer, "%lf,%lf", &p1, &p2)) != 2) {
	    G_fatal_error(_("Error scanning arguments"));
	}
	if (p2 <= 0)
	    G_fatal_error(_("Standard deviation of normal distribution must be >= zero"));
    }

    G_get_window(&window);

    /* Open input */
    Vect_set_open_level(2);
    if (Vect_open_old_head2(&In, parm.in->answer, "", parm.field->answer) < 0)
	G_fatal_error(_("Unable to open vector map <%s>"), parm.in->answer);
    
    field = Vect_get_field_number(&In, parm.field->answer);
    
    out_is_3d = WITHOUT_Z;
    if (Vect_is_3d(&In))
	out_is_3d = WITH_Z;

    /* Open output */
    if (Vect_open_new(&Out, parm.out->answer, out_is_3d) < 0)
	G_fatal_error(_("Unable to create vector map <%s>"), parm.out->answer);
    /* TODO: Add also optional z perturb support */

    Vect_hist_copy(&In, &Out);
    Vect_hist_command(&Out);

    /* Generate a bunch of random numbers */
    zufalli(&seed);
    myrng(numbers, 1000, rng, p1 - min, p2);
    myrng(numbers2, 1000, rng, p1, p2);

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

    nlines = Vect_get_num_lines(&In);

    /* Close input, re-open on level 1 */
    Vect_close(&In);
    Vect_set_open_level(1);
    if (Vect_open_old2(&In, parm.in->answer, "", parm.field->answer) < 0)
	G_fatal_error(_("Unable to open vector map <%s>"), parm.in->answer);

    i = 0;
    line = 0;
    while (1) {
	int type = Vect_read_next_line(&In, Points, Cats);

	/* Note: check for dead lines is not needed, because they are skipped by V1_read_next_line_nat() */
	if (type == -1) {
	    G_fatal_error(_("Unable to read vector map"));
	}
	else if (type == -2) {
	    break;
	}
	G_percent(line++, nlines, 4);

	if (type & GV_POINT) {
	    if (field != -1 && !Vect_cat_get(Cats, field, NULL))
		continue;

	    if (i >= 800) {
		/* Generate some more random numbers */
		myrng(numbers, 1000, rng, p1 - min, p2);
		myrng(numbers2, 1000, rng, p1, p2);
		i = 0;
	    }

	    G_debug(debuglevel, "x:      %f y:      %f", Points->x[0],
		    Points->y[0]);

	    /* perturb */
	    /* TODO: tends to concentrate in box corners when min is used */
	    if (numbers2[i] >= 0) {
		if (numbers[i] >= 0) {
		    G_debug(debuglevel, "deltax: %f", numbers[i] + min);
		    Points->x[0] += numbers[i++] + min;
		}
		else {
		    G_debug(debuglevel, "deltax: %f", numbers[i] - min);
		    Points->x[0] += numbers[i++] - min;
		}
		Points->y[0] += numbers2[i++];
	    }
	    else {
		if (numbers[i] >= 0) {
		    G_debug(debuglevel, "deltay: %f", numbers[i] + min);
		    Points->y[0] += numbers[i++] + min;
		}
		else {
		    G_debug(debuglevel, "deltay: %f", numbers[i] - min);
		    Points->y[0] += numbers[i++] - min;
		}
		Points->x[0] += numbers2[i++];
	    }

	    G_debug(debuglevel, "x_pert: %f y_pert: %f", Points->x[0],
		    Points->y[0]);
	}

	Vect_write_line(&Out, type, Points, Cats);
    }
    G_percent(1, 1, 1);

    /* Copy tables */
    n = Vect_get_num_dblinks(&In);
    ttype = GV_1TABLE;
    if (n > 1)
	ttype = GV_MTABLE;

    for (i = 0; i < n; i++) {
	Fi = Vect_get_dblink(&In, i);
	if (Fi == NULL) {
	    G_fatal_error(_("Cannot get db link info"));
	}
	Fin = Vect_default_field_info(&Out, Fi->number, Fi->name, ttype);
	Vect_map_add_dblink(&Out, Fi->number, Fi->name, Fin->table, Fi->key,
			    Fin->database, Fin->driver);

	ret = db_copy_table(Fi->driver, Fi->database, Fi->table,
			    Fin->driver, Vect_subst_var(Fin->database, &Out),
			    Fin->table);
	if (ret == DB_FAILED) {
	    G_warning("Cannot copy table");
	}
    }

    Vect_close(&In);

    if (!parm.no_topo->answer)
	Vect_build(&Out);
    Vect_close(&Out);

    return (EXIT_SUCCESS);
}
