/* **************************************************************
 * 
 * MODULE:       vector library
 * 
 * AUTHOR(S):    Radim Blazek
 *               
 * PURPOSE:      Clean boundaries at nodes
 *               
 * COPYRIGHT:    (C) 2001 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 <grass/gis.h>
#include <grass/Vect.h>

/*!
 \fn void Vect_clean_small_angles_at_nodes ( struct Map_info *Map, int type, struct Map_info *Err, FILE *msgout)
 \brief It may happen that even if the angle between 2 boundaries at node is very small,
        the calculated angle is 0 because of representation error.
	The map must be built at least on level GV_BUILD_BASE

 \param Map input map
 \param Err vector map where error line segments are written
 \param msgout file pointer where messages will be written or NULL
 \return number of line modifications
*/
int 
Vect_clean_small_angles_at_nodes ( struct Map_info *Map, int otype, struct Map_info *Err, FILE *msgout)
{
    int node;
    int nmodif = 0;
    struct line_pnts *Points;
    struct line_cats *SCats, *LCats, *OCats;

    Points = Vect_new_line_struct ();
    SCats = Vect_new_cats_struct ();
    LCats = Vect_new_cats_struct ();
    OCats = Vect_new_cats_struct ();

    if ( msgout ) fprintf (msgout, "Modifications: %5d", nmodif ); 
    
    for ( node = 1; node <= Vect_get_num_nodes(Map); node++ ){ 
	int i, nlines;
	
	G_debug (3, "node = %d", node );
	if ( !Vect_node_alive ( Map, node ) ) continue;

	while ( 1 ) {
	    float angle1 = -100;
	    int line1 = -999; /* value not important, just for debug */
	    int clean = 1;
	    
	    
	    nlines = Vect_get_node_n_lines ( Map, node );
	    G_debug (3, "nlines = %d", nlines );

	    for ( i = 0; i < nlines; i++ ) {
		P_LINE *Line;
		int line2;
		float angle2;
		    
		line2 = Vect_get_node_line ( Map, node, i );
		Line = Map->plus.Line[abs(line2)];
		if ( !Line ) continue;
		G_debug (4, "  type = %d", Line->type );
		if ( !(Line->type & (otype & GV_LINES)) ) continue;
		
		angle2 = Vect_get_node_line_angle ( Map, node, i );
		if ( angle2 == -9.0 ) continue; /* Degenerated line */
	    
		G_debug (4, "  line1 = %d angle1 = %e line2 = %d angle2 = %e", line1, angle1, line2, angle2 );

		if ( angle2 == angle1 ) { 
		    int j;
		    double length1, length2;
		    int short_line; /* line with shorter end segment */
		    int long_line;  /* line with longer end segment */
		    int new_short_line=0; /* line number of short line after rewrite */
		    int short_type, long_type, type;
		    double x, y, z, nx, ny, nz;
		    
		    G_debug (4, "  identical angles -> clean" );

		    /* Length of end segments for both lines */
		    Vect_read_line ( Map, Points, NULL, abs(line1) );
		    if ( line1 > 0 ) {
			length1 = Vect_points_distance ( Points->x[0], Points->y[0], 0.0,
				                         Points->x[1], Points->y[1], 0.0, 0 );
		    } else {
			int np;
			np = Points->n_points;
			length1 = Vect_points_distance ( Points->x[np-1], Points->y[np-1], 0.0,
				                         Points->x[np-2], Points->y[np-2], 0.0, 0 );
		    }

		    Vect_read_line ( Map, Points, NULL, abs(line2) );
		    if ( line2 > 0 ) {
			length2 = Vect_points_distance ( Points->x[0], Points->y[0], 0.0,
				                         Points->x[1], Points->y[1], 0.0, 0 );
		    } else {
			int np;
			np = Points->n_points;
			length2 = Vect_points_distance ( Points->x[np-1], Points->y[np-1], 0.0,
				                         Points->x[np-2], Points->y[np-2], 0.0, 0 );
		    }
		    
		    G_debug (4, "  length1 = %f length2 = %f", length1, length2 );

		    if ( length1 < length2 ) {
			short_line = line1;
			long_line = line2;
		    } else { 
			short_line = line2;
			long_line = line1;
		    }

		    /* Remove end segment from short_line */
		    short_type = Vect_read_line ( Map, Points, SCats, abs(short_line) );
			    
		    if ( short_line > 0 ) {
			x = Points->x[1];
			y = Points->y[1];
			z = Points->z[1];
			Vect_line_delete_point ( Points, 0 ); /* first */
		    } else {
			x = Points->x[Points->n_points-2];
			y = Points->y[Points->n_points-2];
			z = Points->z[Points->n_points-2];
			Vect_line_delete_point ( Points, Points->n_points-1 ); /* last */
		    }

		    if ( Points->n_points > 1 ) {
			new_short_line = Vect_rewrite_line ( Map, abs(short_line), short_type, Points, SCats );
		    } else {
			Vect_delete_line ( Map, abs(short_line) );
		    }

		    /* It may happen that it is one line, in that case we have to take the new
		     * short line as long line, orientation is not changed */
		    if ( abs(line1) == abs(line2) ) {
			if ( long_line > 0 )
			    long_line = new_short_line;
			else 
			    long_line = -new_short_line;
		    }
		    

		    /* Add new line (must be before rewrite of long_line otherwise node could be deleted) */
		    long_type = Vect_read_line ( Map, NULL, LCats, abs(long_line) );
		    
		    Vect_reset_cats ( OCats );
		    for ( j = 0; j < SCats->n_cats; j++ ) {
			Vect_cat_set ( OCats, SCats->field[j], SCats->cat[j] );
		    }
		    for ( j = 0; j < LCats->n_cats; j++ ) {
			Vect_cat_set ( OCats, LCats->field[j], LCats->cat[j] );
		    }

		    if ( long_type == GV_BOUNDARY || short_type == GV_BOUNDARY ) {
			type = GV_BOUNDARY;
		    } else { 
			type = GV_LINE;
		    }

		    Vect_get_node_coor ( Map, node, &nx, &ny, &nz );
		    Vect_reset_line ( Points );
		    Vect_append_point ( Points, nx, ny, nz );
		    Vect_append_point ( Points, x, y, z );
		    Vect_write_line ( Map, type, Points, OCats );

		    if ( Err ) {
		        Vect_write_line ( Err, type, Points, OCats );
		    }

		    /* Snap long_line to the new short_line end */ 
		    long_type = Vect_read_line ( Map, Points, LCats, abs(long_line) );
		    if ( long_line > 0 ) {
			Points->x[0] = x;
			Points->y[0] = y;
			Points->z[0] = z;
		    } else {
			Points->x[Points->n_points-1] = x;
			Points->y[Points->n_points-1] = y;
			Points->z[Points->n_points-1] = z;
		    }
		    Vect_line_prune ( Points );
		    if ( Points->n_points > 1 ) {
		        Vect_rewrite_line ( Map, abs(long_line), long_type, Points, LCats );
		    } else {
			Vect_delete_line ( Map, abs(long_line) );
		    }

		    nmodif += 3;
		    clean = 0;
		    
		    break;
		}
		
		line1 = line2;
		angle1 = angle2;
	    }
    
	    if ( msgout ) {
		fprintf (msgout, "\rModifications: %5d", nmodif ); 
		fflush ( msgout );
	    }

	    if ( clean )
	        break;

	}

    }
    if ( msgout ) fprintf (stderr, "\n" ); 

    return (nmodif);
}

