#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <math.h>
#include <grass/gis.h>
#include <grass/vector.h>
#include <grass/glocale.h>
#include "sw_defs.h"
#include "defs.h"

int sorted, plot, debug, mode3d;
struct Site *sites;
int nsites;
int siteidx;
int sqrt_nsites;
int nvertices;
struct Freelist sfl;
struct Site *bottomsite;
int nedges;
struct Freelist efl;
double xmin, xmax, ymin, ymax, deltax, deltay;
double xcenter, ycenter;
struct Freelist hfl;
struct Halfedge *ELleftend, *ELrightend;
int ELhashsize;
struct Halfedge **ELhash;
int PQhashsize;
struct Halfedge *PQhash;
int PQcount;
int PQmin;

struct Cell_head Window;
struct bound_box Box;
struct Map_info In, Out, Out2;
int Type;
int Field;
int in_area;
int skeleton;
double segf;

int nsites_alloc;

/* sort sites on y, then x, coord */
int scomp(const void *v1, const void *v2)
{
    struct Point *s1 = (struct Point *)v1;
    struct Point *s2 = (struct Point *)v2;

    if (s1->y < s2->y)
        return (-1);
    if (s1->y > s2->y)
        return (1);
    if (s1->x < s2->x)
        return (-1);
    if (s1->x > s2->x)
        return (1);
    return (0);
}

/* return a single in-storage site */
struct Site *nextone(void)
{
    struct Site *s;

    if (siteidx < nsites) {
        s = &sites[siteidx];
        siteidx++;
        return (s);
    }
    else
        return ((struct Site *)NULL);
}

/* removes duplicate sites that would break the voronoi algorithm */
void removeDuplicates(void)
{
    int i, j;

    i = j = 1;
    while (i < nsites)
        if (mode3d) {
            if (sites[i].coord.x == sites[i - 1].coord.x &&
                sites[i].coord.y == sites[i - 1].coord.y &&
                sites[i].coord.z == sites[i - 1].coord.z)
                i++;
            else {
                if (i != j)
                    sites[j] = sites[i];
                i++;
                j++;
                ;
            }
        }
        else {
            if (sites[i].coord.x == sites[i - 1].coord.x &&
                sites[i].coord.y == sites[i - 1].coord.y)
                i++;
            else {
                if (i != j)
                    sites[j] = sites[i];
                i++;
                j++;
                ;
            }
        }

    if (j != nsites) {
        nsites = j;
        sites = (struct Site *)G_realloc(sites, nsites * sizeof(struct Site));
    }
}

int addsite(double x, double y, double z, int id)
{
    if (nsites >= nsites_alloc) {
        nsites_alloc += 100;
        sites = (struct Site *)G_realloc(sites,
                                         (nsites_alloc) * sizeof(struct Site));
    }
    sites[nsites].coord.x = x;
    sites[nsites].coord.y = y;
    sites[nsites].coord.z = z;

    sites[nsites].sitenbr = id;
    sites[nsites].refcnt = 0;

    if (nsites > 0) {
        if (xmin > sites[nsites].coord.x)
            xmin = sites[nsites].coord.x;
        if (xmax < sites[nsites].coord.x)
            xmax = sites[nsites].coord.x;
        if (ymin > sites[nsites].coord.y)
            ymin = sites[nsites].coord.y;
        if (ymax < sites[nsites].coord.y)
            ymax = sites[nsites].coord.y;
    }
    else {
        xmin = xmax = sites[nsites].coord.x;
        ymin = ymax = sites[nsites].coord.y;
    }

    sites[nsites].coord.x -= xcenter;
    sites[nsites].coord.y -= ycenter;

    nsites++;

    return nsites;
}

/* read all sites, sort, and compute xmin, xmax, ymin, ymax */
int readsites(void)
{
    int nlines, ltype;
    struct line_pnts *Points;
    struct line_cats *Cats;
    double z = 0.;

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

    nlines = Vect_get_num_primitives(&In, GV_POINTS);

    nsites = 0;
    sites = (struct Site *)G_malloc(nlines * sizeof(struct Site));

    Vect_set_constraint_type(&In, GV_POINTS);
    Vect_set_constraint_field(&In, Field);

    while (TRUE) {
        ltype = Vect_read_next_line(&In, Points, Cats);
        if (ltype == -2)
            break;

        if (!(ltype & GV_POINTS))
            continue;
        /* G_percent(Vect_get_next_line_id(&In), nlines, 2); */

        if (!Vect_point_in_box(Points->x[0], Points->y[0], 0.0, &Box))
            continue;

        if (mode3d) {
            G_debug(3, "Points->z[0]: %f", Points->z[0]);
            z = Points->z[0];
        }

        addsite(Points->x[0], Points->y[0], z, nsites);
    }

    if (nsites < 2) {
        const char *name = Vect_get_full_name(&In);

        Vect_close(&In);
        G_fatal_error(
            n_("Found %d point/centroid in <%s>, but at least 2 are needed. "
               "Are the current region extents covering at least parts of the "
               "input map?",
               "Found %d points/centroids in <%s>, but at least 2 are needed. "
               "Are the current region extents covering at least parts of the "
               "input map?",
               nsites),
            nsites, name);
    }

    if (nsites < nlines)
        sites = (struct Site *)G_realloc(sites, (nsites) * sizeof(struct Site));

    qsort(sites, nsites, sizeof(struct Site), scomp);
    removeDuplicates();

    Vect_destroy_line_struct(Points);
    Vect_destroy_cats_struct(Cats);

    return 0;
}

/* valid boundary: one area with centroid */
int n_areas(int line, int *aid)
{
    int larea, rarea, ncentroids;

    ncentroids = 0;
    Vect_get_line_areas(&In, line, &larea, &rarea);

    if (larea < 0)
        larea = Vect_get_isle_area(&In, -larea);
    if (larea > 0) {
        if (Vect_get_area_centroid(&In, larea) > 0) {
            ncentroids++;
            *aid = larea;
        }
    }
    if (rarea < 0)
        rarea = Vect_get_isle_area(&In, -rarea);
    if (rarea > 0) {
        if (Vect_get_area_centroid(&In, rarea) > 0) {
            ncentroids++;
            *aid = rarea;
        }
    }

    return ncentroids;
}

/* read all boundaries, sort, and compute xmin, xmax, ymin, ymax */
int readbounds(void)
{
    int line, nlines, ltype, node, nnodes;
    struct line_pnts *Points;
    struct line_cats *Cats;
    int i;
    int area_id;
    double x, y, z, x1, y1, z1;
    double maxdist, sdist, l, dx, dy, dz;
    struct ilist *linelist, *arealist;

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

    nlines = Vect_get_num_lines(&In);

    nsites = 0;
    nsites_alloc = nlines * 2;
    sites = (struct Site *)G_malloc(nsites_alloc * sizeof(struct Site));

    Vect_set_constraint_type(&In, GV_BOUNDARY);
    Vect_set_constraint_field(&In, Field);

    l = 0;
    maxdist = 0;
    for (line = 1; line <= nlines; line++) {

        if (!Vect_line_alive(&In, line))
            continue;
        ltype = Vect_get_line_type(&In, line);

        if (!(ltype & GV_BOUNDARY))
            continue;

        area_id = 0;
        if (n_areas(line, &area_id) < 1)
            continue;

        Vect_read_line(&In, Points, Cats, line);
        Vect_line_prune(Points);

        l += Vect_line_length(Points);
        nsites += Points->n_points;
    }
    if (nsites)
        maxdist = segf * l / nsites;
    G_verbose_message("Maximum segment length: %g", maxdist);

    nsites = 0;
    z = 0.;
    z1 = 0;
    dz = 0;
    for (line = 1; line <= nlines; line++) {

        if (!Vect_line_alive(&In, line))
            continue;
        ltype = Vect_get_line_type(&In, line);

        if (!(ltype & GV_BOUNDARY))
            continue;

        area_id = 0;
        if (skeleton) {
            if (n_areas(line, &area_id) < 1)
                continue;
        }
        else {
            if (n_areas(line, &area_id) != 1)
                continue;
        }

        Vect_read_line(&In, Points, Cats, line);
        Vect_line_prune(Points);

        if (nsites + Points->n_points > nsites_alloc) {
            nsites_alloc = nsites + Points->n_points;
            sites = (struct Site *)G_realloc(sites, (nsites_alloc) *
                                                        sizeof(struct Site));
        }

        for (i = 0; i < Points->n_points; i++) {
            if (!Vect_point_in_box(Points->x[i], Points->y[i], 0.0, &Box))
                continue;

            x = Points->x[i];
            y = Points->y[i];
            if (mode3d) {
                G_debug(3, "Points->z[i]: %f", Points->z[i]);
                z = Points->z[i];
            }

            if (i > 0 && i < Points->n_points - 1)
                addsite(x, y, z, area_id);

            /* densify */
            if (maxdist > 0 && i < Points->n_points - 1) {
                dx = Points->x[i + 1] - Points->x[i];
                dy = Points->y[i + 1] - Points->y[i];
                if (mode3d)
                    dz = Points->z[i + 1] - Points->z[i];
                l = sqrt(dx * dx + dy * dy);

                if (l > maxdist) {
                    int n = ceil(l / maxdist) + 0.5;
                    double step = l / n;

                    while (--n) {
                        sdist = (step * n) / l;
                        x1 = x + sdist * dx;
                        y1 = y + sdist * dy;
                        if (mode3d)
                            z1 = z + sdist * dz;

                        addsite(x1, y1, z1, area_id);
                    }
                }
            }
        }
    }

    /* process nodes */
    nnodes = Vect_get_num_nodes(&In);
    linelist = Vect_new_list();
    arealist = Vect_new_list();

    for (node = 1; node <= nnodes; node++) {
        Vect_get_node_coor(&In, node, &x, &y, &z);
        if (!mode3d)
            z = 0.;

        if (!Vect_point_in_box(x, y, 0.0, &Box))
            continue;

        /* count number of connected boundaries
         * must be >= 2 */
        /* count number of valid boundaries */
        nlines = Vect_get_node_n_lines(&In, node);
        Vect_reset_list(linelist);
        Vect_reset_list(arealist);

        for (i = 0; i < nlines; i++) {
            line = Vect_get_node_line(&In, node, i);

            ltype = Vect_get_line_type(&In, abs(line));

            if (!(ltype & GV_BOUNDARY))
                continue;

            if (n_areas(abs(line), &area_id) == 1) {
                Vect_list_append(linelist, line);
                Vect_list_append(arealist, area_id);
            }
        }

        if (arealist->n_values == 1) {

            area_id = arealist->value[0];
            addsite(x, y, z, area_id);
        }
        else if (arealist->n_values > 1) {
            /* displacement */
            double displace;

            if (fabs(x) > fabs(y))
                displace = d_ulp(fabs(x));
            else
                displace = d_ulp(fabs(y));

            displace *= 2;

            for (i = 0; i < linelist->n_values; i++) {

                line = linelist->value[i];

                if (n_areas(abs(line), &area_id) != 1)
                    G_fatal_error(
                        _("All boundaries in the list should be valid"));

                Vect_read_line(&In, Points, Cats, abs(line));
                Vect_line_prune(Points);

                if (Points->n_points < 2)
                    G_fatal_error(_("Boundary is degenerate"));

                if (line < 0) {
                    dx = Points->x[Points->n_points - 2] -
                         Points->x[Points->n_points - 1];
                    dy = Points->y[Points->n_points - 2] -
                         Points->y[Points->n_points - 1];
                }
                else {
                    dx = Points->x[1] - Points->x[0];
                    dy = Points->y[1] - Points->y[0];
                }
                l = sqrt(dx * dx + dy * dy);
                if (displace > l)
                    displace = l;
            }

            for (i = 0; i < linelist->n_values; i++) {

                line = linelist->value[i];

                if (n_areas(abs(line), &area_id) != 1)
                    G_fatal_error(
                        _("All boundaries in the list should be valid"));

                Vect_read_line(&In, Points, Cats, abs(line));
                Vect_line_prune(Points);

                if (Points->n_points < 2)
                    G_fatal_error(_("Boundary is degenerate"));

                if (line < 0) {
                    dx = Points->x[Points->n_points - 2] -
                         Points->x[Points->n_points - 1];
                    dy = Points->y[Points->n_points - 2] -
                         Points->y[Points->n_points - 1];
                }
                else {
                    dx = Points->x[1] - Points->x[0];
                    dy = Points->y[1] - Points->y[0];
                }
                l = sqrt(dx * dx + dy * dy);
                if (l > displace * 2) {
                    sdist = displace / l;
                    x1 = x + sdist * dx;
                    y1 = y + sdist * dy;
                    z1 = 0;

                    addsite(x1, y1, z1, area_id);
                }
            }
        }
    }

    if (nsites < 2) {
        const char *name = Vect_get_full_name(&In);

        Vect_close(&In);
        G_fatal_error(n_("Found %d vertex in <%s>, but at least 2 are needed",
                         "Found %d vertices in <%s>, but at least 2 are needed",
                         nsites),
                      nsites, name);
    }

    qsort(sites, nsites, sizeof(struct Site), scomp);
    removeDuplicates();

    Vect_destroy_line_struct(Points);
    Vect_destroy_cats_struct(Cats);
    Vect_destroy_list(linelist);
    Vect_destroy_list(arealist);

    return 0;
}

int copybounds(void)
{
    int line, nlines, ltype;
    int area_id;
    int ncopied;
    struct line_pnts *Points;
    struct line_cats *Cats;

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

    nlines = Vect_get_num_lines(&In);
    ncopied = 0;

    for (line = 1; line <= nlines; line++) {

        if (!Vect_line_alive(&In, line))
            continue;
        ltype = Vect_get_line_type(&In, line);

        if (!(ltype & GV_BOUNDARY))
            continue;

        if (n_areas(line, &area_id) != 2)
            continue;

        Vect_read_line(&In, Points, Cats, line);
        Vect_line_prune(Points);

        Vect_reset_cats(Cats);
        Vect_cat_set(Cats, 1, 2);
        Vect_write_line(&Out, GV_BOUNDARY, Points, Cats);
        ncopied++;
    }

    Vect_destroy_line_struct(Points);
    Vect_destroy_cats_struct(Cats);

    return ncopied;
}
