#include <stdlib.h>
#include <grass/gis.h>
#include <grass/vector.h>
#include <grass/glocale.h>
#include "proto.h"

/**
 * \brief Consolidate network arcs (edge) based on given point vector map
 * (nodes)
 *
 * If there is no connection between network edge and point, new edge
 * is added, the line broken, and new point added to nfield layer
 *
 * \param In,Points input vector maps
 * \param Out output vector map
 * \param nfield nodes layer
 * \param thresh threshold value to find neareast line
 *
 * \return number of new arcs
 */
int connect_arcs(struct Map_info *In, struct Map_info *Pnts,
                 struct Map_info *Out, int afield, int nfield, double thresh,
                 int snap)
{
    int narcs;
    int type, line, seg, i, ltype, broken;
    double px, py, pz, spdist, dist;

    struct line_pnts *Points, *Pline, *Pout;
    struct line_cats *Cats, *Cline, *Cnew;
    int maxcat, findex, ncats;

    struct ilist *exclude_list;
    int pline;

    exclude_list = Vect_new_list();

    narcs = 0;

    Points = Vect_new_line_struct();
    Pline = Vect_new_line_struct();
    Pout = Vect_new_line_struct();
    Cats = Vect_new_cats_struct();
    Cline = Vect_new_cats_struct();
    Cnew = Vect_new_cats_struct();

    /* rewrite all primitives to output file */
    Vect_copy_map_lines(In, Out);
    Vect_build_partial(Out, GV_BUILD_BASE);

    findex = Vect_cidx_get_field_index(In, afield);
    ncats = Vect_cidx_get_num_cats_by_index(In, findex);
    Vect_cidx_get_cat_by_index(In, findex, ncats - 1, &maxcat, &type, &line);

    /* go thorough all points in point map and write a new arcs if missing */
    pz = 0.0;
    while ((type = Vect_read_next_line(Pnts, Points, Cats)) >= 0) {
        if (type != GV_POINT)
            continue;

        /* find the nearest line in given threshold */
        line = Vect_find_line_list(Out, Points->x[0], Points->y[0],
                                   Points->z[0], GV_LINES, thresh, WITHOUT_Z,
                                   exclude_list, NULL);

        if (line < 1 || !Vect_line_alive(Out, line))
            continue;

        ltype = Vect_read_line(Out, Pline, Cline, line);

        /* find point on the line */
        seg =
            Vect_line_distance(Pline, Points->x[0], Points->y[0], Points->z[0],
                               WITHOUT_Z, &px, &py, &pz, &dist, &spdist, NULL);

        if (seg == 0)
            G_fatal_error(_("Failed to find intersection segment"));
        /* break the line */
        broken = 0;
        Vect_reset_line(Pout);
        for (i = 0; i < seg; i++) {
            Vect_append_point(Pout, Pline->x[i], Pline->y[i], Pline->z[i]);
        }
        Vect_append_point(Pout, px, py, pz);
        Vect_line_prune(Pout);
        if (Pout->n_points > 1) {
            Vect_rewrite_line(Out, line, ltype, Pout, Cline);
            broken++;
        }

        Vect_reset_line(Pout);
        Vect_append_point(Pout, px, py, pz);
        for (i = seg; i < Pline->n_points; i++) {
            Vect_append_point(Pout, Pline->x[i], Pline->y[i], Pline->z[i]);
        }
        Vect_line_prune(Pout);
        if (Pout->n_points > 1) {
            if (broken)
                Vect_write_line(Out, ltype, Pout, Cline);
            else
                Vect_rewrite_line(Out, line, ltype, Pout, Cline);
            broken++;
        }
        if (broken == 2)
            narcs++;

        if (dist > 0.0) {
            if (snap) {
                /* snap point */
                Points->x[0] = px;
                Points->y[0] = py;
                Points->z[0] = pz;
            }
            else {
                /* write new arc */
                Vect_reset_line(Pout);
                Vect_append_point(Pout, px, py, pz);
                Vect_append_point(Pout, Points->x[0], Points->y[0],
                                  Points->z[0]);
                maxcat++;
                Vect_reset_cats(Cnew);
                Vect_cat_set(Cnew, afield, maxcat);
                pline = Vect_write_line(Out, ltype, Pout, Cnew);

                Vect_list_append(exclude_list, pline);

                narcs++;
            }
        }

        /* add points to 'nfield' layer */
        for (i = 0; i < Cats->n_cats; i++) {
            Cats->field[i] = nfield; /* all points to 'nfield' layer */
        }

        Vect_write_line(Out, type, Points, Cats);
    }

    Vect_destroy_line_struct(Points);
    Vect_destroy_line_struct(Pline);
    Vect_destroy_line_struct(Pout);
    Vect_destroy_cats_struct(Cats);
    Vect_destroy_cats_struct(Cline);
    Vect_destroy_cats_struct(Cnew);
    Vect_destroy_list(exclude_list);

    return narcs;
}
