/* LIBDGL -- a Directed Graph Library implementation
 * Copyright (C) 2002 Roberto Micarelli
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

/*
 * best view with tabstop=4
 */

#include <grass/gis.h>

/*
 * Add edge can be performed on TREE state graph. If the state is FLAT
 * return BadOnFlatGraph error.
 */
int DGL_ADD_EDGE_FUNC(dglGraph_s *pgraph, dglInt32_t nHead, dglInt32_t nTail,
                      dglInt32_t nCost, dglInt32_t nEdge, void *pvHeadAttr,
                      void *pvTailAttr, void *pvEdgeAttr, dglInt32_t nFlags)
{
    dglInt32_t *pHead;
    dglInt32_t *pTail;
    dglInt32_t *pEdgeset;
    dglInt32_t *pEdge;
    DGL_T_NODEITEM_TYPE *pHeadNodeItem;
    DGL_T_NODEITEM_TYPE *pTailNodeItem;

#if defined(_DGL_V2)
    dglInt32_t *pinEdgeset;
    dglTreeEdge_s *pEdgeItem;
    /* dglTreeEdge_s findEdge; */
#endif

    if (pgraph->Flags & DGL_GS_FLAT) {
        pgraph->iErrno = DGL_ERR_BadOnFlatGraph;
        return -pgraph->iErrno;
    }

#ifdef DGL_STATS
    {
        clock_t clk = clock();
#endif

        if ((pHeadNodeItem = DGL_T_NODEITEM_Add(pgraph->pNodeTree, nHead)) ==
                NULL ||
            (pTailNodeItem = DGL_T_NODEITEM_Add(pgraph->pNodeTree, nTail)) ==
                NULL) {
            pgraph->iErrno = DGL_ERR_MemoryExhausted;
            return -pgraph->iErrno;
        }

#ifdef DGL_STATS
        pgraph->clkNodeTree += clock() - clk;
        pgraph->cNodeTree++;
        pgraph->cNodeTree++;
    }
#endif

    if (DGL_T_NODEITEM_NodePTR(pHeadNodeItem) == NULL) {
        if ((pHead = DGL_NODE_ALLOC(pgraph->NodeAttrSize)) == NULL) {
            pgraph->iErrno = DGL_ERR_MemoryExhausted;
            return -1;
        }
        DGL_NODE_STATUS(pHead) = 0;
        DGL_T_NODEITEM_Set_NodePTR(pHeadNodeItem, pHead);
        pgraph->cNode++;
        pgraph->cHead++;
    }
    else {
        pHead = DGL_T_NODEITEM_NodePTR(pHeadNodeItem);
        if (!(DGL_NODE_STATUS(pHead) & DGL_NS_HEAD))
            pgraph->cHead++;
    }

    if (DGL_T_NODEITEM_NodePTR(pTailNodeItem) == NULL) {
        if ((pTail = DGL_NODE_ALLOC(pgraph->NodeAttrSize)) == NULL) {
            pgraph->iErrno = DGL_ERR_MemoryExhausted;
            return -pgraph->iErrno;
        }
        DGL_NODE_STATUS(pTail) = 0;
        DGL_T_NODEITEM_Set_NodePTR(pTailNodeItem, pTail);
        pgraph->cNode++;
        pgraph->cTail++;
    }
    else {
        pTail = DGL_T_NODEITEM_NodePTR(pTailNodeItem);
        if (!(DGL_NODE_STATUS(pTail) & DGL_NS_TAIL))
            pgraph->cTail++;
    }

    DGL_NODE_STATUS(pHead) |= DGL_NS_HEAD;
    DGL_NODE_STATUS(pTail) |= DGL_NS_TAIL;

    if (DGL_NODE_STATUS(pHead) & DGL_NS_ALONE) {
        DGL_NODE_STATUS(pHead) &= ~DGL_NS_ALONE;
        pgraph->cAlone--;
    }

    if (DGL_NODE_STATUS(pTail) & DGL_NS_ALONE) {
        DGL_NODE_STATUS(pTail) &= ~DGL_NS_ALONE;
        pgraph->cAlone--;
    }

    DGL_NODE_ID(pHead) = nHead;
    DGL_NODE_ID(pTail) = nTail;

    DGL_NODE_EDGESET_OFFSET(pHead) = -1;
    DGL_NODE_EDGESET_OFFSET(pTail) = -1;

    if (pvHeadAttr && pgraph->NodeAttrSize) {
        memcpy(DGL_NODE_ATTR_PTR(pHead), pvHeadAttr, pgraph->NodeAttrSize);
    }

    if (pvTailAttr && pgraph->NodeAttrSize) {
        memcpy(DGL_NODE_ATTR_PTR(pTail), pvTailAttr, pgraph->NodeAttrSize);
    }

    /*
       if ( DGL_T_NODEITEM_OutEdgesetPTR(pTailNodeItem) == NULL )
       {
       pEdgeset = DGL_EDGESET_ALLOC( 0 , pgraph->EdgeAttrSize );
       if ( pEdgeset == NULL ) {
       pgraph->iErrno = DGL_ERR_MemoryExhausted;
       return -pgraph->iErrno;
       }
       DGL_EDGESET_EDGECOUNT(pEdgeset) = 0;
       DGL_T_NODEITEM_Set_OutEdgesetPTR(pTailNodeItem,pEdgeset);
       }
     */

    if ((pEdgeset = DGL_T_NODEITEM_OutEdgesetPTR(pHeadNodeItem)) == NULL) {
        pEdgeset = DGL_EDGESET_ALLOC(1, pgraph->EdgeAttrSize);
        if (pEdgeset == NULL) {
            pgraph->iErrno = DGL_ERR_MemoryExhausted;
            return -pgraph->iErrno;
        }
        DGL_EDGESET_EDGECOUNT(pEdgeset) = 0;
        DGL_T_NODEITEM_Set_OutEdgesetPTR(pHeadNodeItem, pEdgeset);
    }
    else {
        pEdgeset =
            DGL_EDGESET_REALLOC(pEdgeset, DGL_EDGESET_EDGECOUNT(pEdgeset) + 1,
                                pgraph->EdgeAttrSize);

        if (pEdgeset == NULL) {
            pgraph->iErrno = DGL_ERR_MemoryExhausted;
            return -pgraph->iErrno;
        }
        DGL_T_NODEITEM_Set_OutEdgesetPTR(pHeadNodeItem, pEdgeset);
    }

#if defined(_DGL_V2)
    /*
       if ( DGL_T_NODEITEM_InEdgesetPTR(pHeadNodeItem) == NULL )
       {
       pinEdgeset = DGL_EDGESET_ALLOC( 0 , pgraph->EdgeAttrSize );
       if ( pinEdgeset == NULL ) {
       pgraph->iErrno = DGL_ERR_MemoryExhausted;
       return -pgraph->iErrno;
       }
       DGL_EDGESET_EDGECOUNT(pinEdgeset) = 0;
       DGL_T_NODEITEM_Set_InEdgesetPTR(pHeadNodeItem,pinEdgeset);
       }
     */

    if ((pinEdgeset = DGL_T_NODEITEM_InEdgesetPTR(pTailNodeItem)) == NULL) {
        pinEdgeset = DGL_EDGESET_ALLOC(1, pgraph->EdgeAttrSize);
        if (pinEdgeset == NULL) {
            pgraph->iErrno = DGL_ERR_MemoryExhausted;
            return -pgraph->iErrno;
        }
        DGL_EDGESET_EDGECOUNT(pinEdgeset) = 0;
        DGL_T_NODEITEM_Set_InEdgesetPTR(pTailNodeItem, pinEdgeset);
    }
    else {
        pinEdgeset = DGL_EDGESET_REALLOC(pinEdgeset,
                                         DGL_EDGESET_EDGECOUNT(pinEdgeset) + 1,
                                         pgraph->EdgeAttrSize);

        if (pinEdgeset == NULL) {
            pgraph->iErrno = DGL_ERR_MemoryExhausted;
            return -pgraph->iErrno;
        }
        DGL_T_NODEITEM_Set_InEdgesetPTR(pTailNodeItem, pinEdgeset);
    }

    /*
     * Set the edge-tree
     */
    /* findEdge.nKey = nEdge; */

    if ((pEdgeItem = dglTreeEdgeAdd(pgraph->pEdgeTree, nEdge)) == NULL) {
        pgraph->iErrno = DGL_ERR_MemoryExhausted;
        return -pgraph->iErrno;
    }
    if (pEdgeItem->pv) {
        pgraph->iErrno = DGL_ERR_EdgeAlreadyExist;
        return -pgraph->iErrno;
    }
    if ((pEdgeItem->pv = DGL_EDGE_ALLOC(pgraph->EdgeAttrSize)) == NULL) {
        pgraph->iErrno = DGL_ERR_MemoryExhausted;
        return -pgraph->iErrno;
    }

    /*
     * assign edge id
     */
    pEdgeset[DGL_EDGESET_EDGECOUNT(pEdgeset) + 1] = nEdge;
    pinEdgeset[DGL_EDGESET_EDGECOUNT(pinEdgeset) + 1] = nEdge;
    ++DGL_EDGESET_EDGECOUNT(pEdgeset);
    ++DGL_EDGESET_EDGECOUNT(pinEdgeset);

    /*
       printf( "add edge: node %ld(%ld,%ld) -> %ld(%ld,%ld)\n",
       DGL_NODE_ID(pHead), DGL_EDGESET_EDGECOUNT(pEdgeset),0,
       DGL_NODE_ID(pTail), 0,DGL_EDGESET_EDGECOUNT(pinEdgeset));
     */

    pEdge = pEdgeItem->pv;
#endif

#if defined(_DGL_V1)
    pEdge = DGL_EDGESET_EDGE_PTR(pEdgeset, DGL_EDGESET_EDGECOUNT(pEdgeset),
                                 pgraph->EdgeAttrSize);
    DGL_EDGESET_EDGECOUNT(pEdgeset)++;
#endif

    DGL_EDGE_HEADNODE_OFFSET(pEdge) =
        nHead; /* will be an offset after flattening */
    DGL_EDGE_TAILNODE_OFFSET(pEdge) =
        nTail; /* will be an offset after flattening */
    DGL_EDGE_COST(pEdge) = nCost;
    DGL_EDGE_ID(pEdge) = nEdge;

#if !defined(_DGL_V1)
    if (nFlags & DGL_ES_DIRECTED)
        DGL_EDGE_STATUS(pEdge) = DGL_ES_DIRECTED;
    else
        DGL_EDGE_STATUS(pEdge) = 0;
#endif

    pgraph->cEdge++;
    pgraph->nnCost += (dglInt64_t)nCost;

    if (pvEdgeAttr && pgraph->EdgeAttrSize) {
        memcpy(DGL_EDGE_ATTR_PTR(pEdge), pvEdgeAttr, pgraph->EdgeAttrSize);
    }

    /*
     * If requested add a cost-weighted entry into the edge prioritizer
     */
#if !defined(_DGL_V1)
    if (pgraph->nOptions & DGL_GO_EdgePrioritize_COST) {
        if (dgl_edge_prioritizer_add(pgraph, DGL_EDGE_ID(pEdge),
                                     DGL_EDGE_COST(pEdge)) < 0) {
            return -pgraph->iErrno;
        }
    }
#endif

    if (nFlags & DGL_STRONGCONNECT) {
        return DGL_ADD_EDGE_FUNC(pgraph, nTail, nHead, nCost, nEdge, pvHeadAttr,
                                 pvTailAttr, pvEdgeAttr,
                                 nFlags & ~DGL_STRONGCONNECT);
    }

    return 0;
}

int DGL_DEL_EDGE_FUNC(dglGraph_s *pgraph, dglInt32_t nEdge UNUSED)
{
#if defined(_DGL_V1)
    pgraph->iErrno = DGL_ERR_NotSupported;
    return -pgraph->iErrno;
#else
    dglTreeEdge_s *pEdgeItem, findEdgeItem;
    dglInt32_t *pEdge;

    if (pgraph->Flags & DGL_GS_FLAT) {
        pgraph->iErrno = DGL_ERR_BadOnFlatGraph;
        return -pgraph->iErrno;
    }

    if (pgraph->pEdgeTree == NULL) {
        pgraph->iErrno = DGL_ERR_UnexpectedNullPointer;
        return -pgraph->iErrno;
    }

    findEdgeItem.nKey = nEdge;
    if ((pEdgeItem = avl_find(pgraph->pEdgeTree, &findEdgeItem)) == NULL) {
        pgraph->iErrno = DGL_ERR_EdgeNotFound;
        return -pgraph->iErrno;
    }

    pEdge = pEdgeItem->pv;

    if (DGL_DEL_NODE_INEDGE_FUNC(pgraph, DGL_EDGE_TAILNODE_OFFSET(pEdge),
                                 DGL_EDGE_ID(pEdge)) < 0) {
        return -pgraph->iErrno;
    }

    if (DGL_DEL_NODE_OUTEDGE_FUNC(pgraph, DGL_EDGE_HEADNODE_OFFSET(pEdge),
                                  DGL_EDGE_ID(pEdge)) < 0) {
        return -pgraph->iErrno;
    }

    /* prioritizer sync
     */
    if (pgraph->nOptions & DGL_GO_EdgePrioritize_COST) {
        if (dgl_edge_prioritizer_del(pgraph, DGL_EDGE_ID(pEdge),
                                     DGL_EDGE_COST(pEdge)) < 0) {
            return -pgraph->iErrno;
        }
    }
    /*
     */
    pgraph->cEdge--;
    pgraph->nnCost -= (dglInt64_t)DGL_EDGE_COST(pEdge);

    avl_delete(pgraph->pEdgeTree, pEdgeItem);
    dglTreeEdgeCancel(pEdgeItem, NULL);
    return 0;
#endif
}

dglInt32_t *DGL_GET_EDGE_FUNC(dglGraph_s *pgraph, dglInt32_t nEdge UNUSED)
{
#if defined(_DGL_V1)
    pgraph->iErrno = DGL_ERR_NotSupported;
    return NULL;
#else
    register dglInt32_t top; /* top of table */
    register dglInt32_t pos; /* current position to compare */
    register dglInt32_t bot; /* bottom of table */
    register dglInt32_t *pref;
    register int cwords; /* size of a edge in words of 32 bit */
    register dglTreeEdge_s *ptreeEdge;
    dglTreeEdge_s findEdge;
    dglInt32_t id;

    pgraph->iErrno = 0;
    if (pgraph->Flags & DGL_GS_FLAT) {
        cwords = DGL_EDGE_WSIZE(pgraph->EdgeAttrSize);
        /*bot    = pgraph->iEdgeBuffer / DGL_EDGE_SIZEOF(pgraph->EdgeAttrSize);
         */
        bot = pgraph->cEdge;
        top = 0;
        pos = 0;
        pref = (dglInt32_t *)pgraph->pEdgeBuffer;

        /* perform a binary search
         */
        while (top != bot) {
            pos = top + (bot - top) / 2;
            id = DGL_EDGE_ID(&pref[pos * cwords]);
            if (id == nEdge) {
                break;
            }
            else if (nEdge < id) {
                bot = pos;
            }
            else if (nEdge > id) {
                top = pos + 1;
            }
        }
        if (top == bot) {
            return NULL;
        }
        return &pref[pos * cwords];
    }
    else {
        findEdge.nKey = nEdge;
        ptreeEdge = avl_find(pgraph->pEdgeTree, &findEdge);
        if (ptreeEdge && ptreeEdge->pv) {
            return ptreeEdge->pv;
        }
        return NULL;
    }
#endif
}
