/**
 * \file plus_line.c
 *
 * \brief Vector library - update topo for lines (lower level functions)
 *
 * Lower level functions for reading/writing/manipulating vectors.
 *
 * This program is free software under the GNU General Public License
 * (>=v2). Read the file COPYING that comes with GRASS for details.
 *
 * \author CERL (probably Dave Gerdes), Radim Blazek
 *
 * \date 2001-2008
 */

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

static int add_line(struct Plus_head *plus, int lineid, int type,
                    const struct line_pnts *Points, const struct bound_box *box,
                    off_t offset)
{
    int node, lp, node_new;
    struct P_line *line;

    plus->Line[lineid] = dig_alloc_line();
    line = plus->Line[lineid];

    line->type = type;
    line->offset = offset;

    dig_spidx_add_line(plus, lineid, box);
    if (plus->uplist.do_uplist) {
        dig_line_add_updated(plus, lineid, offset);
    }

    if (type & GV_POINT) {
        line->topo = NULL;
        return (lineid);
    }

    line->topo = dig_alloc_topo(type);

    if (type & GV_CENTROID) {
        struct P_topo_c *topo = (struct P_topo_c *)line->topo;

        topo->area = 0;
        return (lineid);
    }

    /* Add nodes for lines */
    G_debug(3, "Register node: type = %d,  %f,%f", type, Points->x[0],
            Points->y[0]);

    /* Start node */
    node = dig_find_node(plus, Points->x[0], Points->y[0], Points->z[0]);
    G_debug(3, "node = %d", node);
    if (node == 0) {
        node = dig_add_node(plus, Points->x[0], Points->y[0], Points->z[0]);
        G_debug(3, "Add new node: %d", node);
        node_new = TRUE;
    }
    else {
        G_debug(3, "Old node found: %d", node);
        node_new = FALSE;
    }

    if (type == GV_LINE) {
        struct P_topo_l *topo = (struct P_topo_l *)line->topo;

        topo->N1 = node;
        topo->N2 = 0;
    }
    else if (type == GV_BOUNDARY) {
        struct P_topo_b *topo = (struct P_topo_b *)line->topo;

        topo->N1 = node;
        topo->N2 = 0;
        topo->left = 0;
        topo->right = 0;
    }

    dig_node_add_line(plus, node, lineid, Points, type);
    if (plus->uplist.do_uplist)
        dig_node_add_updated(plus, node_new ? -node : node);

    /* End node */
    lp = Points->n_points - 1;
    G_debug(3, "Register node %f,%f", Points->x[lp], Points->y[lp]);
    node = dig_find_node(plus, Points->x[lp], Points->y[lp], Points->z[lp]);
    G_debug(3, "node = %d", node);
    if (node == 0) {
        node = dig_add_node(plus, Points->x[lp], Points->y[lp], Points->z[lp]);
        G_debug(3, "Add new node: %d", node);
        node_new = TRUE;
    }
    else {
        G_debug(3, "Old node found: %d", node);
        node_new = FALSE;
    }
    if (type == GV_LINE) {
        struct P_topo_l *topo = (struct P_topo_l *)line->topo;

        topo->N2 = node;
    }
    else if (type == GV_BOUNDARY) {
        struct P_topo_b *topo = (struct P_topo_b *)line->topo;

        topo->N2 = node;
    }

    dig_node_add_line(plus, node, -lineid, Points, type);
    if (plus->uplist.do_uplist)
        dig_node_add_updated(plus, node_new ? -node : node);

    return (lineid);
}

/*!
 * \brief Add new line to Plus_head structure.
 *
 * \param[in,out] plus pointer to Plus_head structure
 * \param type feature type
 * \param Points line geometry
 * \param box bounding box
 * \param offset line offset
 *
 * \return -1 on error
 * \return line id
 */
int dig_add_line(struct Plus_head *plus, int type,
                 const struct line_pnts *Points, const struct bound_box *box,
                 off_t offset)
{
    int ret;

    /* First look if we have space in array of pointers to lines
     *  and reallocate if necessary */
    if (plus->n_lines >= plus->alloc_lines) { /* array is full */
        if (dig_alloc_lines(plus, 1000) == -1)
            return -1;
    }

    ret = add_line(plus, plus->n_lines + 1, type, Points, box, offset);

    if (ret == -1)
        return ret;

    plus->n_lines++;

    switch (type) {
    case GV_POINT:
        plus->n_plines++;
        break;
    case GV_LINE:
        plus->n_llines++;
        break;
    case GV_BOUNDARY:
        plus->n_blines++;
        break;
    case GV_CENTROID:
        plus->n_clines++;
        break;
    case GV_FACE:
        plus->n_flines++;
        break;
    case GV_KERNEL:
        plus->n_klines++;
        break;
    }

    return ret;
}

/*!
 * \brief Restore line in Plus_head structure.
 *
 * \param[in,out] plus pointer to Plus_head structure
 * \param lineid
 * \param type feature type
 * \param Points line geometry
 * \param box bounding box
 * \param offset line offset
 *
 * \return -1 on error
 * \return line id
 */
int dig_restore_line(struct Plus_head *plus, int lineid, int type,
                     const struct line_pnts *Points,
                     const struct bound_box *box, off_t offset)
{
    if (lineid < 1 || lineid > plus->n_lines) {
        return -1;
    }

    return add_line(plus, lineid, type, Points, box, offset);
}

/*!
 * \brief Delete line from Plus_head structure.
 *
 * Doesn't update area/isle references (dig_del_area() or dig_del_isle()) must
 * be run before the line is deleted if the line is part of such structure).
 * Update is info about line in nodes. If this line is last in node then node is
 * deleted.
 *
 * \param[in,out] plus pointer to Plus_head structure
 * \param[in] line line id
 * \param[in] x,y,z coordinates
 *
 * \return -1 on error
 * \return  0 OK
 *
 */
int dig_del_line(struct Plus_head *plus, int line, double x, double y, double z)
{
    int i;
    plus_t N1 = 0, N2 = 0;
    struct P_line *Line;
    struct P_node *Node;

    G_debug(3, "dig_del_line() line =  %d", line);

    Line = plus->Line[line];
    dig_spidx_del_line(plus, line, x, y, z);

    if (plus->uplist.do_uplist) {
        dig_line_add_updated(plus, line, -Line->offset);
    }

    if (!(Line->type & GV_LINES)) {
        /* Delete line */
        dig_free_line(Line);
        plus->Line[line] = NULL;

        return 0;
    }

    /* Delete from nodes (and nodes) */
    if (Line->type == GV_LINE) {
        struct P_topo_l *topo = (struct P_topo_l *)Line->topo;

        N1 = topo->N1;
    }
    else if (Line->type == GV_BOUNDARY) {
        struct P_topo_b *topo = (struct P_topo_b *)Line->topo;

        N1 = topo->N1;
    }

    Node = plus->Node[N1];

    i = 0;
    while (i < Node->n_lines && Node->lines[i] != line)
        i++;

    if (i == Node->n_lines) {
        G_fatal_error(
            _("Attempt to delete not registered line %d from node %d"), line,
            N1);
    }

    i++;
    while (i < Node->n_lines) {
        Node->lines[i - 1] = Node->lines[i];
        Node->angles[i - 1] = Node->angles[i];
        i++;
    }
    Node->n_lines--;

    if (plus->uplist.do_uplist) {
        dig_node_add_updated(plus, Node->n_lines > 0 ? N1 : -N1);
    }
    if (Node->n_lines == 0) {
        G_debug(3, "    node %d has 0 lines -> delete", N1);
        dig_spidx_del_node(plus, N1);
        /* free structures */
        dig_free_node(Node);
        plus->Node[N1] = NULL;
    }

    if (Line->type == GV_LINE) {
        struct P_topo_l *topo = (struct P_topo_l *)Line->topo;

        N2 = topo->N2;
    }
    else if (Line->type == GV_BOUNDARY) {
        struct P_topo_b *topo = (struct P_topo_b *)Line->topo;

        N2 = topo->N2;
    }

    Node = plus->Node[N2];
    i = 0;
    while (i < Node->n_lines && Node->lines[i] != -line)
        i++;

    if (i == Node->n_lines) {
        G_fatal_error(
            _("Attempt to delete not registered line %d from node %d"), -line,
            N2);
    }

    i++;
    while (i < Node->n_lines) {
        Node->lines[i - 1] = Node->lines[i];
        Node->angles[i - 1] = Node->angles[i];
        i++;
    }
    Node->n_lines--;

    if (plus->uplist.do_uplist) {
        dig_node_add_updated(plus, Node->n_lines > 0 ? N2 : -N2);
    }
    if (Node->n_lines == 0) {
        G_debug(3, "    node %d has 0 lines -> delete", N2);
        dig_spidx_del_node(plus, N2);
        /* free structures */
        dig_free_node(Node);
        plus->Node[N2] = NULL;
    }

    /* Delete line */
    dig_free_line(Line);
    plus->Line[line] = NULL;

    return 0;
}

/*!
 * \brief Get area number on line side.
 *
 * \param[in] plus pointer Plus_head structure
 * \param[in] line line id
 * \param[in] side side id (GV_LEFT || GV_RIGHT)
 *
 * \return  area number
 * \return  0 no area
 * \return -1 on error
 */
plus_t dig_line_get_area(struct Plus_head *plus, plus_t line, int side)
{
    struct P_line *Line;
    struct P_topo_b *topo;

    Line = plus->Line[line];
    if (!Line) /* dead */
        return -1;

    if (Line->type != GV_BOUNDARY)
        return -1;

    topo = (struct P_topo_b *)Line->topo;
    if (side == GV_LEFT) {
        G_debug(3,
                "dig_line_get_area(): line = %d, side = %d (left), area = %d",
                line, side, topo->left);
        return (topo->left);
    }
    if (side == GV_RIGHT) {
        G_debug(3,
                "dig_line_get_area(): line = %d, side = %d (right), area = %d",
                line, side, topo->right);

        return (topo->right);
    }

    return (-1);
}

/*!
 * \brief Set area number on line side
 *
 * \param[in] plus pointer Plus_head structure
 * \param[in] line line id
 * \param[in] side side id (GV_LEFT || GV_RIGHT)
 * \param[in] area area id
 *
 * \return 1
 */
int dig_line_set_area(struct Plus_head *plus, plus_t line, int side,
                      plus_t area)
{
    struct P_line *Line;
    struct P_topo_b *topo;

    Line = plus->Line[line];
    if (Line->type != GV_BOUNDARY)
        return (0);

    topo = (struct P_topo_b *)Line->topo;

    if (side == GV_LEFT) {
        topo->left = area;
    }
    else if (side == GV_RIGHT) {
        topo->right = area;
    }

    return (1);
}

/*!
 * \brief Set line bounding box
 *
 * \param[in] plus pointer Plus_head structure
 * \param[in] line line id
 * \param[in] Box  bounding box
 *
 * \return 1
 */
/*
   int dig_line_set_box(struct Plus_head *plus, plus_t line, struct bound_box *
   Box)
   {
   struct P_line *Line;

   Line = plus->Line[line];

   Line->N = Box->N;
   Line->S = Box->S;
   Line->E = Box->E;
   Line->W = Box->W;
   Line->T = Box->T;
   Line->B = Box->B;

   return (1);
   }
 */

/*!
 * \brief Get line bounding box saved in topo
 *
 * \param[in] plus pointer Plus_head structure
 * \param[in] line line id
 * \param[in,out] Box bounding box
 *
 * \return 1
 */
/*
   int dig_line_get_box(struct Plus_head *plus, plus_t line, struct bound_box *
   Box)
   {
   struct P_line *Line;

   Line = plus->Line[line];

   Box->N = Line->N;
   Box->S = Line->S;
   Box->E = Line->E;
   Box->W = Line->W;
   Box->T = Line->T;
   Box->B = Line->B;

   return (1);
   }
 */
