/* ***************************************************************
 * *
 * * MODULE:       v.clean
 * * 
 * * AUTHOR(S):    Radim Blazek
 * *               OGR support by Martin Landa <landa.martin gmail.com> (2009)
 * *
 * * PURPOSE:      Clean vector features
 * *               
 * * COPYRIGHT:    (C) 2001-2009, 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 <stdlib.h>
#include <string.h>
#include <stdio.h>

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

#include "proto.h"

static void error_handler_err(void *p);

int main(int argc, char *argv[])
{
    struct Map_info In, Out, Err, *pErr;
    int i, otype, with_z, native;
    struct GModule *module;
    struct {
	struct Option *in, *field, *out, *type, *tool, *thresh,
	    *err;
    } opt;
    struct {
	struct Flag *no_build, *combine;
    } flag;
    int *tools, ntools, atools;
    double *threshs;
    int count;
    double size;
    char *desc;
    
    G_gisinit(argv[0]);

    module = G_define_module();
    G_add_keyword(_("vector"));
    G_add_keyword(_("topology"));
    G_add_keyword(_("geometry"));
    G_add_keyword(_("snapping"));
    module->description = _("Toolset for cleaning topology of vector map.");

    opt.in = G_define_standard_option(G_OPT_V_INPUT);

    opt.field = G_define_standard_option(G_OPT_V_FIELD_ALL);
    opt.field->answer = "-1";
    opt.field->guisection = _("Selection");

    opt.type = G_define_standard_option(G_OPT_V3_TYPE);
    opt.type->guisection = _("Selection");

    opt.out = G_define_standard_option(G_OPT_V_OUTPUT);

    opt.err = G_define_standard_option(G_OPT_V_OUTPUT);
    opt.err->key = "error";
    opt.err->description = _("Name of output map where errors are written");
    opt.err->required = NO;

    opt.tool = G_define_option();
    opt.tool->key = "tool";
    opt.tool->type = TYPE_STRING;
    opt.tool->required = YES;
    opt.tool->multiple = YES;
    opt.tool->options =
	"break,snap,rmdangle,chdangle,rmbridge,chbridge,rmdupl,rmdac,bpol,prune,"
	"rmarea,rmline,rmsa";
    opt.tool->description = _("Cleaning tool");
    desc = NULL;
    G_asprintf(&desc,
	       "break;%s;"
	       "rmdupl;%s;"
	       "rmdangle;%s;"
	       "chdangle;%s;"
	       "rmbridge;%s;"
	       "chbridge;%s;"
	       "snap;%s;"
	       "rmdac;%s;"
	       "bpol;%s;"
	       "prune;%s;"
	       "rmarea;%s;"
	       "rmline;%s;"
	       "rmsa;%s",
	       _("break lines at each intersection"),
	       _("remove duplicate geometry features (pay attention to categories!)"),
	       _("remove dangles, threshold ignored if < 0"),
	       _("change the type of boundary dangle to line, "
		 "threshold ignored if < 0, input line type is ignored"),
	       _("remove bridges connecting area and island or 2 islands"),
	       _("change the type of bridges connecting area and island "
		 "or 2 islands from boundary to line"),
	       _("snap lines to vertex in threshold"),
	       _("remove duplicate area centroids ('type' option ignored)"),
	       _("break (topologically clean) polygons (imported from "
		 "non topological format, like ShapeFile). Boundaries are broken on each "
		 "point shared between 2 and more polygons where angles of segments are different"),
	       _("remove vertices in threshold from lines and boundaries, "
		 "boundary is pruned only if topology is not damaged (new intersection, "
		 "changed attachment of centroid), first and last segment of the boundary "
		 "is never changed"),
	       _("remove small areas, the longest boundary with adjacent area is removed"),
	       _("remove all lines or boundaries of zero length, threshold is ignored"),
	       _("remove small angles between lines at nodes"));
    opt.tool->descriptions = desc;
    
    opt.thresh = G_define_option();
    opt.thresh->key = "threshold";
    opt.thresh->type = TYPE_DOUBLE;
    opt.thresh->required = NO;
    opt.thresh->multiple = YES;
    opt.thresh->label = _("Threshold in map units, one value for each tool");
    opt.thresh->description = _("Default: 0.0[,0.0,...])");

    flag.no_build = G_define_flag();
    flag.no_build->key = 'b';
    flag.no_build->description =
	_("Do not build topology for the output vector");

    flag.combine = G_define_flag();
    flag.combine->key = 'c';
    flag.combine->description =
	_("Combine tools with recommended follow-up tools");

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

    otype = Vect_option_to_types(opt.type);

    Vect_check_input_output_name(opt.in->answer, opt.out->answer,
				 G_FATAL_EXIT);
    if (opt.err->answer) {
	Vect_check_input_output_name(opt.in->answer, opt.err->answer,
				     G_FATAL_EXIT);
    }

    atools = 20;
    tools = (int *)G_malloc(atools * sizeof(int));

    /* Read tools */
    ntools = 0;
    i = 0;
    if (strlen(opt.tool->answer) < 1)
	G_fatal_error(_("You must select at least one tool"));
    while (opt.tool->answers[i]) {
	if (i + 1 >= atools) {
	    atools += 20;
	    G_realloc(tools, atools * sizeof(int));
	}

	G_debug(1, "tool : %s", opt.tool->answers[i]);
	if (strcmp(opt.tool->answers[i], "break") == 0)
	    tools[ntools] = TOOL_BREAK;
	else if (strcmp(opt.tool->answers[i], "rmdupl") == 0)
	    tools[ntools] = TOOL_RMDUPL;
	else if (strcmp(opt.tool->answers[i], "rmdangle") == 0)
	    tools[ntools] = TOOL_RMDANGLE;
	else if (strcmp(opt.tool->answers[i], "chdangle") == 0)
	    tools[ntools] = TOOL_CHDANGLE;
	else if (strcmp(opt.tool->answers[i], "rmbridge") == 0)
	    tools[ntools] = TOOL_RMBRIDGE;
	else if (strcmp(opt.tool->answers[i], "chbridge") == 0)
	    tools[ntools] = TOOL_CHBRIDGE;
	else if (strcmp(opt.tool->answers[i], "snap") == 0)
	    tools[ntools] = TOOL_SNAP;
	else if (strcmp(opt.tool->answers[i], "rmdac") == 0)
	    tools[ntools] = TOOL_RMDAC;
	else if (strcmp(opt.tool->answers[i], "bpol") == 0)
	    tools[ntools] = TOOL_BPOL;
	else if (strcmp(opt.tool->answers[i], "prune") == 0)
	    tools[ntools] = TOOL_PRUNE;
	else if (strcmp(opt.tool->answers[i], "rmarea") == 0)
	    tools[ntools] = TOOL_RMAREA;
	else if (strcmp(opt.tool->answers[i], "rmsa") == 0)
	    tools[ntools] = TOOL_RMSA;
	else if (strcmp(opt.tool->answers[i], "rmline") == 0)
	    tools[ntools] = TOOL_RMLINE;
	else
	    G_fatal_error(_("Tool doesn't exist"));

	ntools++;
	i++;
    }

    G_debug(1, "ntools = %d", ntools);
    threshs = (double *)G_malloc(ntools * sizeof(double));

    /* TODO: threshold might be recalculated with optional geodesic support to meters */
    if (G_projection() == PROJECTION_LL)
        G_important_message(_("Note: In latitude-longitude coordinate system specify threshold in degree unit"));

    /* Read thresholds */
    for (i = 0; i < ntools; i++)
	threshs[i] = 0.0;
    i = 0;
    while (opt.thresh->answers && opt.thresh->answers[i]) {
	threshs[i] = atof(opt.thresh->answers[i]);
	G_debug(1, "thresh : %s -> %f ", opt.tool->answers[i], threshs[i]);

	if (threshs[i] != 0 && tools[i] != TOOL_SNAP &&
	    tools[i] != TOOL_RMDANGLE && tools[i] != TOOL_CHDANGLE && 
	    tools[i] != TOOL_PRUNE && tools[i] != TOOL_RMAREA) {
	    G_warning(_("Threshold for tool %d may not be > 0, set to 0"),
		      i + 1);
	    threshs[i] = 0.0;
	}
	i++;
    }

    /* Print tool table */
    G_message(SEP);
    G_message(_("Tool: Threshold"));

    for (i = 0; i < ntools; i++) {
	switch (tools[i]) {
	case (TOOL_BREAK):
	    G_message("%s: %.15g", _("Break"), threshs[i]);
	    break;
	case (TOOL_RMDUPL):
	    G_message("%s: %.15g", _("Remove duplicates"), threshs[i]);
	    break;
	case (TOOL_RMDANGLE):
	    G_message("%s: %.15g", _("Remove dangles"), threshs[i]);
	    break;
	case (TOOL_CHDANGLE):
	    G_message("%s: %.15g", _("Change type of boundary dangles"),
		      threshs[i]);
	    break;
	case (TOOL_RMBRIDGE):
	    G_message("%s: %.15g", _("Remove bridges"), threshs[i]);
	    break;
	case (TOOL_CHBRIDGE):
	    G_message("%s: %.15g", _("Change type of boundary bridges"),
		      threshs[i]);
	    break;
	case (TOOL_SNAP):
	    G_message("%s: %.15g", _("Snap vertices"), threshs[i]);
	    break;
	case (TOOL_RMDAC):
	    G_message("%s: %.15g", _("Remove duplicate area centroids"),
		      threshs[i]);
	    break;
	case (TOOL_BPOL):
	    G_message("%s: %.15g", _("Break polygons"), threshs[i]);
	    break;
	case (TOOL_PRUNE):
	    G_message("%s: %.15g", _("Prune"), threshs[i]);
	    break;
	case (TOOL_RMAREA):
	    G_message("%s: %.15g", _("Remove small areas"), threshs[i]);
	    break;
	case (TOOL_RMSA):
	    G_message("%s: %.15g", _("Remove small angles at nodes"),
		      threshs[i]);
	    break;
	case (TOOL_RMLINE):
	    G_message("%s: %.15g",
		      _("Remove all lines or boundaries of zero length"),
		      threshs[i]);
	    break;
	}
    }

    G_message(SEP);

    /* Input vector may be both on level 1 and 2. Level 2 is necessary for 
     * virtual centroids (shapefile/OGR) and level 1 is better if input is too big 
     * and build in previous module (like v.in.ogr or other call to v.clean) would take 
     * a long time */
    if (Vect_open_old2(&In, opt.in->answer, "", opt.field->answer) < 0)
	G_fatal_error(_("Unable to open vector map <%s>"), opt.in->answer);

    with_z = Vect_is_3d(&In);
    
    if (Vect_open_new(&Out, opt.out->answer, with_z) < 0)
	G_fatal_error(_("Unable to create vector map <%s>"), opt.out->answer);

    Vect_set_error_handler_io(&In, &Out);

    if (opt.err->answer) {
	Vect_set_open_level(2);
	if (Vect_open_new(&Err, opt.err->answer, with_z) < 0)
	    G_fatal_error(_("Unable to create vector map <%s>"),
			    opt.err->answer);
        G_add_error_handler(error_handler_err, &Err);
	pErr = &Err;
    }
    else {
	pErr = NULL;
    }

    /* Copy input to output */
    Vect_copy_head_data(&In, &Out);
    Vect_hist_copy(&In, &Out);
    Vect_hist_command(&Out);

    native = Vect_maptype(&Out) == GV_FORMAT_NATIVE;

    if (!native) {
	/* area cleaning tools might produce unexpected results for 
	 * non-native vectors */
	G_warning(_("Topological cleaning works best with native GRASS vector format"));
	/* Copy attributes (OGR format) */
	Vect_copy_map_dblinks(&In, &Out, TRUE);
    }
    
    /* This works for both level 1 and 2 */
    Vect_copy_map_lines_field(&In, Vect_get_field_number(&In, opt.field->answer), &Out);
    
    if (native) {
	/* Copy attribute tables (native format only) */
	if (Vect_copy_tables(&In, &Out, 0))
	    G_warning(_("Failed to copy attribute table to output vector map"));
    }
    Vect_set_release_support(&In);
    Vect_close(&In);

    /* Start with GV_BUILD_NONE and for each tool use unly the necessary level! */

    for (i = 0; i < ntools; i++) {
	if (tools[i] == TOOL_RMDAC || tools[i] == TOOL_PRUNE ||
	    tools[i] == TOOL_RMAREA) {
	    if (Vect_get_built(&Out) >= GV_BUILD_CENTROIDS) {
		Vect_build_partial(&Out, GV_BUILD_CENTROIDS);
		G_message(SEP);
	    }
	    else {
		G_important_message(_("Rebuilding parts of topology..."));
		Vect_build_partial(&Out, GV_BUILD_CENTROIDS);
		G_message(SEP);
	    }
	}
	else {
	    if (Vect_get_built(&Out) >= GV_BUILD_BASE) {
		Vect_build_partial(&Out, GV_BUILD_BASE);
		G_message(SEP);
	    }
	    else {
		G_important_message(_("Rebuilding parts of topology..."));
		Vect_build_partial(&Out, GV_BUILD_BASE);
		G_message(SEP);
	    }
	}

	switch (tools[i]) {
	case TOOL_BREAK:
	    if (flag.combine->answer && (otype & GV_LINES)) {
		G_message(_("Tool: Split lines"));
		split_lines(&Out, otype, pErr);
	    }
	    G_message(_("Tool: Break lines at intersections"));
	    Vect_break_lines(&Out, otype, pErr);
	    if (flag.combine->answer) {
		G_message(_("Tool: Remove duplicates"));
		Vect_remove_duplicates(&Out, otype, pErr);
		if (otype & GV_LINES) {
		    G_message(_("Tool: Merge lines"));
		    Vect_merge_lines(&Out, otype, NULL, pErr);
		}
	    }
	    break;
	case TOOL_RMDUPL:
	    G_message(_("Tool: Remove duplicates"));
	    Vect_remove_duplicates(&Out, otype, pErr);
	    break;
	case TOOL_RMDANGLE:
	    G_message(_("Tool: Remove dangles"));
	    Vect_remove_dangles(&Out, otype, threshs[i], pErr);
	    break;
	case TOOL_CHDANGLE:
	    G_message(_("Tool: Change type of boundary dangles"));
	    Vect_chtype_dangles(&Out, threshs[i], pErr);
	    break;
	case TOOL_RMBRIDGE:
	    G_message(_("Tool: Remove bridges"));
	    Vect_remove_bridges(&Out, pErr, NULL, NULL);
	    break;
	case TOOL_CHBRIDGE:
	    G_message(_("Tool: Change type of boundary bridges"));
	    Vect_chtype_bridges(&Out, pErr, NULL, NULL);
	    break;
	case TOOL_RMDAC:
	    G_message(_("Tool: Remove duplicate area centroids"));
	    count = rmdac(&Out, pErr);
	    break;
	case TOOL_SNAP:
	    G_message(_("Tool: Snap line to vertex in threshold"));
	    Vect_snap_lines(&Out, otype, threshs[i], pErr);
	    if (flag.combine->answer) {
		int nmod;

		if (otype & GV_LINES) {
		    G_message(_("Tool: Split lines"));
		    split_lines(&Out, otype, pErr);
		}
		do {
		    G_message(_("Tool: Break lines at intersections"));
		    Vect_break_lines(&Out, otype, pErr);
		    G_message(_("Tool: Remove duplicates"));
		    Vect_remove_duplicates(&Out, otype, pErr);
		    G_message(_("Tool: Remove small angles at nodes"));
		    nmod =
			Vect_clean_small_angles_at_nodes(&Out, otype, pErr);
		} while (nmod > 0);
		if (otype & GV_LINES) {
		    G_message(_("Tool: Merge lines"));
		    Vect_merge_lines(&Out, otype, NULL, pErr);
		}
	    }
	    break;
	case TOOL_BPOL:
	    G_message(_("Tool: Break polygons"));
	    Vect_break_polygons(&Out, otype, pErr);
	    if (flag.combine->answer) {
		G_message(_("Tool: Remove duplicates"));
		Vect_remove_duplicates(&Out, otype, pErr);
	    }
	    break;
	case TOOL_PRUNE:
	    G_message(_("Tool: Prune lines/boundaries"));
	    prune(&Out, otype, threshs[i], pErr);
	    break;
	case TOOL_RMAREA:
	    G_message(_("Tool: Remove small areas"));
	    count =
		Vect_remove_small_areas(&Out, threshs[i], pErr, &size);
	    if (flag.combine->answer && count > 0) {
		Vect_build_partial(&Out, GV_BUILD_BASE);
		G_message(SEP);
		G_message(_("Tool: Merge boundaries"));
		Vect_merge_lines(&Out, GV_BOUNDARY, NULL, pErr);
	    }
	    break;
	case TOOL_RMSA:
	    G_message(_("Tool: Remove small angles at nodes"));
	    if (!flag.combine->answer) {
		count =
		    Vect_clean_small_angles_at_nodes(&Out, otype, pErr);
	    }
	    else {
		int nmod;

		if (otype & GV_LINES) {
		    G_message(_("Tool: Split lines"));
		    split_lines(&Out, otype, pErr);
		}
		while ((nmod =
		          Vect_clean_small_angles_at_nodes(&Out, otype, pErr)) > 0) {
		    count += nmod;
		    G_message(_("Tool: Break lines at intersections"));
		    Vect_break_lines(&Out, otype, pErr);
		    G_message(_("Tool: Remove duplicates"));
		    Vect_remove_duplicates(&Out, otype, pErr);
		    G_message(_("Tool: Remove small angles at nodes"));
		}
		if (otype & GV_LINES) {
		    G_message(_("Tool: Merge lines"));
		    Vect_merge_lines(&Out, otype, NULL, pErr);
		}
	    }
	    break;
	case TOOL_RMLINE:
	    G_message(_("Tool: Remove all lines and boundaries of zero length"));
	    count = remove_zero_line(&Out, otype, pErr);
	    break;
	}

	G_message(SEP);
    }

    if (!flag.no_build->answer) {
	G_important_message(_("Rebuilding topology for output vector map..."));
	Vect_build_partial(&Out, GV_BUILD_NONE);
	Vect_build(&Out);
    }
    else {
	Vect_build_partial(&Out, GV_BUILD_NONE);	/* -> topo not saved */
    }
    Vect_close(&Out);

    if (pErr) {
	G_message(SEP);
	G_important_message(_("Building topology for error vector map..."));
	Vect_build(pErr);
	Vect_close(pErr);
    }

    exit(EXIT_SUCCESS);
}

void error_handler_err(void *p)
{
    char *name;
    struct Map_info *Err;
    
    Err = (struct Map_info *) p;

    if (Err && Err->open == VECT_OPEN_CODE) {
	name = G_store(Err->name);
	Vect_delete(name);
	G_free(name);
    }
}
