/**CFile****************************************************************

  FileName    [nwkMerge.c]

  SystemName  [ABC: Logic synthesis and verification system.]

  PackageName [Netlist representation.]

  Synopsis    [LUT merging algorithm.]

  Author      [Alan Mishchenko]
  
  Affiliation [UC Berkeley]

  Date        [Ver. 1.0. Started - June 20, 2005.]

  Revision    [$Id: nwkMerge.c,v 1.00 2005/06/20 00:00:00 alanmi Exp $]

***********************************************************************/

#include "nwk.h"
#include "nwkMerge.h"

ABC_NAMESPACE_IMPL_START


////////////////////////////////////////////////////////////////////////
///                        DECLARATIONS                              ///
////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////
///                     FUNCTION DEFINITIONS                         ///
////////////////////////////////////////////////////////////////////////

/**Function*************************************************************

  Synopsis    [Allocates the graph.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
Nwk_Grf_t * Nwk_ManGraphAlloc( int nVertsMax )
{
    Nwk_Grf_t * p;
    p = ABC_ALLOC( Nwk_Grf_t, 1 );
    memset( p, 0, sizeof(Nwk_Grf_t) );
    p->nVertsMax = nVertsMax;
    p->nEdgeHash = Abc_PrimeCudd( 3 * nVertsMax );
    p->pEdgeHash = ABC_CALLOC( Nwk_Edg_t *, p->nEdgeHash );
    p->pMemEdges = Aig_MmFixedStart( sizeof(Nwk_Edg_t), p->nEdgeHash );
    p->vPairs    = Vec_IntAlloc( 1000 );
    return p;
}

/**Function*************************************************************

  Synopsis    [Deallocates the graph.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
void Nwk_ManGraphFree( Nwk_Grf_t * p )
{
    if ( p->vPairs )    Vec_IntFree( p->vPairs );
    if ( p->pMemEdges ) Aig_MmFixedStop( p->pMemEdges, 0 );
    if ( p->pMemVerts ) Aig_MmFlexStop( p->pMemVerts, 0 );
    ABC_FREE( p->pVerts );
    ABC_FREE( p->pEdgeHash );
    ABC_FREE( p->pMapLut2Id );
    ABC_FREE( p->pMapId2Lut );
    ABC_FREE( p );
}

/**Function*************************************************************

  Synopsis    [Prepares the graph for solving the problem.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
void Nwk_ManGraphReportMemoryUsage( Nwk_Grf_t * p )
{
    p->nMemBytes1 = 
        sizeof(Nwk_Grf_t) +
        sizeof(void *) * p->nEdgeHash + 
        sizeof(int) * (p->nObjs + p->nVertsMax) +
        sizeof(Nwk_Edg_t) * p->nEdges;
    p->nMemBytes2 = 
        sizeof(Nwk_Vrt_t) * p->nVerts + 
        sizeof(int) * 2 * p->nEdges;
    printf( "Memory usage stats:  Preprocessing = %.2f MB.  Solving = %.2f MB.\n",
        1.0 * p->nMemBytes1 / (1<<20), 1.0 * p->nMemBytes2 / (1<<20) );
}


/**Function*************************************************************

  Synopsis    [Finds or adds the edge to the graph.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
void Nwk_ManGraphHashEdge( Nwk_Grf_t * p, int iLut1, int iLut2 )
{
    Nwk_Edg_t * pEntry;
    unsigned Key;
    if ( iLut1 == iLut2 )
        return;
    if ( iLut1 > iLut2 )
    {
        Key = iLut1;
        iLut1 = iLut2;
        iLut2 = Key;
    }
    assert( iLut1 < iLut2 );
    if ( p->nObjs < iLut2 )
        p->nObjs = iLut2;
    Key = (unsigned)(741457 * iLut1 + 4256249 * iLut2) % p->nEdgeHash;
    for ( pEntry = p->pEdgeHash[Key]; pEntry; pEntry = pEntry->pNext )
        if ( pEntry->iNode1 == iLut1 && pEntry->iNode2 == iLut2 )
            return;
    pEntry = (Nwk_Edg_t *)Aig_MmFixedEntryFetch( p->pMemEdges );
    pEntry->iNode1 = iLut1;
    pEntry->iNode2 = iLut2;
    pEntry->pNext = p->pEdgeHash[Key];
    p->pEdgeHash[Key] = pEntry;
    p->nEdges++;
}

/**Function*************************************************************

  Synopsis    [Adds one entry to the list.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
static inline void Nwk_ManGraphListAdd( Nwk_Grf_t * p, int * pList, Nwk_Vrt_t * pVertex )
{
    if ( *pList )
    {
        Nwk_Vrt_t * pHead;
        pHead = p->pVerts[*pList];
        pVertex->iPrev = 0;
        pVertex->iNext = pHead->Id;
        pHead->iPrev = pVertex->Id;
    }
    *pList = pVertex->Id;
}

/**Function*************************************************************

  Synopsis    [Deletes one entry from the list.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
static inline void Nwk_ManGraphListDelete( Nwk_Grf_t * p, int * pList, Nwk_Vrt_t * pVertex )
{
    assert( *pList );
    if ( pVertex->iPrev )
    {
//        assert( p->pVerts[pVertex->iPrev]->iNext == pVertex->Id );
        p->pVerts[pVertex->iPrev]->iNext = pVertex->iNext;
    }
    if ( pVertex->iNext )
    {
//        assert( p->pVerts[pVertex->iNext]->iPrev == pVertex->Id );
        p->pVerts[pVertex->iNext]->iPrev = pVertex->iPrev;
    }
    if ( *pList == pVertex->Id )
        *pList = pVertex->iNext;
    pVertex->iPrev = pVertex->iNext = 0;
}

/**Function*************************************************************

  Synopsis    [Inserts the edge into one of the linked lists.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
static inline void Nwk_ManGraphListInsert( Nwk_Grf_t * p, Nwk_Vrt_t * pVertex )
{
    Nwk_Vrt_t * pNext;
    assert( pVertex->nEdges > 0 );

    if ( pVertex->nEdges == 1 )
    {
        pNext = p->pVerts[ pVertex->pEdges[0] ];
        if ( pNext->nEdges >= NWK_MAX_LIST )
            Nwk_ManGraphListAdd( p, p->pLists1 + NWK_MAX_LIST, pVertex );
        else
            Nwk_ManGraphListAdd( p, p->pLists1 + pNext->nEdges, pVertex );
    }
    else
    {
        if ( pVertex->nEdges >= NWK_MAX_LIST )
            Nwk_ManGraphListAdd( p, p->pLists2 + NWK_MAX_LIST, pVertex );
        else
            Nwk_ManGraphListAdd( p, p->pLists2 + pVertex->nEdges, pVertex );
    }
}

/**Function*************************************************************

  Synopsis    [Extracts the edge from one of the linked lists.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
static inline void Nwk_ManGraphListExtract( Nwk_Grf_t * p, Nwk_Vrt_t * pVertex )
{
    Nwk_Vrt_t * pNext;
    assert( pVertex->nEdges > 0 );

    if ( pVertex->nEdges == 1 )
    {
        pNext = p->pVerts[ pVertex->pEdges[0] ];
        if ( pNext->nEdges >= NWK_MAX_LIST )
            Nwk_ManGraphListDelete( p, p->pLists1 + NWK_MAX_LIST, pVertex );
        else
            Nwk_ManGraphListDelete( p, p->pLists1 + pNext->nEdges, pVertex );
    }
    else
    {
        if ( pVertex->nEdges >= NWK_MAX_LIST )
            Nwk_ManGraphListDelete( p, p->pLists2 + NWK_MAX_LIST, pVertex );
        else
            Nwk_ManGraphListDelete( p, p->pLists2 + pVertex->nEdges, pVertex );
    }
}

/**Function*************************************************************

  Synopsis    [Prepares the graph for solving the problem.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
void Nwk_ManGraphPrepare( Nwk_Grf_t * p )
{
    Nwk_Edg_t * pEntry;
    Nwk_Vrt_t * pVertex;
    int * pnEdges, nBytes, i;
    // allocate memory for the present objects
    p->pMapLut2Id = ABC_ALLOC( int, p->nObjs+1 );
    p->pMapId2Lut = ABC_ALLOC( int, p->nVertsMax+1 );
    memset( p->pMapLut2Id, 0xff, sizeof(int) * (p->nObjs+1) );
    memset( p->pMapId2Lut, 0xff, sizeof(int) * (p->nVertsMax+1) );
    // mark present objects
    Nwk_GraphForEachEdge( p, pEntry, i )
    {
        assert( pEntry->iNode1 <= p->nObjs );
        assert( pEntry->iNode2 <= p->nObjs );
        p->pMapLut2Id[ pEntry->iNode1 ] = 0;
        p->pMapLut2Id[ pEntry->iNode2 ] = 0;
    }
    // map objects
    p->nVerts = 0;
    for ( i = 0; i <= p->nObjs; i++ )
    {
        if ( p->pMapLut2Id[i] == 0 )
        {
            p->pMapLut2Id[i] = ++p->nVerts;
            p->pMapId2Lut[p->nVerts] = i;
        }
    }
    // count the edges and mark present objects
    pnEdges = ABC_CALLOC( int, p->nVerts+1 );
    Nwk_GraphForEachEdge( p, pEntry, i )
    {
        // translate into vertices
        assert( pEntry->iNode1 <= p->nObjs );
        assert( pEntry->iNode2 <= p->nObjs );
        pEntry->iNode1 = p->pMapLut2Id[pEntry->iNode1];
        pEntry->iNode2 = p->pMapLut2Id[pEntry->iNode2];
        // count the edges
        assert( pEntry->iNode1 <= p->nVerts );
        assert( pEntry->iNode2 <= p->nVerts );
        pnEdges[pEntry->iNode1]++;
        pnEdges[pEntry->iNode2]++;
    }
    // allocate the real graph
    p->pMemVerts  = Aig_MmFlexStart();
    p->pVerts = ABC_ALLOC( Nwk_Vrt_t *, p->nVerts + 1 );
    p->pVerts[0] = NULL;
    for ( i = 1; i <= p->nVerts; i++ )
    {
        assert( pnEdges[i] > 0 );
        nBytes = sizeof(Nwk_Vrt_t) + sizeof(int) * pnEdges[i];
        p->pVerts[i] = (Nwk_Vrt_t *)Aig_MmFlexEntryFetch( p->pMemVerts, nBytes );
        memset( p->pVerts[i], 0, nBytes );
        p->pVerts[i]->Id = i;
    }
    // add edges to the real graph
    Nwk_GraphForEachEdge( p, pEntry, i )
    {
        pVertex = p->pVerts[pEntry->iNode1];
        pVertex->pEdges[ pVertex->nEdges++ ] = pEntry->iNode2;
        pVertex = p->pVerts[pEntry->iNode2];
        pVertex->pEdges[ pVertex->nEdges++ ] = pEntry->iNode1;
    }
    // put vertices into the data structure
    for ( i = 1; i <= p->nVerts; i++ )
    {
        assert( p->pVerts[i]->nEdges == pnEdges[i] );
        Nwk_ManGraphListInsert( p, p->pVerts[i] );
    }
    // clean up
    Aig_MmFixedStop( p->pMemEdges, 0 ); p->pMemEdges = NULL;
    ABC_FREE( p->pEdgeHash );
//    p->nEdgeHash = 0;
    ABC_FREE( pnEdges );
}

/**Function*************************************************************

  Synopsis    [Sort pairs by the first vertex in the topological order.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
void Nwk_ManGraphSortPairs( Nwk_Grf_t * p )
{
    int nSize = Vec_IntSize(p->vPairs);
    int * pIdToPair, i;
    // allocate storage
    pIdToPair = ABC_ALLOC( int, p->nObjs+1 );
    for ( i = 0; i <= p->nObjs; i++ )
        pIdToPair[i] = -1;
    // create mapping
    for ( i = 0; i < p->vPairs->nSize; i += 2 )
    {
        assert( pIdToPair[ p->vPairs->pArray[i] ] == -1 );
        pIdToPair[ p->vPairs->pArray[i] ] = p->vPairs->pArray[i+1];
    }
    // recreate pairs
    Vec_IntClear( p->vPairs );
    for ( i = 0; i <= p->nObjs; i++ )
        if ( pIdToPair[i] >= 0 )
        {
            assert( i < pIdToPair[i] );
            Vec_IntPush( p->vPairs, i );
            Vec_IntPush( p->vPairs, pIdToPair[i] );
        }
    assert( nSize == Vec_IntSize(p->vPairs) );
    ABC_FREE( pIdToPair );
}


/**Function*************************************************************

  Synopsis    [Updates the problem after pulling out one edge.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
void Nwk_ManGraphCheckLists( Nwk_Grf_t * p )
{
    Nwk_Vrt_t * pVertex, * pNext;
    int i, j;
    assert( p->pLists1[0] == 0 );
    for ( i = 1; i <= NWK_MAX_LIST; i++ )
        if ( p->pLists1[i] )
        {
            pVertex = p->pVerts[ p->pLists1[i] ];
            assert( pVertex->nEdges == 1 );
            pNext = p->pVerts[ pVertex->pEdges[0] ];
            assert( pNext->nEdges == i || pNext->nEdges > NWK_MAX_LIST );
        }
    // find the next vertext to extract
    assert( p->pLists2[0] == 0 );
    assert( p->pLists2[1] == 0 );
    for ( j = 2; j <= NWK_MAX_LIST; j++ )
        if ( p->pLists2[j] )
        {
            pVertex = p->pVerts[ p->pLists2[j] ];
            assert( pVertex->nEdges == j || pVertex->nEdges > NWK_MAX_LIST );
        }
}

/**Function*************************************************************

  Synopsis    [Extracts the edge from one of the linked lists.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
static inline void Nwk_ManGraphVertexRemoveEdge( Nwk_Vrt_t * pThis, Nwk_Vrt_t * pNext )
{
    int k;
    for ( k = 0; k < pThis->nEdges; k++ )
        if ( pThis->pEdges[k] == pNext->Id )
            break;
    assert( k < pThis->nEdges );
    pThis->nEdges--;
    for ( ; k < pThis->nEdges; k++ )
        pThis->pEdges[k] = pThis->pEdges[k+1];
}

/**Function*************************************************************

  Synopsis    [Updates the problem after pulling out one edge.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
void Nwk_ManGraphUpdate( Nwk_Grf_t * p, Nwk_Vrt_t * pVertex, Nwk_Vrt_t * pNext )
{
    Nwk_Vrt_t * pChanged, * pOther;
    int i, k;
//    Nwk_ManGraphCheckLists( p );
    Nwk_ManGraphListExtract( p, pVertex );
    Nwk_ManGraphListExtract( p, pNext );
    // update neihbors of pVertex
    Nwk_VertexForEachAdjacent( p, pVertex, pChanged, i )
    {
        if ( pChanged == pNext )
            continue;
        Nwk_ManGraphListExtract( p, pChanged );
        // move those that use this one
        if ( pChanged->nEdges > 1 )
        Nwk_VertexForEachAdjacent( p, pChanged, pOther, k )
        {
            if ( pOther == pVertex || pOther->nEdges > 1 )
                continue;
            assert( pOther->nEdges == 1 );
            Nwk_ManGraphListExtract( p, pOther );
            pChanged->nEdges--;
            Nwk_ManGraphListInsert( p, pOther );
            pChanged->nEdges++;
        }
        // remove the edge
        Nwk_ManGraphVertexRemoveEdge( pChanged, pVertex );
        // add the changed vertex back
        if ( pChanged->nEdges > 0 )
            Nwk_ManGraphListInsert( p, pChanged );
    }
    // update neihbors of pNext
    Nwk_VertexForEachAdjacent( p, pNext, pChanged, i )
    {
        if ( pChanged == pVertex )
            continue;
        Nwk_ManGraphListExtract( p, pChanged );
        // move those that use this one
        if ( pChanged->nEdges > 1 )
        Nwk_VertexForEachAdjacent( p, pChanged, pOther, k )
        {
            if ( pOther == pNext || pOther->nEdges > 1 )
                continue;
            assert( pOther->nEdges == 1 );
            Nwk_ManGraphListExtract( p, pOther );
            pChanged->nEdges--;
            Nwk_ManGraphListInsert( p, pOther );
            pChanged->nEdges++;
        }
        // remove the edge
        Nwk_ManGraphVertexRemoveEdge( pChanged, pNext );
        // add the changed vertex back
        if ( pChanged->nEdges > 0 )
            Nwk_ManGraphListInsert( p, pChanged );
    }
    // add to the result
    if ( pVertex->Id < pNext->Id )
    {
        Vec_IntPush( p->vPairs, p->pMapId2Lut[pVertex->Id] ); 
        Vec_IntPush( p->vPairs, p->pMapId2Lut[pNext->Id] ); 
    }
    else
    {
        Vec_IntPush( p->vPairs, p->pMapId2Lut[pNext->Id] ); 
        Vec_IntPush( p->vPairs, p->pMapId2Lut[pVertex->Id] ); 
    }
//    Nwk_ManGraphCheckLists( p );
}

/**Function*************************************************************

  Synopsis    [Counts the number of entries in the list.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
int Nwk_ManGraphListLength( Nwk_Grf_t * p, int List )
{
    Nwk_Vrt_t * pThis;
    int fVerbose = 0;
    int Counter = 0;
    Nwk_ListForEachVertex( p, List, pThis )
    {
        if ( fVerbose && Counter < 20 )
            printf( "%d ", p->pVerts[pThis->pEdges[0]]->nEdges );
        Counter++;
    }
    if ( fVerbose )
        printf( "\n" );
    return Counter;
}

/**Function*************************************************************

  Synopsis    [Returns the adjacent vertex with the mininum number of edges.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
Nwk_Vrt_t * Nwk_ManGraphListFindMinEdge( Nwk_Grf_t * p, Nwk_Vrt_t * pVert )
{
    Nwk_Vrt_t * pThis, * pMinCost = NULL;
    int k;
    Nwk_VertexForEachAdjacent( p, pVert, pThis, k )
    {
        if ( pMinCost == NULL || pMinCost->nEdges > pThis->nEdges )
            pMinCost = pThis;
    }
    return pMinCost;
}

/**Function*************************************************************

  Synopsis    [Finds the best vertext in the list.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
Nwk_Vrt_t * Nwk_ManGraphListFindMin( Nwk_Grf_t * p, int List )
{
    Nwk_Vrt_t * pThis, * pMinCost = NULL;
    int k, Counter = 10000, BestCost = 1000000;
    Nwk_ListForEachVertex( p, List, pThis )
    {
        for ( k = 0; k < pThis->nEdges; k++ )
        {
            if ( pMinCost == NULL || BestCost > p->pVerts[pThis->pEdges[k]]->nEdges )
            {
                BestCost = p->pVerts[pThis->pEdges[k]]->nEdges;
                pMinCost = pThis;
            }
        }
        if ( --Counter == 0 )
            break;
    }
    return pMinCost;
}

/**Function*************************************************************

  Synopsis    [Solves the problem by extracting one edge at a time.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
void Nwk_ManGraphSolve( Nwk_Grf_t * p )
{
    Nwk_Vrt_t * pVertex, * pNext;
    int i, j;
    Nwk_ManGraphPrepare( p );
    while ( 1 )
    {
        // find the next vertex to extract
        assert( p->pLists1[0] == 0 );
        for ( i = 1; i <= NWK_MAX_LIST; i++ )
            if ( p->pLists1[i] )
            {
//                printf( "%d ", i );
//                printf( "ListA = %2d. Length = %5d.\n", i, Nwk_ManGraphListLength(p,p->pLists1[i]) );
                pVertex = p->pVerts[ p->pLists1[i] ];
                assert( pVertex->nEdges == 1 );
                pNext = p->pVerts[ pVertex->pEdges[0] ];
                Nwk_ManGraphUpdate( p, pVertex, pNext );
                break;
            }
        if ( i < NWK_MAX_LIST + 1 )
            continue;
        // find the next vertex to extract
        assert( p->pLists2[0] == 0 );
        assert( p->pLists2[1] == 0 );
        for ( j = 2; j <= NWK_MAX_LIST; j++ )
            if ( p->pLists2[j] )
            {
//                printf( "***%d ", j );
//                printf( "ListB = %2d. Length = %5d.\n", j, Nwk_ManGraphListLength(p,p->pLists2[j]) );
                pVertex = Nwk_ManGraphListFindMin( p, p->pLists2[j] ); 
                assert( pVertex->nEdges == j || j == NWK_MAX_LIST );
                pNext = Nwk_ManGraphListFindMinEdge( p, pVertex );
                Nwk_ManGraphUpdate( p, pVertex, pNext );
                break;
            }
        if ( j == NWK_MAX_LIST + 1 )
            break;
    }
    Nwk_ManGraphSortPairs( p );
}

/**Function*************************************************************

  Synopsis    [Reads graph from file.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
Nwk_Grf_t * Nwk_ManLutMergeReadGraph( char * pFileName )
{
    Nwk_Grf_t * p;
    FILE * pFile;
    char Buffer[100];
    int nNodes, nEdges, iNode1, iNode2;
    int RetValue;
    pFile = fopen( pFileName, "r" );
    RetValue = fscanf( pFile, "%s %d", Buffer, &nNodes );
    RetValue = fscanf( pFile, "%s %d", Buffer, &nEdges );
    p = Nwk_ManGraphAlloc( nNodes );
    while ( fscanf( pFile, "%s %d %d", Buffer, &iNode1, &iNode2 ) == 3 )
        Nwk_ManGraphHashEdge( p, iNode1, iNode2 );
    assert( p->nEdges == nEdges );
    fclose( pFile );
    return p;
}

/**Function*************************************************************

  Synopsis    [Solves the graph coming from file.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
int Nwk_ManLutMergeGraphTest( char * pFileName )
{
    int nPairs;
    Nwk_Grf_t * p;
    abctime clk = Abc_Clock();
    p = Nwk_ManLutMergeReadGraph( pFileName );
    ABC_PRT( "Reading", Abc_Clock() - clk );
    clk = Abc_Clock();
    Nwk_ManGraphSolve( p );
    printf( "GRAPH: Nodes = %6d. Edges = %6d.  Pairs = %6d.  ", 
        p->nVerts, p->nEdges, Vec_IntSize(p->vPairs)/2 );
    ABC_PRT( "Solving", Abc_Clock() - clk );
    nPairs = Vec_IntSize(p->vPairs)/2;
    Nwk_ManGraphReportMemoryUsage( p );
    Nwk_ManGraphFree( p );
    return nPairs;
}




/**Function*************************************************************

  Synopsis    [Marks the fanins of the node with the current trav ID.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
void Nwk_ManMarkFanins_rec( Nwk_Obj_t * pLut, int nLevMin )
{
    Nwk_Obj_t * pNext;
    int i;
    if ( !Nwk_ObjIsNode(pLut) )
        return;
    if ( Nwk_ObjIsTravIdCurrent( pLut ) )
        return;
    Nwk_ObjSetTravIdCurrent( pLut );
    if ( Nwk_ObjLevel(pLut) < nLevMin )
        return;
    Nwk_ObjForEachFanin( pLut, pNext, i )
        Nwk_ManMarkFanins_rec( pNext, nLevMin );
}

/**Function*************************************************************

  Synopsis    [Marks the fanouts of the node with the current trav ID.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
void Nwk_ManMarkFanouts_rec( Nwk_Obj_t * pLut, int nLevMax, int nFanMax )
{
    Nwk_Obj_t * pNext;
    int i;
    if ( !Nwk_ObjIsNode(pLut) )
        return;
    if ( Nwk_ObjIsTravIdCurrent( pLut ) )
        return;
    Nwk_ObjSetTravIdCurrent( pLut );
    if ( Nwk_ObjLevel(pLut) > nLevMax )
        return;
    if ( Nwk_ObjFanoutNum(pLut) > nFanMax )
        return;
    Nwk_ObjForEachFanout( pLut, pNext, i )
        Nwk_ManMarkFanouts_rec( pNext, nLevMax, nFanMax );
}

/**Function*************************************************************

  Synopsis    [Collects the circle of nodes around the given set.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
void Nwk_ManCollectCircle( Vec_Ptr_t * vStart, Vec_Ptr_t * vNext, int nFanMax )
{
    Nwk_Obj_t * pObj, * pNext;
    int i, k;
    Vec_PtrClear( vNext );
    Vec_PtrForEachEntry( Nwk_Obj_t *, vStart, pObj, i )
    {
        Nwk_ObjForEachFanin( pObj, pNext, k )
        {
            if ( !Nwk_ObjIsNode(pNext) )
                continue;
            if ( Nwk_ObjIsTravIdCurrent( pNext ) )
                continue;
            Nwk_ObjSetTravIdCurrent( pNext );
            Vec_PtrPush( vNext, pNext );
        }
        Nwk_ObjForEachFanout( pObj, pNext, k )
        {
            if ( !Nwk_ObjIsNode(pNext) )
                continue;
            if ( Nwk_ObjIsTravIdCurrent( pNext ) )
                continue;
            Nwk_ObjSetTravIdCurrent( pNext );
            if ( Nwk_ObjFanoutNum(pNext) > nFanMax )
                continue;
            Vec_PtrPush( vNext, pNext );
        }
    }
}

/**Function*************************************************************

  Synopsis    [Collects the circle of nodes removes from the given one.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
void Nwk_ManCollectNonOverlapCands( Nwk_Obj_t * pLut, Vec_Ptr_t * vStart, Vec_Ptr_t * vNext, Vec_Ptr_t * vCands, Nwk_LMPars_t * pPars )
{
    Vec_Ptr_t * vTemp;
    Nwk_Obj_t * pObj;
    int i, k;
    Vec_PtrClear( vCands );
    if ( pPars->nMaxSuppSize - Nwk_ObjFaninNum(pLut) <= 1 )
        return;

    // collect nodes removed by this distance
    assert( pPars->nMaxDistance > 0 );
    Vec_PtrClear( vStart );
    Vec_PtrPush( vStart, pLut );
    Nwk_ManIncrementTravId( pLut->pMan );
    Nwk_ObjSetTravIdCurrent( pLut );
    for ( i = 1; i <= pPars->nMaxDistance; i++ )
    {
        Nwk_ManCollectCircle( vStart, vNext, pPars->nMaxFanout );
        vTemp  = vStart;
        vStart = vNext;
        vNext  = vTemp;
        // collect the nodes in vStart
        Vec_PtrForEachEntry( Nwk_Obj_t *, vStart, pObj, k )
            Vec_PtrPush( vCands, pObj );
    }

    // mark the TFI/TFO nodes
    Nwk_ManIncrementTravId( pLut->pMan );
    if ( pPars->fUseTfiTfo )
        Nwk_ObjSetTravIdCurrent( pLut );
    else
    {
        Nwk_ObjSetTravIdPrevious( pLut );
        Nwk_ManMarkFanins_rec( pLut, Nwk_ObjLevel(pLut) - pPars->nMaxDistance );
        Nwk_ObjSetTravIdPrevious( pLut );
        Nwk_ManMarkFanouts_rec( pLut, Nwk_ObjLevel(pLut) + pPars->nMaxDistance, pPars->nMaxFanout );
    }

    // collect nodes satisfying the following conditions:
    // - they are close enough in terms of distance
    // - they are not in the TFI/TFO of the LUT
    // - they have no more than the given number of fanins
    // - they have no more than the given diff in delay
    k = 0;
    Vec_PtrForEachEntry( Nwk_Obj_t *, vCands, pObj, i )
    {
        if ( Nwk_ObjIsTravIdCurrent(pObj) )
            continue;
        if ( Nwk_ObjFaninNum(pLut) + Nwk_ObjFaninNum(pObj) > pPars->nMaxSuppSize )
            continue;
        if ( Nwk_ObjLevel(pLut) - Nwk_ObjLevel(pObj) > pPars->nMaxLevelDiff || 
             Nwk_ObjLevel(pObj) - Nwk_ObjLevel(pLut) > pPars->nMaxLevelDiff )
             continue;
        Vec_PtrWriteEntry( vCands, k++, pObj );
    }
    Vec_PtrShrink( vCands, k );
}


/**Function*************************************************************

  Synopsis    [Count the total number of fanins.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
int Nwk_ManCountTotalFanins( Nwk_Obj_t * pLut, Nwk_Obj_t * pCand )
{
    Nwk_Obj_t * pFanin;
    int i, nCounter = Nwk_ObjFaninNum(pLut);
    Nwk_ObjForEachFanin( pCand, pFanin, i )
        nCounter += !pFanin->MarkC;
    return nCounter;
}

/**Function*************************************************************

  Synopsis    [Collects overlapping candidates.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
void Nwk_ManCollectOverlapCands( Nwk_Obj_t * pLut, Vec_Ptr_t * vCands, Nwk_LMPars_t * pPars )
{
    Nwk_Obj_t * pFanin, * pObj;
    int i, k;
    // mark fanins of pLut
    Nwk_ObjForEachFanin( pLut, pFanin, i )
        pFanin->MarkC = 1;
    // collect the matching fanouts of each fanin of the node
    Vec_PtrClear( vCands );
    Nwk_ManIncrementTravId( pLut->pMan );
    Nwk_ObjSetTravIdCurrent( pLut );
    Nwk_ObjForEachFanin( pLut, pFanin, i )
    {
        if ( !Nwk_ObjIsNode(pFanin) )
            continue;
        if ( Nwk_ObjFanoutNum(pFanin) > pPars->nMaxFanout )
            continue;
        Nwk_ObjForEachFanout( pFanin, pObj, k )
        {
            if ( !Nwk_ObjIsNode(pObj) )
                continue;
            if ( Nwk_ObjIsTravIdCurrent( pObj ) )
                continue;
            Nwk_ObjSetTravIdCurrent( pObj );
            // check the difference in delay
            if ( Nwk_ObjLevel(pLut) - Nwk_ObjLevel(pObj) > pPars->nMaxLevelDiff || 
                 Nwk_ObjLevel(pObj) - Nwk_ObjLevel(pLut) > pPars->nMaxLevelDiff )
                 continue;
            // check the total number of fanins of the node
            if ( Nwk_ManCountTotalFanins(pLut, pObj) > pPars->nMaxSuppSize )
                continue;
            Vec_PtrPush( vCands, pObj );
        }
    }
    // unmark fanins of pLut
    Nwk_ObjForEachFanin( pLut, pFanin, i )
        pFanin->MarkC = 0;
}

/**Function*************************************************************

  Synopsis    [Performs LUT merging with parameters.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
Vec_Int_t * Nwk_ManLutMerge( Nwk_Man_t * pNtk, void * pParsInit )
{
    Nwk_LMPars_t * pPars = (Nwk_LMPars_t *)pParsInit;
    Nwk_Grf_t * p;
    Vec_Int_t * vResult;
    Vec_Ptr_t * vStart, * vNext, * vCands1, * vCands2;
    Nwk_Obj_t * pLut, * pCand;
    int i, k, nVertsMax, nCands;
    abctime clk = Abc_Clock();
    // count the number of vertices
    nVertsMax = 0;
    Nwk_ManForEachNode( pNtk, pLut, i )
        nVertsMax += (int)(Nwk_ObjFaninNum(pLut) <= pPars->nMaxLutSize);
    p = Nwk_ManGraphAlloc( nVertsMax );
    // create graph
    vStart  = Vec_PtrAlloc( 1000 );
    vNext   = Vec_PtrAlloc( 1000 );
    vCands1 = Vec_PtrAlloc( 1000 );
    vCands2 = Vec_PtrAlloc( 1000 );
    nCands  = 0;
    Nwk_ManForEachNode( pNtk, pLut, i )
    {
        if ( Nwk_ObjFaninNum(pLut) > pPars->nMaxLutSize )
            continue;
        Nwk_ManCollectOverlapCands( pLut, vCands1, pPars );
        if ( pPars->fUseDiffSupp )
            Nwk_ManCollectNonOverlapCands( pLut, vStart, vNext, vCands2, pPars );
        if ( Vec_PtrSize(vCands1) == 0 && Vec_PtrSize(vCands2) == 0 )
            continue;
        nCands += Vec_PtrSize(vCands1) + Vec_PtrSize(vCands2);
        // save candidates
        Vec_PtrForEachEntry( Nwk_Obj_t *, vCands1, pCand, k )
            Nwk_ManGraphHashEdge( p, Nwk_ObjId(pLut), Nwk_ObjId(pCand) );
        Vec_PtrForEachEntry( Nwk_Obj_t *, vCands2, pCand, k )
            Nwk_ManGraphHashEdge( p, Nwk_ObjId(pLut), Nwk_ObjId(pCand) );
        // print statistics about this node
        if ( pPars->fVeryVerbose )
        printf( "Node %6d : Fanins = %d. Fanouts = %3d.  Cand1 = %3d. Cand2 = %3d.\n",
            Nwk_ObjId(pLut), Nwk_ObjFaninNum(pLut), Nwk_ObjFaninNum(pLut), 
            Vec_PtrSize(vCands1), Vec_PtrSize(vCands2) );
    }
    Vec_PtrFree( vStart );
    Vec_PtrFree( vNext );
    Vec_PtrFree( vCands1 );
    Vec_PtrFree( vCands2 );
    if ( pPars->fVerbose )
    {
        printf( "Mergable LUTs = %6d. Total cands = %6d. ", p->nVertsMax, nCands );
        ABC_PRT( "Deriving graph", Abc_Clock() - clk );
    }
    // solve the graph problem
    clk = Abc_Clock();
    Nwk_ManGraphSolve( p );
    if ( pPars->fVerbose )
    {
        printf( "GRAPH: Nodes = %6d. Edges = %6d.  Pairs = %6d.  ", 
            p->nVerts, p->nEdges, Vec_IntSize(p->vPairs)/2 );
        ABC_PRT( "Solving", Abc_Clock() - clk );
        Nwk_ManGraphReportMemoryUsage( p );
    }
    vResult = p->vPairs; p->vPairs = NULL;
/*
    for ( i = 0; i < vResult->nSize; i += 2 )
        printf( "(%d,%d) ", vResult->pArray[i], vResult->pArray[i+1] );
    printf( "\n" );
*/
    Nwk_ManGraphFree( p );
    return vResult;
}

////////////////////////////////////////////////////////////////////////
///                       END OF FILE                                ///
////////////////////////////////////////////////////////////////////////


ABC_NAMESPACE_IMPL_END