/*!
   \file lib/vector/Vlib/remove_areas.c

   \brief Vector library - clean geometry (remove small areas)

   Higher level functions for reading/writing/manipulating vectors.

   (C) 2001-2009 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.

   \author Radim Blazek, Markus Metz
 */

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

int Vect_remove_small_areas_nat(struct Map_info *, double, struct Map_info *,
                                double *);

int Vect_remove_small_areas_ext(struct Map_info *, double, struct Map_info *,
                                double *);

/*!
   \brief Remove small areas from the map map.

   Centroid of the area and the longest boundary with adjacent area is
   removed.  Map topology must be built GV_BUILD_CENTROIDS.

   \param[in,out] Map vector map
   \param thresh maximum area size for removed areas
   \param[out] Err vector map where removed lines and centroids are written
   \param removed_area  pointer to where total size of removed area is stored or
   NULL

   \return number of removed areas
 */

int Vect_remove_small_areas(struct Map_info *Map, double thresh,
                            struct Map_info *Err, double *removed_area)
{

    if (Map->format == GV_FORMAT_NATIVE)
        return Vect_remove_small_areas_nat(Map, thresh, Err, removed_area);
    else
        return Vect_remove_small_areas_ext(Map, thresh, Err, removed_area);
}

int Vect_remove_small_areas_ext(struct Map_info *Map, double thresh,
                                struct Map_info *Err, double *removed_area)
{
    int area, nareas;
    int nremoved = 0;
    struct ilist *List;
    struct ilist *AList;
    struct line_pnts *Points;
    struct line_cats *Cats;
    double size_removed = 0.0;

    List = Vect_new_list();
    AList = Vect_new_list();
    Points = Vect_new_line_struct();
    Cats = Vect_new_cats_struct();

    nareas = Vect_get_num_areas(Map);
    for (area = 1; area <= nareas; area++) {
        int i, j, centroid, dissolve_neighbour;
        double length, size;

        G_percent(area, nareas, 1);
        G_debug(3, "area = %d", area);
        if (!Vect_area_alive(Map, area))
            continue;

        size = Vect_get_area_area(Map, area);
        if (size > thresh)
            continue;
        size_removed += size;

        /* The area is smaller than the limit -> remove */

        /* Remove centroid */
        centroid = Vect_get_area_centroid(Map, area);
        if (centroid > 0) {
            if (Err) {
                Vect_read_line(Map, Points, Cats, centroid);
                Vect_write_line(Err, GV_CENTROID, Points, Cats);
            }
            Vect_delete_line(Map, centroid);
        }

        /* Find the adjacent area with which the longest boundary is shared */

        Vect_get_area_boundaries(Map, area, List);

        /* Create a list of neighbour areas */
        Vect_reset_list(AList);
        for (i = 0; i < List->n_values; i++) {
            int line, left, right, neighbour;

            line = List->value[i];

            if (!Vect_line_alive(Map, abs(line))) /* Should not happen */
                G_fatal_error(_("Area is composed of dead boundary"));

            Vect_get_line_areas(Map, abs(line), &left, &right);
            if (line > 0)
                neighbour = left;
            else
                neighbour = right;

            G_debug(4, "  line = %d left = %d right = %d neighbour = %d", line,
                    left, right, neighbour);

            Vect_list_append(AList, neighbour); /* this checks for duplicity */
        }
        G_debug(3, "num neighbours = %d", AList->n_values);

        /* Go through the list of neighbours and find that with the longest
         * boundary */
        dissolve_neighbour = 0;
        length = -1.0;
        for (i = 0; i < AList->n_values; i++) {
            int neighbour1;
            double l = 0.0;

            neighbour1 = AList->value[i];
            G_debug(4, "   neighbour1 = %d", neighbour1);

            for (j = 0; j < List->n_values; j++) {
                int line, left, right, neighbour2;

                line = List->value[j];
                Vect_get_line_areas(Map, abs(line), &left, &right);
                if (line > 0)
                    neighbour2 = left;
                else
                    neighbour2 = right;

                if (neighbour2 == neighbour1) {
                    Vect_read_line(Map, Points, NULL, abs(line));
                    l += Vect_line_length(Points);
                }
            }
            if (l > length) {
                length = l;
                dissolve_neighbour = neighbour1;
            }
        }

        G_debug(3, "dissolve_neighbour = %d", dissolve_neighbour);

        /* Make list of boundaries to be removed */
        Vect_reset_list(AList);
        for (i = 0; i < List->n_values; i++) {
            int line, left, right, neighbour;

            line = List->value[i];
            Vect_get_line_areas(Map, abs(line), &left, &right);
            if (line > 0)
                neighbour = left;
            else
                neighbour = right;

            G_debug(3, "   neighbour = %d", neighbour);

            if (neighbour == dissolve_neighbour) {
                Vect_list_append(AList, abs(line));
            }
        }

        /* Remove boundaries */
        for (i = 0; i < AList->n_values; i++) {
            int line;

            line = AList->value[i];

            if (Err) {
                Vect_read_line(Map, Points, Cats, line);
                Vect_write_line(Err, GV_BOUNDARY, Points, Cats);
            }
            Vect_delete_line(Map, line);
        }

        nremoved++;
        nareas = Vect_get_num_areas(Map);
    }

    if (removed_area)
        *removed_area = size_removed;

    G_message(_("%d areas of total size %g removed"), nremoved, size_removed);

    return (nremoved);
}

/* much faster version */
int Vect_remove_small_areas_nat(struct Map_info *Map, double thresh,
                                struct Map_info *Err, double *removed_area)
{
    int area, nareas;
    int nremoved = 0;
    struct ilist *List;
    struct ilist *AList;
    struct ilist *BList;
    struct ilist *NList;
    struct ilist *IList;
    struct line_pnts *Points;
    struct line_cats *Cats;
    double size_removed = 0.0;
    int dissolve_neighbour;
    int line, left, right, neighbour;
    int nisles, nnisles;

    List = Vect_new_list();
    AList = Vect_new_list();
    BList = Vect_new_list();
    NList = Vect_new_list();
    IList = Vect_new_list();
    Points = Vect_new_line_struct();
    Cats = Vect_new_cats_struct();

    nareas = Vect_get_num_areas(Map);
    for (area = 1; area <= nareas; area++) {
        int i, j, centroid, ncentroid;
        double length, l, size;
        int outer_area = -1;
        int narea, same_atype = 0;

        G_percent(area, nareas, 1);
        G_debug(3, "area = %d", area);
        if (!Vect_area_alive(Map, area))
            continue;

        size = Vect_get_area_area(Map, area);
        if (size > thresh)
            continue;
        size_removed += size;

        /* The area is smaller than the limit -> remove */

        /* Remove centroid */
        centroid = Vect_get_area_centroid(Map, area);
        if (centroid > 0) {
            if (Err) {
                Vect_read_line(Map, Points, Cats, centroid);
                Vect_write_line(Err, GV_CENTROID, Points, Cats);
            }
            Vect_delete_line(Map, centroid);
        }

        /* Find the adjacent area with which the longest boundary is shared */

        Vect_get_area_boundaries(Map, area, List);

        /* Create a list of neighbour areas */
        Vect_reset_list(AList);
        for (i = 0; i < List->n_values; i++) {

            line = List->value[i];

            if (!Vect_line_alive(Map, abs(line))) /* Should not happen */
                G_fatal_error(_("Area is composed of dead boundary"));

            Vect_get_line_areas(Map, abs(line), &left, &right);
            if (line > 0)
                neighbour = left;
            else
                neighbour = right;

            G_debug(4, "  line = %d left = %d right = %d neighbour = %d", line,
                    left, right, neighbour);

            ncentroid = 0;
            if (neighbour > 0) {
                ncentroid = Vect_get_area_centroid(Map, neighbour);
            }
            if (neighbour < 0) {
                narea = Vect_get_isle_area(Map, -neighbour);
                if (narea > 0)
                    ncentroid = Vect_get_area_centroid(Map, narea);
            }
            if ((centroid != 0) + (ncentroid != 0) != 1)
                same_atype = 1;

            Vect_list_append(AList, neighbour); /* this checks for duplicity */
        }
        G_debug(3, "num neighbours = %d", AList->n_values);

        if (AList->n_values == 1)
            same_atype = 0;

        /* Go through the list of neighbours and find the one with the longest
         * boundary */
        dissolve_neighbour = 0;
        length = -1.0;
        for (i = 0; i < AList->n_values; i++) {
            int neighbour1;

            l = 0.0;
            neighbour1 = AList->value[i];
            G_debug(4, "   neighbour1 = %d", neighbour1);

            if (same_atype) {
                ncentroid = 0;
                if (neighbour1 > 0) {
                    ncentroid = Vect_get_area_centroid(Map, neighbour1);
                }
                if (neighbour1 < 0) {
                    narea = Vect_get_isle_area(Map, -neighbour1);
                    if (narea > 0)
                        ncentroid = Vect_get_area_centroid(Map, narea);
                }
                if ((centroid != 0) + (ncentroid != 0) == 1)
                    continue;
            }

            for (j = 0; j < List->n_values; j++) {
                int neighbour2;

                line = List->value[j];
                Vect_get_line_areas(Map, abs(line), &left, &right);
                if (line > 0)
                    neighbour2 = left;
                else
                    neighbour2 = right;

                if (neighbour2 == neighbour1) {
                    Vect_read_line(Map, Points, NULL, abs(line));
                    l += Vect_line_length(Points);
                }
            }
            if (l > length) {
                length = l;
                dissolve_neighbour = neighbour1;
            }
        }

        G_debug(3, "dissolve_neighbour = %d", dissolve_neighbour);

        if (dissolve_neighbour == 0) {
            G_fatal_error("could not find neighbour to dissolve");
        }

        /* Make list of boundaries to be removed */
        Vect_reset_list(AList);
        Vect_reset_list(BList);
        for (i = 0; i < List->n_values; i++) {

            line = List->value[i];
            Vect_get_line_areas(Map, abs(line), &left, &right);
            if (line > 0)
                neighbour = left;
            else
                neighbour = right;

            G_debug(3, "   neighbour = %d", neighbour);

            if (neighbour == dissolve_neighbour) {
                Vect_list_append(AList, abs(line));
            }
            else
                Vect_list_append(BList, line);
        }
        G_debug(3, "remove %d of %d boundaries", AList->n_values,
                List->n_values);

        /* Get isles inside area */
        Vect_reset_list(IList);
        if ((nisles = Vect_get_area_num_isles(Map, area)) > 0) {
            for (i = 0; i < nisles; i++) {
                Vect_list_append(IList, Vect_get_area_isle(Map, area, i));
            }
        }

        /* Remove boundaries */
        for (i = 0; i < AList->n_values; i++) {
            int ret;

            line = AList->value[i];

            if (Err) {
                Vect_read_line(Map, Points, Cats, line);
                Vect_write_line(Err, GV_BOUNDARY, Points, Cats);
            }
            /* Vect_delete_line(Map, line); */

            /* delete the line from coor */
            ret = V1_delete_line_nat(Map, Map->plus.Line[line]->offset);

            if (ret == -1) {
                G_fatal_error(_("Could not delete line from coor"));
            }
        }

        /* update topo */
        if (dissolve_neighbour > 0) {

            G_debug(3, "dissolve with neighbour area");

            /* get neighbour centroid */
            centroid = Vect_get_area_centroid(Map, dissolve_neighbour);
            /* get neighbour isles */
            if ((nnisles = Vect_get_area_num_isles(Map, dissolve_neighbour)) >
                0) {
                for (i = 0; i < nnisles; i++) {
                    Vect_list_append(
                        IList, Vect_get_area_isle(Map, dissolve_neighbour, i));
                }
            }

            /* get neighbour boundaries */
            Vect_get_area_boundaries(Map, dissolve_neighbour, NList);

            /* delete area from topo */
            dig_del_area(&(Map->plus), area);
            /* delete neighbour area from topo */
            dig_del_area(&(Map->plus), dissolve_neighbour);
            /* delete boundaries from topo */
            for (i = 0; i < AList->n_values; i++) {
                struct P_topo_b *topo;
                struct P_node *Node;

                line = AList->value[i];
                topo = (struct P_topo_b *)Map->plus.Line[line]->topo;
                Node = Map->plus.Node[topo->N1];
                dig_del_line(&(Map->plus), line, Node->x, Node->y, Node->z);
            }
            /* build new area from leftover boundaries of deleted area */
            for (i = 0; i < BList->n_values; i++) {
                struct P_topo_b *topo;
                int new_isle;

                line = BList->value[i];
                topo = Map->plus.Line[abs(line)]->topo;

                if (topo->left == 0 || topo->right == 0) {
                    new_isle = Vect_build_line_area(
                        Map, abs(line), (line > 0 ? GV_RIGHT : GV_LEFT));
                    if (new_isle > 0) {
                        if (outer_area > 0)
                            G_fatal_error("dissolve_neighbour > 0, new area "
                                          "has already been created");
                        outer_area = new_isle;
                        /* reattach centroid */
                        Map->plus.Area[outer_area]->centroid = centroid;
                        if (centroid > 0) {
                            struct P_topo_c *ctopo =
                                Map->plus.Line[centroid]->topo;

                            ctopo->area = outer_area;
                        }
                    }
                    else if (new_isle < 0) {
                        /* leftover boundary creates a new isle */
                        Vect_list_append(IList, -new_isle);
                    }
                    else {
                        /* neither area nor isle, should not happen */
                        G_fatal_error(_("dissolve_neighbour > 0, failed to "
                                        "build new area"));
                    }
                }
                /* check */
                if (topo->left == 0 || topo->right == 0)
                    G_fatal_error(
                        _("Dissolve with neighbour area: corrupt topology"));
            }
            /* build new area from neighbour's boundaries */
            for (i = 0; i < NList->n_values; i++) {
                struct P_topo_b *topo;

                line = NList->value[i];
                if (!Vect_line_alive(Map, abs(line)))
                    continue;

                topo = Map->plus.Line[abs(line)]->topo;

                if (topo->left == 0 || topo->right == 0) {
                    int new_isle;

                    new_isle = Vect_build_line_area(
                        Map, abs(line), (line > 0 ? GV_RIGHT : GV_LEFT));
                    if (new_isle > 0) {
                        if (outer_area > 0)
                            G_fatal_error("dissolve_neighbour > 0, new area "
                                          "has already been created");
                        outer_area = new_isle;
                        /* reattach centroid */
                        Map->plus.Area[outer_area]->centroid = centroid;
                        if (centroid > 0) {
                            struct P_topo_c *ctopo =
                                Map->plus.Line[centroid]->topo;

                            ctopo->area = outer_area;
                        }
                    }
                    else if (new_isle < 0) {
                        /* Neigbour's boundary creates a new isle */
                        Vect_list_append(IList, -new_isle);
                    }
                    else {
                        /* neither area nor isle, should not happen */
                        G_fatal_error(_("Failed to build new area"));
                    }
                }
                if (topo->left == 0 || topo->right == 0)
                    G_fatal_error(
                        _("Dissolve with neighbour area: corrupt topology"));
            }
        }
        /* dissolve with outer isle */
        else if (dissolve_neighbour < 0) {

            G_debug(3, "dissolve with outer isle");

            outer_area = Vect_get_isle_area(Map, -dissolve_neighbour);

            /* get isle boundaries */
            Vect_get_isle_boundaries(Map, -dissolve_neighbour, NList);

            /* delete area from topo */
            dig_del_area(&(Map->plus), area);
            /* delete isle from topo */
            dig_del_isle(&(Map->plus), -dissolve_neighbour);
            /* delete boundaries from topo */
            for (i = 0; i < AList->n_values; i++) {
                struct P_topo_b *topo;
                struct P_node *Node;

                line = AList->value[i];
                topo = (struct P_topo_b *)Map->plus.Line[line]->topo;
                Node = Map->plus.Node[topo->N1];
                dig_del_line(&(Map->plus), line, Node->x, Node->y, Node->z);
            }
            /* build new isle(s) from leftover boundaries */
            for (i = 0; i < BList->n_values; i++) {
                struct P_topo_b *topo;

                line = BList->value[i];
                topo = Map->plus.Line[abs(line)]->topo;

                if (topo->left == 0 || topo->right == 0) {
                    int new_isle;

                    new_isle = Vect_build_line_area(
                        Map, abs(line), (line > 0 ? GV_RIGHT : GV_LEFT));
                    if (new_isle < 0) {
                        Vect_list_append(IList, -new_isle);
                    }
                    else {
                        /* area or nothing should not happen */
                        G_fatal_error(_("Failed to build new isle"));
                    }
                }
                /* check */
                if (topo->left == 0 || topo->right == 0)
                    G_fatal_error(
                        _("Dissolve with outer isle: corrupt topology"));
            }

            /* build new isle(s) from old isle's boundaries */
            for (i = 0; i < NList->n_values; i++) {
                struct P_topo_b *topo;

                line = NList->value[i];
                if (!Vect_line_alive(Map, abs(line)))
                    continue;

                topo = Map->plus.Line[abs(line)]->topo;

                if (topo->left == 0 || topo->right == 0) {
                    int new_isle;

                    new_isle = Vect_build_line_area(
                        Map, abs(line), (line > 0 ? GV_RIGHT : GV_LEFT));
                    if (new_isle < 0) {
                        Vect_list_append(IList, -new_isle);
                    }
                    else {
                        /* area or nothing should not happen */
                        G_fatal_error(_("Failed to build new isle"));
                    }
                }
                /* check */
                if (topo->left == 0 || topo->right == 0)
                    G_fatal_error(
                        _("Dissolve with outer isle: corrupt topology"));
            }
        }

        if (dissolve_neighbour > 0 && outer_area <= 0) {
            G_fatal_error(_("Area merging failed"));
        }

        /* attach all isles to outer or new area */
        if (outer_area >= 0) {
            for (i = 0; i < IList->n_values; i++) {
                if (!Map->plus.Isle[IList->value[i]])
                    continue;
                Map->plus.Isle[IList->value[i]]->area = outer_area;
                if (outer_area > 0)
                    dig_area_add_isle(&(Map->plus), outer_area,
                                      IList->value[i]);
            }
        }

        nremoved++;
        nareas = Vect_get_num_areas(Map);
    }

    if (removed_area)
        *removed_area = size_removed;

    G_message(_("%d areas of total size %g removed"), nremoved, size_removed);

    Vect_destroy_list(List);
    Vect_destroy_list(AList);
    Vect_destroy_list(BList);
    Vect_destroy_list(NList);
    Vect_destroy_list(IList);
    Vect_destroy_line_struct(Points);
    Vect_destroy_cats_struct(Cats);

    return (nremoved);
}
