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

  FileName    [giaStr.c]

  SystemName  [ABC: Logic synthesis and verification system.]

  PackageName [Scalable AIG package.]

  Synopsis    [AIG structuring.]

  Author      [Alan Mishchenko]
  
  Affiliation [UC Berkeley]

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

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

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

#include "gia.h"
#include "misc/util/utilNam.h"
#include "misc/vec/vecWec.h"
#include "misc/tim/tim.h"

ABC_NAMESPACE_IMPL_START

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

#define STR_SUPER  100
#define MAX_TREE 10000

enum { 
    STR_NONE   =  0,
    STR_CONST0 =  1,
    STR_PI     =  2,
    STR_AND    =  3,
    STR_XOR    =  4,
    STR_MUX    =  5,
    STR_BUF    =  6,
    STR_PO     =  7,
    STR_UNUSED =  8      
};

typedef struct Str_Obj_t_ Str_Obj_t; 
struct Str_Obj_t_
{
    unsigned       Type    :  4;     // object type
    unsigned       nFanins : 28;     // fanin count
    int            iOffset;          // place where fanins are stored
    int            iTop;             // top level MUX
    int            iCopy;            // copy of this node
};
typedef struct Str_Ntk_t_ Str_Ntk_t; 
struct Str_Ntk_t_
{
    int            nObjs;            // object count
    int            nObjsAlloc;       // alloc objects
    Str_Obj_t *    pObjs;            // objects 
    Vec_Int_t      vFanins;          // object fanins
    int            nObjCount[STR_UNUSED];
    int            nTrees;
    int            nGroups;
    int            DelayGain;
};
typedef struct Str_Man_t_ Str_Man_t; 
struct Str_Man_t_
{
    // user data
    Gia_Man_t *     pOld;            // manager
    int             nLutSize;        // LUT size
    int             fCutMin;         // cut minimization
    // internal data
    Str_Ntk_t *     pNtk;            // balanced network
    // AIG under construction
    Gia_Man_t *     pNew;            // newly constructed 
    Vec_Int_t *     vDelays;         // delays of each object
};

static inline Str_Obj_t * Str_NtkObj( Str_Ntk_t * p, int i )                         { assert( i < p->nObjs );  return p->pObjs + i;                                        }
static inline int         Str_ObjFaninId( Str_Ntk_t * p, Str_Obj_t * pObj, int i )   { return Abc_Lit2Var( Vec_IntEntry(&p->vFanins, pObj->iOffset + i) );                  }
static inline Str_Obj_t * Str_ObjFanin( Str_Ntk_t * p, Str_Obj_t * pObj, int i )     { return Str_NtkObj( p, Str_ObjFaninId(p, pObj, i) );                                  }
static inline int         Str_ObjFaninC( Str_Ntk_t * p, Str_Obj_t * pObj, int i )    { return Abc_LitIsCompl( Vec_IntEntry(&p->vFanins, pObj->iOffset + i) );               }
static inline int         Str_ObjFaninCopy( Str_Ntk_t * p, Str_Obj_t * pObj, int i ) { return Abc_LitNotCond( Str_ObjFanin(p, pObj, i)->iCopy, Str_ObjFaninC(p, pObj, i) ); }
static inline int         Str_ObjId( Str_Ntk_t * p, Str_Obj_t * pObj )               { return pObj - p->pObjs;                                                              }

#define Str_NtkManForEachObj( p, pObj )  \
    for ( pObj = p->pObjs; Str_ObjId(p, pObj) < p->nObjs; pObj++ )
#define Str_NtkManForEachObjVec( vVec, p, pObj, i )  \
    for ( i = 0; (i < Vec_IntSize(vVec)) && ((pObj) = Str_NtkObj(p, Vec_IntEntry(vVec,i))); i++ )

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

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

  Synopsis    [Logic network manipulation.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
static inline int Str_ObjCreate( Str_Ntk_t * p, int Type, int nFanins, int * pFanins )
{
    Str_Obj_t * pObj = p->pObjs + p->nObjs; int i;
    assert( p->nObjs < p->nObjsAlloc );
    pObj->Type    = Type;
    pObj->nFanins = nFanins;
    pObj->iOffset = Vec_IntSize(&p->vFanins);
    pObj->iTop    = pObj->iCopy = -1;
    for ( i = 0; i < nFanins; i++ )
    {
        Vec_IntPush( &p->vFanins, pFanins[i] );
        assert( pFanins[i] >= 0 );
    }
    p->nObjCount[Type]++;
    return Abc_Var2Lit( p->nObjs++, 0 );
}
static inline Str_Ntk_t * Str_NtkCreate( int nObjsAlloc, int nFaninsAlloc )
{
    Str_Ntk_t * p;
    p = ABC_CALLOC( Str_Ntk_t, 1 );
    p->pObjs = ABC_ALLOC( Str_Obj_t, nObjsAlloc );
    p->nObjsAlloc = nObjsAlloc;
    Str_ObjCreate( p, STR_CONST0, 0, NULL );
    Vec_IntGrow( &p->vFanins, nFaninsAlloc );
    return p;
}
static inline void Str_NtkDelete( Str_Ntk_t * p )
{
//    printf( "Total delay gain = %d.\n", p->DelayGain );
    ABC_FREE( p->vFanins.pArray );
    ABC_FREE( p->pObjs );
    ABC_FREE( p );
}
static inline void Str_NtkPs( Str_Ntk_t * p, abctime clk )
{
    printf( "Network contains %d ands, %d xors, %d muxes (%d trees in %d groups).  ", 
        p->nObjCount[STR_AND], p->nObjCount[STR_XOR], p->nObjCount[STR_MUX], p->nTrees, p->nGroups );
    Abc_PrintTime( 1, "Time", clk );
}
static inline void Str_ObjReadGroup( Str_Ntk_t * p, Str_Obj_t * pObj, int * pnGroups, int * pnMuxes )
{
    Str_Obj_t * pObj1, * pObj2;
    *pnGroups = *pnMuxes = 0;
    if ( pObj->iTop == 0 )
        return;
    pObj1 = Str_NtkObj( p, pObj->iTop ); 
    pObj2 = Str_NtkObj( p, pObj1->iTop );
    *pnMuxes  = pObj1 - pObj + 1;
    *pnGroups = (pObj2 - pObj + 1) / *pnMuxes;
}
static inline void Str_NtkPrintGroups( Str_Ntk_t * p )
{
    Str_Obj_t * pObj; 
    int nGroups, nMuxes;
    Str_NtkManForEachObj( p, pObj )
        if ( pObj->Type == STR_MUX && pObj->iTop > 0 )
        {
            Str_ObjReadGroup( p, pObj, &nGroups, &nMuxes );
            pObj += nGroups * nMuxes - 1;
            printf( "%d x %d  ", nGroups, nMuxes );
        }
    printf( "\n" );
}
Gia_Man_t * Str_NtkToGia( Gia_Man_t * pGia, Str_Ntk_t * p )
{
    Gia_Man_t * pNew, * pTemp;
    Str_Obj_t * pObj; int k;
    assert( pGia->pMuxes == NULL );
    pNew = Gia_ManStart( 3 * Gia_ManObjNum(pGia) / 2 );
    pNew->pName = Abc_UtilStrsav( pGia->pName );
    pNew->pSpec = Abc_UtilStrsav( pGia->pSpec );
    Gia_ManHashStart( pNew );
    Str_NtkManForEachObj( p, pObj )
    {
        if ( pObj->Type == STR_PI )
            pObj->iCopy = Gia_ManAppendCi( pNew );
        else if ( pObj->Type == STR_AND )
        {
            pObj->iCopy = 1;
            for ( k = 0; k < (int)pObj->nFanins; k++ )
                pObj->iCopy = Gia_ManHashAnd( pNew, pObj->iCopy, Str_ObjFaninCopy(p, pObj, k) );
        }
        else if ( pObj->Type == STR_XOR )
        {
            pObj->iCopy = 0;
            for ( k = 0; k < (int)pObj->nFanins; k++ )
                pObj->iCopy = Gia_ManHashXor( pNew, pObj->iCopy, Str_ObjFaninCopy(p, pObj, k) );
        }
        else if ( pObj->Type == STR_MUX )
            pObj->iCopy = Gia_ManHashMux( pNew, Str_ObjFaninCopy(p, pObj, 2), Str_ObjFaninCopy(p, pObj, 1), Str_ObjFaninCopy(p, pObj, 0) );
        else if ( pObj->Type == STR_PO )
            pObj->iCopy = Gia_ManAppendCo( pNew, Str_ObjFaninCopy(p, pObj, 0) );
        else if ( pObj->Type == STR_CONST0 )
            pObj->iCopy = 0;
        else assert( 0 );
    }
    Gia_ManHashStop( pNew );
//    assert( Gia_ManObjNum(pNew) <= Gia_ManObjNum(pGia) );
    Gia_ManSetRegNum( pNew, Gia_ManRegNum(pGia) );
    pNew = Gia_ManCleanup( pTemp = pNew );
    Gia_ManStop( pTemp );
    return pNew;
}


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

  Synopsis    [Constructs a normalized AIG without structural hashing.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
Gia_Man_t * Gia_ManDupMuxesNoHash( Gia_Man_t * p )
{
    Gia_Man_t * pNew;
    Gia_Obj_t * pObj, * pFan0, * pFan1, * pFanC;
    int i, iLit0, iLit1, fCompl;
    assert( p->pMuxes == NULL );
    ABC_FREE( p->pRefs );
    Gia_ManCreateRefs( p ); 
    // discount nodes with one fanout pointed to by MUX type
    Gia_ManForEachAnd( p, pObj, i )
    {
        if ( !Gia_ObjIsMuxType(pObj) )
            continue;
        Gia_ObjRefDec(p, Gia_ObjFanin0(pObj));
        Gia_ObjRefDec(p, Gia_ObjFanin1(pObj));
    }
    // start the new manager
    pNew = Gia_ManStart( Gia_ManObjNum(p) );
    pNew->pName  = Abc_UtilStrsav( p->pName );
    pNew->pSpec  = Abc_UtilStrsav( p->pSpec );
    pNew->pMuxes = ABC_CALLOC( unsigned, pNew->nObjsAlloc );
    Gia_ManFillValue(p);
    Gia_ManConst0(p)->Value = 0;
    Gia_ManForEachCi( p, pObj, i )
        pObj->Value = Gia_ManAppendCi( pNew );
    Gia_ManForEachAnd( p, pObj, i )
    {
        if ( !Gia_ObjRefNumId(p, i) )
            continue;
        if ( !Gia_ObjIsMuxType(pObj) )
            pObj->Value = Gia_ManAppendAnd( pNew, Gia_ObjFanin0Copy(pObj), Gia_ObjFanin1Copy(pObj) );
        else if ( Gia_ObjRecognizeExor(pObj, &pFan0, &pFan1) )
        {
            iLit0 = Gia_ObjLitCopy(p, Gia_ObjToLit(p, pFan0));
            iLit1 = Gia_ObjLitCopy(p, Gia_ObjToLit(p, pFan1));
            fCompl = Abc_LitIsCompl(iLit0) ^ Abc_LitIsCompl(iLit1);
            pObj->Value = fCompl ^ Gia_ManAppendXorReal( pNew, Abc_LitRegular(iLit0), Abc_LitRegular(iLit1) );
        }
        else
        {
            pFanC = Gia_ObjRecognizeMux( pObj, &pFan1, &pFan0 );
            iLit0 = Gia_ObjLitCopy(p, Gia_ObjToLit(p, pFan0));
            iLit1 = Gia_ObjLitCopy(p, Gia_ObjToLit(p, pFan1));
            if ( iLit0 == iLit1 )
                pObj->Value = iLit0;
            else if ( Abc_Lit2Var(iLit0) == Abc_Lit2Var(iLit1) )
            {
                iLit1 = Gia_ObjLitCopy(p, Gia_ObjToLit(p, pFanC));
                fCompl = Abc_LitIsCompl(iLit0) ^ Abc_LitIsCompl(iLit1);
                pObj->Value = fCompl ^ Gia_ManAppendXorReal( pNew, Abc_LitRegular(iLit0), Abc_LitRegular(iLit1) );
            }
            else
                pObj->Value = Gia_ManAppendMuxReal( pNew, Gia_ObjLitCopy(p, Gia_ObjToLit(p, pFanC)), Gia_ObjLitCopy(p, Gia_ObjToLit(p, pFan1)), Gia_ObjLitCopy(p, Gia_ObjToLit(p, pFan0)) );
        }
    }
    Gia_ManForEachCo( p, pObj, i )
        pObj->Value = Gia_ManAppendCo( pNew, Gia_ObjFanin0Copy(pObj) );
    Gia_ManSetRegNum( pNew, Gia_ManRegNum(p) );
    assert( !Gia_ManHasDangling(pNew) );
    return pNew;
}

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

  Synopsis    [Constructs AIG ordered for balancing.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
void Str_MuxInputsCollect_rec( Gia_Man_t * p, Gia_Obj_t * pObj, Vec_Int_t * vNodes )
{
    if ( !pObj->fMark0 )
    {
        Vec_IntPush( vNodes, Gia_ObjId(p, pObj) );
        return;
    }
    Vec_IntPush( vNodes, Gia_ObjFaninId2p(p, pObj) );
    Str_MuxInputsCollect_rec( p, Gia_ObjFanin0(pObj), vNodes );
    Str_MuxInputsCollect_rec( p, Gia_ObjFanin1(pObj), vNodes );
}
void Str_MuxInputsCollect( Gia_Man_t * p, Gia_Obj_t * pObj, Vec_Int_t * vNodes )
{
    assert( !pObj->fMark0 );
    pObj->fMark0 = 1;
    Vec_IntClear( vNodes );
    Str_MuxInputsCollect_rec( p, pObj, vNodes );
    pObj->fMark0 = 0;
}
void Str_MuxStructCollect_rec( Gia_Man_t * p, Gia_Obj_t * pObj, Vec_Int_t * vNodes )
{
    if ( !pObj->fMark0 )
        return;
    Str_MuxStructCollect_rec( p, Gia_ObjFanin0(pObj), vNodes );
    Str_MuxStructCollect_rec( p, Gia_ObjFanin1(pObj), vNodes );
    Vec_IntPush( vNodes, Gia_ObjId(p, pObj) );
}
void Str_MuxStructCollect( Gia_Man_t * p, Gia_Obj_t * pObj, Vec_Int_t * vNodes )
{
    assert( !pObj->fMark0 );
    pObj->fMark0 = 1;
    Vec_IntClear( vNodes );
    Str_MuxStructCollect_rec( p, pObj, vNodes );
    pObj->fMark0 = 0;
}
void Str_MuxStructDump_rec( Gia_Man_t * p, Gia_Obj_t * pObj, Vec_Str_t * vStr )
{
    if ( !pObj->fMark0 )
        return;
    Vec_StrPush( vStr, '[' );
    Vec_StrPush( vStr, '(' );
    Vec_StrPrintNum( vStr, Gia_ObjFaninId2p(p, pObj) );
    Vec_StrPush( vStr, ')' );
    Str_MuxStructDump_rec( p, Gia_ObjFaninC2(p, pObj) ? Gia_ObjFanin0(pObj) : Gia_ObjFanin1(pObj), vStr );
    Vec_StrPush( vStr, '|' );
    Str_MuxStructDump_rec( p, Gia_ObjFaninC2(p, pObj) ? Gia_ObjFanin1(pObj) : Gia_ObjFanin0(pObj), vStr );
    Vec_StrPush( vStr, ']' );
}
void Str_MuxStructDump( Gia_Man_t * p, Gia_Obj_t * pObj, Vec_Str_t * vStr )
{
    assert( !pObj->fMark0 );
    pObj->fMark0 = 1;
    Vec_StrClear( vStr );
    Str_MuxStructDump_rec( p, pObj, vStr );
    Vec_StrPush( vStr, '\0' );
    pObj->fMark0 = 0;
}
int Str_ManMuxCountOne( char * p )
{
    int Count = 0;
    for ( ; *p; p++ )
        Count += (*p == '[');
    return Count;
}
Vec_Wec_t * Str_ManDeriveTrees( Gia_Man_t * p )
{
    int fPrintStructs = 0;
    Abc_Nam_t * pNames; 
    Vec_Wec_t * vGroups;  
    Vec_Str_t * vStr;
    Gia_Obj_t * pObj, * pFanin;
    int i, iStructId, fFound;
    assert( p->pMuxes != NULL );
    // mark MUXes whose only fanout is a MUX
    ABC_FREE( p->pRefs );
    Gia_ManCreateRefs( p ); 
    Gia_ManForEachMuxId( p, i )
    {
        pObj = Gia_ManObj(p, i);
        pFanin = Gia_ObjFanin0(pObj);
        if ( Gia_ObjIsMux(p, pFanin) && Gia_ObjRefNum(p, pFanin) == 1 )
            pFanin->fMark0 = 1;
        pFanin = Gia_ObjFanin1(pObj);
        if ( Gia_ObjIsMux(p, pFanin) && Gia_ObjRefNum(p, pFanin) == 1 )
            pFanin->fMark0 = 1;
    }
    // traverse for top level MUXes
    vStr   = Vec_StrAlloc( 1000 );
    pNames = Abc_NamStart( 10000, 50 );
    vGroups = Vec_WecAlloc( 1000 );
    Vec_WecPushLevel( vGroups );
    Gia_ManForEachMuxId( p, i )
    {
        // skip internal
        pObj = Gia_ManObj(p, i);
        if ( pObj->fMark0 )
            continue;
        // skip trees of size one
        if ( !Gia_ObjFanin0(pObj)->fMark0 && !Gia_ObjFanin1(pObj)->fMark0 )
            continue;
        // hash the tree
        Str_MuxStructDump( p, pObj, vStr );
        iStructId = Abc_NamStrFindOrAdd( pNames, Vec_StrArray(vStr), &fFound );
        if ( !fFound ) Vec_WecPushLevel( vGroups );
        assert( Abc_NamObjNumMax(pNames) == Vec_WecSize(vGroups) );
        Vec_IntPush( Vec_WecEntry(vGroups, iStructId), i );
    }
    if ( fPrintStructs )
    {
        char * pTemp; 
        Abc_NamManForEachObj( pNames, pTemp, i )
        {
            printf( "%5d : ", i );
            printf( "Occur = %4d   ", Vec_IntSize(Vec_WecEntry(vGroups,i)) );
            printf( "Size = %4d   ",  Str_ManMuxCountOne(pTemp) );
            printf( "%s\n", pTemp );
        }
    }
    Abc_NamStop( pNames );
    Vec_StrFree( vStr );
    return vGroups;
}
Vec_Int_t * Str_ManCreateRoots( Vec_Wec_t * vGroups, int nObjs )
{   // map tree MUXes into their classes
    Vec_Int_t * vRoots;
    Vec_Int_t * vGroup;
    int i, k, Entry;
    vRoots = Vec_IntStartFull( nObjs );
    Vec_WecForEachLevel( vGroups, vGroup, i )
        Vec_IntForEachEntry( vGroup, Entry, k )
            Vec_IntWriteEntry( vRoots, Entry, i );
    return vRoots;
}

void Str_MuxTraverse_rec( Gia_Man_t * p, int i )
{
    Gia_Obj_t * pObj;
    if ( Gia_ObjIsTravIdCurrentId(p, i) )
        return;
    Gia_ObjSetTravIdCurrentId(p, i);
    pObj = Gia_ManObj(p, i);
    if ( !Gia_ObjIsAnd(pObj) )
        return;
    Str_MuxTraverse_rec(p, Gia_ObjFaninId0(pObj, i) );
    Str_MuxTraverse_rec(p, Gia_ObjFaninId1(pObj, i) );
    if ( Gia_ObjIsMux(p, pObj) )
        Str_MuxTraverse_rec(p, Gia_ObjFaninId2(p, i) );
}
void Str_ManCheckOverlap( Gia_Man_t * p, Vec_Wec_t * vGroups )
{   // check that members of each group are not in the TFI of each other
    Vec_Int_t * vGroup, * vGroup2;
    int i, k, n, iObj, iObj2;

//    vGroup = Vec_WecEntry(vGroups, 7);
//    Vec_IntForEachEntry( vGroup, iObj, n )
//        Gia_ManPrintCone2( p, Gia_ManObj(p, iObj) ), printf( "\n" );

    Vec_WecForEachLevel( vGroups, vGroup, i )
    Vec_IntForEachEntry( vGroup, iObj, k )
    {
        if ( Vec_IntSize(vGroup) == 1 )
            continue;
        // high light the cone
        Gia_ManIncrementTravId( p );
        Str_MuxTraverse_rec( p, iObj );
        // check that none of the others are highlighted
        Vec_IntForEachEntry( vGroup, iObj2, n )
            if ( iObj != iObj2 && Gia_ObjIsTravIdCurrentId(p, iObj2) )
                break;
        if ( n == Vec_IntSize(vGroup) )
            continue;
        // split the group into individual trees
        Vec_IntForEachEntryStart( vGroup, iObj2, n, 1 )
        {
            vGroup2 = Vec_WecPushLevel( vGroups );
            vGroup  = Vec_WecEntry( vGroups, i );
            Vec_IntPush( vGroup2, iObj2 );
        }
        Vec_IntShrink( vGroup, 1 );

/*
        // this does not work because there can be a pair of independent trees
        // with another tree squeezed in between them, so that there is a combo loop

        // divide this group
        nNew = 0;
        vGroup2 = Vec_WecPushLevel( vGroups );
        vGroup  = Vec_WecEntry( vGroups, i );
        Vec_IntForEachEntry( vGroup, iObj2, n )
        {
            if ( iObj != iObj2 && Gia_ObjIsTravIdCurrentId(p, iObj2) )
                Vec_IntPush( vGroup2, iObj2 );
            else
                Vec_IntWriteEntry( vGroup, nNew++, iObj2 );
        }
        Vec_IntShrink( vGroup, nNew );
        i--;
        break;
*/

/*
        // check that none of the others are highlighted
        Vec_IntForEachEntry( vGroup, iObj, n )
            if ( n != k && Gia_ObjIsTravIdCurrentId(p, iObj) )
            {
                printf( "Overlap of TFI cones of trees %d and %d in group %d of size %d!\n", k, n, i, Vec_IntSize(vGroup) );
                Vec_IntShrink( vGroup, 1 );
                break;
            }
*/
    }
}

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

  Synopsis    [Simplify multi-input AND/XOR.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
static inline void Gia_ManSimplifyXor( Vec_Int_t * vSuper )
{
    int i, k = 0, Prev = -1, This, fCompl = 0;
    Vec_IntForEachEntry( vSuper, This, i )
    {
        if ( This == 0 )
            continue;
        if ( This == 1 )
            fCompl ^= 1; 
        else if ( Prev != This )
            Vec_IntWriteEntry( vSuper, k++, This ), Prev = This;
        else
            Prev = -1, k--;
    }
    Vec_IntShrink( vSuper, k );
    if ( Vec_IntSize( vSuper ) == 0 )
        Vec_IntPush( vSuper, fCompl );
    else if ( fCompl )
        Vec_IntWriteEntry( vSuper, 0, Abc_LitNot(Vec_IntEntry(vSuper, 0)) );
}
static inline void Gia_ManSimplifyAnd( Vec_Int_t * vSuper )
{
    int i, k = 0, Prev = -1, This;
    Vec_IntForEachEntry( vSuper, This, i )
    {
        if ( This == 0 )
            { Vec_IntFill(vSuper, 1, 0); return; }
        if ( This == 1 )
            continue;
        if ( Prev == -1 || Abc_Lit2Var(Prev) != Abc_Lit2Var(This) )
            Vec_IntWriteEntry( vSuper, k++, This ), Prev = This;
        else if ( Prev != This )
            { Vec_IntFill(vSuper, 1, 0); return; }
    }
    Vec_IntShrink( vSuper, k );
    if ( Vec_IntSize( vSuper ) == 0 )
        Vec_IntPush( vSuper, 1 );
}

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

  Synopsis    [Collect multi-input AND/XOR.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
static inline void Gia_ManSuperCollectXor_rec( Gia_Man_t * p, Gia_Obj_t * pObj )
{
    assert( !Gia_IsComplement(pObj) );
    if ( !Gia_ObjIsXor(pObj) || 
        Gia_ObjRefNum(p, pObj) > 1 || 
//        Gia_ObjRefNum(p, pObj) > 3 || 
//        (Gia_ObjRefNum(p, pObj) == 2 && (Gia_ObjRefNum(p, Gia_ObjFanin0(pObj)) == 1 || Gia_ObjRefNum(p, Gia_ObjFanin1(pObj)) == 1)) || 
        Vec_IntSize(p->vSuper) > STR_SUPER )
    {
        Vec_IntPush( p->vSuper, Gia_ObjToLit(p, pObj) );
        return;
    }
    assert( !Gia_ObjFaninC0(pObj) && !Gia_ObjFaninC1(pObj) );
    Gia_ManSuperCollectXor_rec( p, Gia_ObjFanin0(pObj) );
    Gia_ManSuperCollectXor_rec( p, Gia_ObjFanin1(pObj) );
}
static inline void Gia_ManSuperCollectAnd_rec( Gia_Man_t * p, Gia_Obj_t * pObj )
{
    if ( Gia_IsComplement(pObj) || 
        !Gia_ObjIsAndReal(p, pObj) || 
        Gia_ObjRefNum(p, pObj) > 1 || 
//        Gia_ObjRefNum(p, pObj) > 3 || 
//        (Gia_ObjRefNum(p, pObj) == 2 && (Gia_ObjRefNum(p, Gia_ObjFanin0(pObj)) == 1 || Gia_ObjRefNum(p, Gia_ObjFanin1(pObj)) == 1)) || 
        Vec_IntSize(p->vSuper) > STR_SUPER )
    {
        Vec_IntPush( p->vSuper, Gia_ObjToLit(p, pObj) );
        return;
    }
    Gia_ManSuperCollectAnd_rec( p, Gia_ObjChild0(pObj) );
    Gia_ManSuperCollectAnd_rec( p, Gia_ObjChild1(pObj) );
}
static inline void Gia_ManSuperCollect( Gia_Man_t * p, Gia_Obj_t * pObj )
{
    if ( p->vSuper == NULL )
        p->vSuper = Vec_IntAlloc( STR_SUPER );
    else
        Vec_IntClear( p->vSuper );
    if ( Gia_ObjIsXor(pObj) )
    {
        assert( !Gia_ObjFaninC0(pObj) && !Gia_ObjFaninC1(pObj) );
        Gia_ManSuperCollectXor_rec( p, Gia_ObjFanin0(pObj) );
        Gia_ManSuperCollectXor_rec( p, Gia_ObjFanin1(pObj) );
        Vec_IntSort( p->vSuper, 0 );
        Gia_ManSimplifyXor( p->vSuper );
    }
    else if ( Gia_ObjIsAndReal(p, pObj) )
    {
        Gia_ManSuperCollectAnd_rec( p, Gia_ObjChild0(pObj) );
        Gia_ManSuperCollectAnd_rec( p, Gia_ObjChild1(pObj) );
        Vec_IntSort( p->vSuper, 0 );
        Gia_ManSimplifyAnd( p->vSuper );
    }
    else assert( 0 );
    assert( Vec_IntSize(p->vSuper) > 0 );
}

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

  Synopsis    [Constructs AIG ordered for balancing.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
void Str_ManNormalize_rec( Str_Ntk_t * pNtk, Gia_Man_t * p, Gia_Obj_t * pObj, Vec_Wec_t * vGroups, Vec_Int_t * vRoots )
{
    int i, k, iVar, iLit, iBeg, iEnd;
    if ( ~pObj->Value )
        return;
    pObj->Value = 0;
    assert( Gia_ObjIsAnd(pObj) );
    if ( Gia_ObjIsMux(p, pObj) )
    {
        Vec_Int_t * vGroup;
        Gia_Obj_t * pRoot, * pMux;
        int pFanins[3];
        if ( Vec_IntEntry(vRoots, Gia_ObjId(p, pObj)) == -1 )
        {
            Str_ManNormalize_rec( pNtk, p, Gia_ObjFanin0(pObj), vGroups, vRoots );
            Str_ManNormalize_rec( pNtk, p, Gia_ObjFanin1(pObj), vGroups, vRoots );
            Str_ManNormalize_rec( pNtk, p, Gia_ObjFanin2(p, pObj), vGroups, vRoots );
            pFanins[0] = Gia_ObjFanin0Copy(pObj);
            pFanins[1] = Gia_ObjFanin1Copy(pObj);
            pFanins[2] = Gia_ObjFanin2Copy(p, pObj);
            if ( Abc_LitIsCompl(pFanins[2]) )
            {
                pFanins[2] = Abc_LitNot(pFanins[2]);
                ABC_SWAP( int, pFanins[0], pFanins[1] );
            }
            pObj->Value = Str_ObjCreate( pNtk, STR_MUX, 3, pFanins );
            return;
        }
        vGroup = Vec_WecEntry( vGroups, Vec_IntEntry(vRoots, Gia_ObjId(p, pObj)) );
        // build data-inputs for each tree
        Gia_ManForEachObjVec( vGroup, p, pRoot, i )
        {
            Str_MuxInputsCollect( p, pRoot, p->vSuper );
            iBeg = Vec_IntSize( p->vStore );
            Vec_IntAppend( p->vStore, p->vSuper );
            iEnd = Vec_IntSize( p->vStore );
            Vec_IntForEachEntryStartStop( p->vStore, iVar, k, iBeg, iEnd )
                Str_ManNormalize_rec( pNtk, p, Gia_ManObj(p, iVar), vGroups, vRoots );
            Vec_IntShrink( p->vStore, iBeg );
        }
        // build internal structures
        Gia_ManForEachObjVec( vGroup, p, pRoot, i )
        {
            Str_MuxStructCollect( p, pRoot, p->vSuper );
            Gia_ManForEachObjVec( p->vSuper, p, pMux, k )
            {
                pFanins[0] = Gia_ObjFanin0Copy(pMux);
                pFanins[1] = Gia_ObjFanin1Copy(pMux);
                pFanins[2] = Gia_ObjFanin2Copy(p, pMux);
                if ( Abc_LitIsCompl(pFanins[2]) )
                {
                    pFanins[2] = Abc_LitNot(pFanins[2]);
                    ABC_SWAP( int, pFanins[0], pFanins[1] );
                }
                pMux->Value = Str_ObjCreate( pNtk, STR_MUX, 3, pFanins );
            }
            assert( ~pRoot->Value );
            // set mapping
            Gia_ManForEachObjVec( p->vSuper, p, pMux, k )
                Str_NtkObj(pNtk, Abc_Lit2Var(pMux->Value))->iTop = Abc_Lit2Var(pRoot->Value);
            pNtk->nTrees++;
        }
        assert( ~pObj->Value );
        // set mapping
        pObj = Gia_ManObj( p, Vec_IntEntryLast(vGroup) );
        Gia_ManForEachObjVec( vGroup, p, pRoot, i )
            Str_NtkObj(pNtk, Abc_Lit2Var(pRoot->Value))->iTop = Abc_Lit2Var(pObj->Value);
        pNtk->nGroups++;
        //printf( "%d x %d  ", Vec_IntSize(vGroup), Vec_IntSize(p->vSuper) );
        return;
    }
    // find supergate
    Gia_ManSuperCollect( p, pObj );
    // save entries
    iBeg = Vec_IntSize( p->vStore );
    Vec_IntAppend( p->vStore, p->vSuper );
    iEnd = Vec_IntSize( p->vStore );
    // call recursively
    Vec_IntForEachEntryStartStop( p->vStore, iLit, i, iBeg, iEnd )
    {
        Gia_Obj_t * pTemp = Gia_ManObj( p, Abc_Lit2Var(iLit) );
        Str_ManNormalize_rec( pNtk, p, pTemp, vGroups, vRoots );
        Vec_IntWriteEntry( p->vStore, i, Abc_LitNotCond(pTemp->Value, Abc_LitIsCompl(iLit)) );
    }
    assert( Vec_IntSize(p->vStore) == iEnd );
    // consider general case
    pObj->Value = Str_ObjCreate( pNtk, Gia_ObjIsXor(pObj) ? STR_XOR : STR_AND, iEnd-iBeg, Vec_IntEntryP(p->vStore, iBeg) );
    Vec_IntShrink( p->vStore, iBeg );
}
Str_Ntk_t * Str_ManNormalizeInt( Gia_Man_t * p, Vec_Wec_t * vGroups, Vec_Int_t * vRoots )
{
    Str_Ntk_t * pNtk;
    Gia_Obj_t * pObj;
    int i, iFanin;
    assert( p->pMuxes != NULL );
    if ( p->vSuper == NULL )
        p->vSuper = Vec_IntAlloc( STR_SUPER );
    if ( p->vStore == NULL )
        p->vStore = Vec_IntAlloc( STR_SUPER );
    Gia_ManFillValue( p );
    pNtk = Str_NtkCreate( Gia_ManObjNum(p), 1 + Gia_ManCoNum(p) + 2 * Gia_ManAndNum(p) + Gia_ManMuxNum(p) );
    Gia_ManConst0(p)->Value = 0;
    Gia_ManForEachObj1( p, pObj, i )
    {
        if ( Gia_ObjIsCi(pObj) )
            pObj->Value = Str_ObjCreate( pNtk, STR_PI, 0, NULL );
        else if ( Gia_ObjIsCo(pObj) )
        {
            Str_ManNormalize_rec( pNtk, p, Gia_ObjFanin0(pObj), vGroups, vRoots );
            iFanin = Gia_ObjFanin0Copy(pObj);
            pObj->Value = Str_ObjCreate( pNtk, STR_PO, 1, &iFanin );
        }
    }
    assert( pNtk->nObjs <= Gia_ManObjNum(p) );
    return pNtk;
}
Str_Ntk_t * Str_ManNormalize( Gia_Man_t * p )
{
    Str_Ntk_t * pNtk;
    Gia_Man_t * pMuxes = Gia_ManDupMuxes( p, 5 );
    Vec_Wec_t * vGroups = Str_ManDeriveTrees( pMuxes );
    Vec_Int_t * vRoots;
    Str_ManCheckOverlap( pMuxes, vGroups );
    vRoots = Str_ManCreateRoots( vGroups, Gia_ManObjNum(pMuxes) );
    pNtk = Str_ManNormalizeInt( pMuxes, vGroups, vRoots );
    Gia_ManCleanMark0( pMuxes );
    Gia_ManStop( pMuxes );
    Vec_IntFree( vRoots );
    Vec_WecFree( vGroups );
    return pNtk;
}
 
/**Function*************************************************************

  Synopsis    [Delay computation]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
static inline int Str_Delay2( int d0, int d1, int nLutSize )
{
    int n, d = Abc_MaxInt( d0 >> 4, d1 >> 4 );
    n  = (d == (d0 >> 4)) ? (d0 & 15) : 1;
    n += (d == (d1 >> 4)) ? (d1 & 15) : 1;
    return (d << 4) + (n > nLutSize ? 18 : n);
}
static inline int Str_Delay3( int d0, int d1, int d2, int nLutSize )
{
    int n, d = Abc_MaxInt( Abc_MaxInt(d0 >> 4, d1 >> 4), d2 >> 4 );
    n  = (d == (d0 >> 4)) ? (d0 & 15) : 1;
    n += (d == (d1 >> 4)) ? (d1 & 15) : 1;
    n += (d == (d2 >> 4)) ? (d2 & 15) : 1;
    return (d << 4) + (n > nLutSize ? 19 : n);
}
static inline int Str_ObjDelay( Gia_Man_t * pNew, int iObj, int nLutSize, Vec_Int_t * vDelay )
{
    int Delay = Vec_IntEntry( vDelay, iObj );
    if ( Delay == 0 )
    {
        if ( Gia_ObjIsMuxId(pNew, iObj) )
        {
            int d0 = Vec_IntEntry( vDelay, Gia_ObjFaninId0(Gia_ManObj(pNew, iObj), iObj) );
            int d1 = Vec_IntEntry( vDelay, Gia_ObjFaninId1(Gia_ManObj(pNew, iObj), iObj) );
            int d2 = Vec_IntEntry( vDelay, Gia_ObjFaninId2(pNew, iObj) );
            Delay = Str_Delay3( d0, d1, d2, nLutSize );
        }
        else
        {
            int d0 = Vec_IntEntry( vDelay, Gia_ObjFaninId0(Gia_ManObj(pNew, iObj), iObj) );
            int d1 = Vec_IntEntry( vDelay, Gia_ObjFaninId1(Gia_ManObj(pNew, iObj), iObj) );
            Delay = Str_Delay2( d0, d1, nLutSize );
        }
        Vec_IntWriteEntry( vDelay, iObj, Delay );
    }
    return Delay;
}



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

  Synopsis    [Transposing 64-bit matrix.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
static inline void transpose64( word A[64] )
{
    int j, k;
    word t, m = 0x00000000FFFFFFFF;
    for ( j = 32; j != 0; j = j >> 1, m = m ^ (m << j) )
    {
        for ( k = 0; k < 64; k = (k + j + 1) & ~j )
        {
            t = (A[k] ^ (A[k+j] >> j)) & m;
            A[k] = A[k] ^ t;
            A[k+j] = A[k+j] ^ (t << j);
        }
    }
}

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

  Synopsis    [Perform affinity computation.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
static inline int  Str_ManNum( Gia_Man_t * p, int iObj )             { return Vec_IntEntry(&p->vCopies, iObj);     }
static inline void Str_ManSetNum( Gia_Man_t * p, int iObj, int Num ) { Vec_IntWriteEntry(&p->vCopies, iObj, Num);  }

int Str_ManVectorAffinity( Gia_Man_t * p, Vec_Int_t * vSuper, Vec_Int_t * vDelay, word Matrix[256], int nLimit )
{
    int fVerbose = 0;
    int Levels[256];
    int nSize = Vec_IntSize(vSuper);
    int Prev = nSize, nLevels = 1;
    int i, k, iLit, iFanin, nSizeNew;
    word Mask; 
    assert( nSize > 2 );
    if ( nSize > 64 )
    {
        for ( i = 0; i < 64; i++ )
            Matrix[i] = 0;
        return 0;
    }
    // mark current nodes
    Gia_ManIncrementTravId( p );
    Vec_IntForEachEntry( vSuper, iLit, i )
    {
        Gia_ObjSetTravIdCurrentId( p, Abc_Lit2Var(iLit) );
        Str_ManSetNum( p, Abc_Lit2Var(iLit), i );
        Matrix[i] = ((word)1) << (63-i);
        Levels[i] = 0;
    }
    // collect 64 nodes
    Vec_IntForEachEntry( vSuper, iLit, i )
    {
        Gia_Obj_t * pObj = Gia_ManObj( p, Abc_Lit2Var(iLit) );
        if ( Gia_ObjIsAnd(pObj) )
        {
            for ( k = 0; k < 2; k++ )
            {
                iFanin = k ? Gia_ObjFaninId1p(p, pObj) : Gia_ObjFaninId0p(p, pObj);
                if ( !Gia_ObjIsTravIdCurrentId(p, iFanin) )
                {
                    if ( Vec_IntSize(vSuper) == nLimit )
                        break;
                    Gia_ObjSetTravIdCurrentId( p, iFanin );
                    Matrix[Vec_IntSize(vSuper)] = 0;
                    Levels[Vec_IntSize(vSuper)] = nLevels;
                    Str_ManSetNum( p, iFanin, Vec_IntSize(vSuper) );
                    Vec_IntPush( vSuper, Abc_Var2Lit(iFanin, 0) );
                }
                Matrix[Str_ManNum(p, iFanin)] |= Matrix[i];
            }
        }
        if ( Gia_ObjIsMux(p, pObj) )
        {
            iFanin = Gia_ObjFaninId2p(p, pObj);
            if ( !Gia_ObjIsTravIdCurrentId(p, iFanin) )
            {
                if ( Vec_IntSize(vSuper) == nLimit )
                    break;
                Gia_ObjSetTravIdCurrentId( p, iFanin );
                Matrix[Vec_IntSize(vSuper)] = 0;
                Levels[Vec_IntSize(vSuper)] = nLevels;
                Str_ManSetNum( p, iFanin, Vec_IntSize(vSuper) );
                Vec_IntPush( vSuper, Abc_Var2Lit(iFanin, 0) );
            }
            Matrix[Str_ManNum(p, iFanin)] |= Matrix[i];
        }
        if ( Prev == i )
            Prev = Vec_IntSize(vSuper), nLevels++;
        if ( nLevels == 8 )
            break;
    }

    // remove those that have all 1s or only one 1
    Mask = (~(word)0) << (64 - nSize);
    for ( k = i = 0; i < Vec_IntSize(vSuper); i++ )
    {
        assert( Matrix[i] );
        if ( (Matrix[i] & (Matrix[i] - 1)) == 0 )
            continue;
        if ( Matrix[i] == Mask )
            continue;
        Matrix[k] = Matrix[i];
        Levels[k] = Levels[i];
        k++;
        if ( k == 64 )
            break;
    }
    // clean the remaining ones
    for ( i = k; i < 64; i++ )
        Matrix[i] = 0;
    nSizeNew = k;
    if ( nSizeNew == 0 )
    {
        Vec_IntShrink( vSuper, nSize );
        return 0;
    }
/*
    // report
    if ( fVerbose && nSize > 20 )
    {
        for ( i = 0; i < nSizeNew; i++ )
            Extra_PrintBinary( stdout, Matrix+i, 64 ), printf( "\n" );
        printf( "\n" );
    }
*/
    transpose64( Matrix );

    // report
    if ( fVerbose && nSize > 10 )
    {
        printf( "Gate inputs = %d.  Collected fanins = %d.  All = %d.  Good = %d.  Levels = %d\n", 
            nSize, Vec_IntSize(vSuper) - nSize, Vec_IntSize(vSuper), nSizeNew, nLevels );
        printf( "                     " );
        for ( i = 0; i < nSizeNew; i++ )
            printf( "%d", Levels[i] );
        printf( "\n" );
        for ( i = 0; i < nSize; i++ )
        {
            printf( "%6d : ", Abc_Lit2Var(Vec_IntEntry(vSuper, i)) );
            printf( "%3d   ", Vec_IntEntry(vDelay, i) >> 4 );
            printf( "%3d   ", Vec_IntEntry(vDelay, i) & 15 );
//            Extra_PrintBinary( stdout, Matrix+i, 64 ), printf( "\n" );
        }
        i = 0;
    }
    Vec_IntShrink( vSuper, nSize );
    return nSizeNew;
}

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

  Synopsis    [Count 1s.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
static inline int Str_CountBits( word i )
{
    if ( i == 0 )
        return 0;
    i = (i & (i - 1));
    if ( i == 0 )
        return 1;
    i = (i & (i - 1));
    if ( i == 0 )
        return 2;
    i = i - ((i >> 1) & 0x5555555555555555);
    i = (i & 0x3333333333333333) + ((i >> 2) & 0x3333333333333333);
    i = ((i + (i >> 4)) & 0x0F0F0F0F0F0F0F0F);
    return (i*(0x0101010101010101))>>56;
}

static inline void Str_PrintState( int * pCost, int * pSuper, word * pMatrix, int nSize )
{
    int i;
    for ( i = 0; i < nSize; i++ )
    {
        printf( "%6d : ", i );
        printf( "%6d : ", Abc_Lit2Var(pSuper[i]) );
        printf( "%3d   ", pCost[i] >> 4 );
        printf( "%3d   ", pCost[i] & 15 );
//        Extra_PrintBinary( stdout, pMatrix+i, 64 ), printf( "\n" );
    }
    printf( "\n" );
}


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

  Synopsis    [Perform balancing.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
void Str_NtkBalanceMulti2( Gia_Man_t * pNew, Str_Ntk_t * p, Str_Obj_t * pObj, Vec_Int_t * vDelay, int nLutSize )
{
    int k;
    pObj->iCopy = (pObj->Type == STR_AND);
    for ( k = 0; k < (int)pObj->nFanins; k++ )
    {
        if ( pObj->Type == STR_AND )
            pObj->iCopy = Gia_ManHashAnd( pNew, pObj->iCopy, Str_ObjFaninCopy(p, pObj, k) );
        else
            pObj->iCopy = Gia_ManHashXorReal( pNew, pObj->iCopy, Str_ObjFaninCopy(p, pObj, k) );
        Str_ObjDelay( pNew, Abc_Lit2Var(pObj->iCopy), nLutSize, vDelay );
    }
}

int Str_NtkBalanceTwo( Gia_Man_t * pNew, Str_Ntk_t * p, Str_Obj_t * pObj, int i, int j, Vec_Int_t * vDelay, int * pCost, int * pSuper, word * pMatrix, int nSize, int nLutSize, int CostBest )
{
    int k, iLitRes, Delay;
    assert( i < j );
//    printf( "Merging node %d and %d\n", i, j );
    if ( pObj->Type == STR_AND )
        iLitRes = Gia_ManHashAnd( pNew, pSuper[i], pSuper[j] );
    else
        iLitRes = Gia_ManHashXorReal( pNew, pSuper[i], pSuper[j] );
    Delay = Str_ObjDelay( pNew, Abc_Lit2Var(iLitRes), nLutSize, vDelay );
    // update
    pCost[i] = Delay;
    pSuper[i] = iLitRes;
    pMatrix[i] |= pMatrix[j];
//    assert( (pCost[i] & 15) == CostBest || CostBest == -1 );
    // remove entry j
    nSize--;
    for ( k = j; k < nSize; k++ )
    {
        pCost[k] = pCost[k+1];
        pSuper[k] = pSuper[k+1];
        pMatrix[k] = pMatrix[k+1];
    }
    // move up the first one
    nSize--;
    for ( k = 0; k < nSize; k++ )
    {
        if ( pCost[k] <= pCost[k+1] )
            break;
        ABC_SWAP( int, pCost[k], pCost[k+1] );
        ABC_SWAP( int, pSuper[k], pSuper[k+1] );
        ABC_SWAP( word, pMatrix[k], pMatrix[k+1] );
    }
    return iLitRes;
}

void Str_NtkBalanceMulti( Gia_Man_t * pNew, Str_Ntk_t * p, Str_Obj_t * pObj, Vec_Int_t * vDelay, int nLutSize )
{
    word pMatrix[256];
    int Limit = 256;
    Vec_Int_t * vSuper = pNew->vSuper;
    Vec_Int_t * vCosts = pNew->vStore;
    int * pSuper = Vec_IntArray(vSuper);
    int * pCost  = Vec_IntArray(vCosts);
    int k, iLit, MatrixSize = 0;
    assert( Limit <= Vec_IntCap(vSuper) );
    assert( Limit <= Vec_IntCap(vCosts) );

    // collect nodes
    Vec_IntClear( vSuper );
    for ( k = 0; k < (int)pObj->nFanins; k++ )
        Vec_IntPush( vSuper, Str_ObjFaninCopy(p, pObj, k) );
    Vec_IntSort( vSuper, 0 );
    if ( pObj->Type == STR_AND )
        Gia_ManSimplifyAnd( vSuper );
    else
        Gia_ManSimplifyXor( vSuper );
    assert( Vec_IntSize(vSuper) > 0 );
    if ( Vec_IntSize(vSuper) == 1 )
    {
        pObj->iCopy = Vec_IntEntry(vSuper, 0);
        return;
    }
    if ( Vec_IntSize(vSuper) == 2 )
    {
        pObj->iCopy = Str_NtkBalanceTwo( pNew, p, pObj, 0, 1, vDelay, pCost, pSuper, pMatrix, 2, nLutSize, -1 );
        return;
    }

    // sort by cost
    Vec_IntClear( vCosts );
    Vec_IntForEachEntry( vSuper, iLit, k )
        Vec_IntPush( vCosts, Vec_IntEntry(vDelay, Abc_Lit2Var(iLit)) );
    Vec_IntSelectSortCost2( pSuper, Vec_IntSize(vSuper), pCost );

    // compute affinity
    if ( Vec_IntSize(vSuper) < 64 )
        MatrixSize = Str_ManVectorAffinity( pNew, vSuper, vCosts, pMatrix, Limit );

    // start the new product
    while ( Vec_IntSize(vSuper) > 2 )
    {
        // pair the first entry with another one on the same level
        int i, iStop, iBest,iBest2;
        int CostNew, CostBest, CostBest2;
        int OccurNew, OccurBest, OccurBest2;

        if ( Vec_IntSize(vSuper) > 64 )
        {
            Str_NtkBalanceTwo( pNew, p, pObj, 0, 1, vDelay, pCost, pSuper, pMatrix, Vec_IntSize(vSuper), nLutSize, -1 );
            vSuper->nSize--;
            vCosts->nSize--;
            continue;
        }

        // compute affinity
        if ( Vec_IntSize(vSuper) == 64 )
            MatrixSize = Str_ManVectorAffinity( pNew, vSuper, vCosts, pMatrix, Limit );
        assert( Vec_IntSize(vSuper) <= 64 );
//        Str_PrintState( pCost, pSuper, pMatrix, Vec_IntSize(vSuper) );

        // if the first two are PIs group them
        if ( pCost[0] == 17 && pCost[1] == 17 )
        {
            Str_NtkBalanceTwo( pNew, p, pObj, 0, 1, vDelay, pCost, pSuper, pMatrix, Vec_IntSize(vSuper), nLutSize, 2 );
            vSuper->nSize--;
            vCosts->nSize--;
            continue;
        }

        // find the end of the level
        for ( iStop = 0; iStop < Vec_IntSize(vSuper); iStop++ )
            if ( (pCost[iStop] >> 4) != (pCost[0] >> 4) )
                break;
        // if there is only one this level, pair it with the best match in the next level
        if ( iStop == 1 )
        {
            iBest = iStop, OccurBest = Str_CountBits(pMatrix[0] & pMatrix[iStop]);
            for ( i = iStop + 1; i < Vec_IntSize(vSuper); i++ )
            {
                if ( (pCost[i] >> 4) != (pCost[iStop] >> 4) )
                    break;
                OccurNew = Str_CountBits(pMatrix[0] & pMatrix[i]);
                if ( OccurBest < OccurNew )
                    iBest = i, OccurBest = OccurNew;
            }
            assert( iBest > 0 && iBest < Vec_IntSize(vSuper) );
            Str_NtkBalanceTwo( pNew, p, pObj, 0, iBest, vDelay, pCost, pSuper, pMatrix, Vec_IntSize(vSuper), nLutSize, -1 );
            vSuper->nSize--;
            vCosts->nSize--;
            continue;
        }
        // pair the first entry with another one on the same level
        iBest = -1; CostBest = -1; OccurBest2 = -1; OccurBest = -1;
        for ( i = 1; i < iStop; i++ )
        {
            CostNew = (pCost[0] & 15) + (pCost[i] & 15);
            if ( CostNew > nLutSize )
                continue;
            OccurNew = Str_CountBits(pMatrix[0] & pMatrix[i]);
            if ( CostBest < CostNew || (CostBest == CostNew && OccurBest < OccurNew) )
                CostBest = CostNew, iBest = i, OccurBest = OccurNew;
        }
        // if the best found is perfect, take it
        if ( CostBest == nLutSize )
        {
            assert( iBest > 0 && iBest < Vec_IntSize(vSuper) );
            Str_NtkBalanceTwo( pNew, p, pObj, 0, iBest, vDelay, pCost, pSuper, pMatrix, Vec_IntSize(vSuper), nLutSize, CostBest );
            vSuper->nSize--;
            vCosts->nSize--;
            continue;
        }
        // find the best pair on this level
        iBest = iBest2 = -1; CostBest = CostBest2 = -1, OccurBest = OccurBest2 = -1;
        for ( i = 0; i < iStop; i++ )
        for ( k = i+1; k < iStop; k++ )
        {
            CostNew  = (pCost[i] & 15) + (pCost[k] & 15);
            OccurNew = Str_CountBits(pMatrix[i] & pMatrix[k]);
            if ( CostNew <= nLutSize ) // the same level
            {
                if ( OccurBest < OccurNew || (OccurBest == OccurNew && CostBest < CostNew ))
                    CostBest = CostNew, iBest = (i << 16) | k, OccurBest = OccurNew;
            }
            else // overflow to the next level
            {
                if ( OccurBest2 < OccurNew || (OccurBest2 == OccurNew && CostBest2 < CostNew) )
                    CostBest2 = CostNew, iBest2 = (i << 16) | k, OccurBest2 = OccurNew;
            }
        }
        if ( iBest >= 0 )
        {
            assert( iBest > 0 );
            Str_NtkBalanceTwo( pNew, p, pObj, iBest>>16, iBest&0xFFFF, vDelay, pCost, pSuper, pMatrix, Vec_IntSize(vSuper), nLutSize, CostBest );
            vSuper->nSize--;
            vCosts->nSize--;
            continue;
        }
        // take any remaining pair
        assert( iBest2 > 0 );
        Str_NtkBalanceTwo( pNew, p, pObj, iBest2>>16, iBest2&0xFFFF, vDelay, pCost, pSuper, pMatrix, Vec_IntSize(vSuper), nLutSize, -1 );
        vSuper->nSize--;
        vCosts->nSize--;
        continue;
    }
    pObj->iCopy = Str_NtkBalanceTwo( pNew, p, pObj, 0, 1, vDelay, pCost, pSuper, pMatrix, 2, nLutSize, -1 );

/*
    // simple
    pObj->iCopy = (pObj->Type == STR_AND);
    for ( k = 0; k < Vec_IntSize(vSuper); k++ )
    {
        if ( pObj->Type == STR_AND )
            pObj->iCopy = Gia_ManHashAnd( pNew, pObj->iCopy, Vec_IntEntry(vSuper, k) );
        else
            pObj->iCopy = Gia_ManHashXorReal( pNew, pObj->iCopy, Vec_IntEntry(vSuper, k) );
        Str_ObjDelay( pNew, Abc_Lit2Var(pObj->iCopy), nLutSize, vDelay );
    }
*/
}
void Str_NtkBalanceMux( Gia_Man_t * pNew, Str_Ntk_t * p, Str_Obj_t * pObj, Vec_Int_t * vDelay, int nLutSize, int nGroups, int nMuxes, int fRecursive, int fOptArea, int fVerbose )
{
    extern int Str_MuxRestructure( Gia_Man_t * pNew, Str_Ntk_t * pNtk, int iMux, int nMuxes, Vec_Int_t * vDelay, int nLutSize, int fRecursive, int fOptArea, int fVerbose );
    int n, m, iRes, fUseRestruct = 1;
    if ( fUseRestruct )
    {
        for ( n = 0; n < nGroups; n++ )
        {            
            iRes = Str_MuxRestructure( pNew, p, Str_ObjId(p, pObj), nMuxes, vDelay, nLutSize, fRecursive, fOptArea, fVerbose );
            if ( iRes == -1 )
            {
                for ( m = 0; m < nMuxes; m++, pObj++ )
                {
                    pObj->iCopy = Gia_ManHashMuxReal( pNew, Str_ObjFaninCopy(p, pObj, 2), Str_ObjFaninCopy(p, pObj, 1), Str_ObjFaninCopy(p, pObj, 0) );
                    Str_ObjDelay( pNew, Abc_Lit2Var(pObj->iCopy), nLutSize, vDelay );
                }
            }
            else
            {
                pObj += nMuxes - 1;
                pObj->iCopy = iRes;
                pObj++;
            }
        }
    }
    else
    {
        for ( n = 0; n < nGroups * nMuxes; n++, pObj++ )
        {
            pObj->iCopy = Gia_ManHashMuxReal( pNew, Str_ObjFaninCopy(p, pObj, 2), Str_ObjFaninCopy(p, pObj, 1), Str_ObjFaninCopy(p, pObj, 0) );
            Str_ObjDelay( pNew, Abc_Lit2Var(pObj->iCopy), nLutSize, vDelay );
        }
    }
}
Gia_Man_t * Str_NtkBalance( Gia_Man_t * pGia, Str_Ntk_t * p, int nLutSize, int fUseMuxes, int fRecursive, int fOptArea, int fVerbose )
{
    Gia_Man_t * pNew, * pTemp;
    Vec_Int_t * vDelay;
    Str_Obj_t * pObj; 
    int nGroups, nMuxes, CioId;
    int arrTime, Delay = 0;
    assert( nLutSize < 16 );
    assert( pGia->pMuxes == NULL );
    pNew = Gia_ManStart( Gia_ManObjNum(pGia) );
    pNew->pName = Abc_UtilStrsav( pGia->pName );
    pNew->pSpec = Abc_UtilStrsav( pGia->pSpec );
    pNew->pMuxes = ABC_CALLOC( unsigned, pNew->nObjsAlloc );
    Vec_IntFill( &pNew->vCopies, pNew->nObjsAlloc, -1 );
    if ( pNew->vSuper == NULL )
        pNew->vSuper = Vec_IntAlloc( 1000 );
    if ( pNew->vStore == NULL )
        pNew->vStore = Vec_IntAlloc( 1000 );
    vDelay = Vec_IntStart( 2*pNew->nObjsAlloc );
    Gia_ManHashStart( pNew );
    if ( pGia->pManTime != NULL ) // Tim_Man with unit delay 16
    {
        Tim_ManInitPiArrivalAll( (Tim_Man_t *)pGia->pManTime, 17 );
        Tim_ManIncrementTravId( (Tim_Man_t *)pGia->pManTime );
    }
    Str_NtkManForEachObj( p, pObj )
    {
        if ( pObj->Type == STR_PI )
        {
            pObj->iCopy = Gia_ManAppendCi( pNew );
            arrTime = 17;
            if ( pGia->pManTime != NULL )
            {
                CioId = Gia_ObjCioId( Gia_ManObj(pNew, Abc_Lit2Var(pObj->iCopy)) );
                arrTime = (int)Tim_ManGetCiArrival( (Tim_Man_t *)pGia->pManTime, CioId );
            }
            Vec_IntWriteEntry( vDelay, Abc_Lit2Var(pObj->iCopy), arrTime );
        }
        else if ( pObj->Type == STR_AND || pObj->Type == STR_XOR )
            Str_NtkBalanceMulti( pNew, p, pObj, vDelay, nLutSize );
        else if ( pObj->Type == STR_MUX && pObj->iTop >= 0 && fUseMuxes )
        {
            Str_ObjReadGroup( p, pObj, &nGroups, &nMuxes );
            assert( nGroups * nMuxes >= 2 );
            Str_NtkBalanceMux( pNew, p, pObj, vDelay, nLutSize, nGroups, nMuxes, fRecursive, fOptArea, fVerbose );
            pObj += nGroups * nMuxes - 1;
        }
        else if ( pObj->Type == STR_MUX )
        {
            pObj->iCopy = Gia_ManHashMuxReal( pNew, Str_ObjFaninCopy(p, pObj, 2), Str_ObjFaninCopy(p, pObj, 1), Str_ObjFaninCopy(p, pObj, 0) );
            Str_ObjDelay( pNew, Abc_Lit2Var(pObj->iCopy), nLutSize, vDelay );
        }
        else if ( pObj->Type == STR_PO )
        {
            pObj->iCopy = Gia_ManAppendCo( pNew, Str_ObjFaninCopy(p, pObj, 0) );
            arrTime = Vec_IntEntry(vDelay, Abc_Lit2Var(Str_ObjFaninCopy(p, pObj, 0)) );
            Delay = Abc_MaxInt( Delay, arrTime );
            if ( pGia->pManTime != NULL )
            {
                CioId = Gia_ObjCioId( Gia_ManObj(pNew, Abc_Lit2Var(pObj->iCopy)) );
                Tim_ManSetCoArrival( (Tim_Man_t *)pGia->pManTime, CioId, (float)arrTime );
            }
        }
        else if ( pObj->Type == STR_CONST0 )
            pObj->iCopy = 0, Vec_IntWriteEntry(vDelay, 0, 17);
        else assert( 0 );
    }
    if ( fVerbose )
        printf( "Max delay = %d.  Old objs = %d.  New objs = %d.\n", Delay >> 4, Gia_ManObjNum(pGia), Gia_ManObjNum(pNew) );
    Vec_IntFree( vDelay );
    ABC_FREE( pNew->vCopies.pArray );
    Gia_ManHashStop( pNew );
    Gia_ManSetRegNum( pNew, Gia_ManRegNum(pGia) );
    pNew = Gia_ManDupNoMuxes( pTemp = pNew, 0 );
    Gia_ManStop( pTemp );
//    if ( pGia->pManTime != NULL )
//        pNew->pManTime = Tim_ManDup( (Tim_Man_t *)pGia->pManTime, 0 );
    return pNew;
}

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

  Synopsis    [Test normalization procedure.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
Gia_Man_t * Gia_ManLutBalance( Gia_Man_t * p, int nLutSize, int fUseMuxes, int fRecursive, int fOptArea, int fVerbose )
{
    Str_Ntk_t * pNtk;
    Gia_Man_t * pNew;
    abctime clk = Abc_Clock();
    if ( p->pManTime && Tim_ManBoxNum((Tim_Man_t*)p->pManTime) && Gia_ManIsNormalized(p) )
    {
        Tim_Man_t * pTimOld = (Tim_Man_t *)p->pManTime;
        p->pManTime = Tim_ManDup( pTimOld, 16 );
        pNew = Gia_ManDupUnnormalize( p );
        if ( pNew == NULL )
            return NULL;
        Gia_ManTransferTiming( pNew, p );
        p = pNew;
        // optimize
        pNtk = Str_ManNormalize( p );
        pNew = Str_NtkBalance( p, pNtk, nLutSize, fUseMuxes, fRecursive, fOptArea, fVerbose );
        Gia_ManTransferTiming( pNew, p );
        Gia_ManStop( p );
        // normalize
        pNew = Gia_ManDupNormalize( p = pNew, 0 );
        Gia_ManTransferTiming( pNew, p );
        Gia_ManStop( p );
        // cleanup
        Tim_ManStop( (Tim_Man_t *)pNew->pManTime );
        pNew->pManTime = pTimOld;
        assert( Gia_ManIsNormalized(pNew) );
    }
    else 
    {
        pNtk = Str_ManNormalize( p );
    //    Str_NtkPrintGroups( pNtk );
        pNew = Str_NtkBalance( p, pNtk, nLutSize, fUseMuxes, fRecursive, fOptArea, fVerbose );
        Gia_ManTransferTiming( pNew, p );
    }
    if ( fVerbose )
        Str_NtkPs( pNtk, Abc_Clock() - clk );
    Str_NtkDelete( pNtk );   
    return pNew;
}





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

  Synopsis    [Perform MUX restructuring.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
typedef struct Str_Edg_t_ Str_Edg_t; 
struct Str_Edg_t_
{
    int            Fan;      // fanin ID
    int            fCompl;   // fanin complement
    int            FanDel;   // fanin delay
    int            Copy;     // fanin copy
};

typedef struct Str_Mux_t_ Str_Mux_t; // 64 bytes
struct Str_Mux_t_
{
    int            Id;       // node ID
    int            Delay;    // node delay
    int            Copy;     // node copy
    int            nLutSize; // LUT size
    Str_Edg_t      Edge[3];  // fanins
};

static inline Str_Mux_t * Str_MuxFanin( Str_Mux_t * pMux, int i )     { return pMux - pMux->Id + pMux->Edge[i].Fan;                         }
static inline int         Str_MuxHasFanin( Str_Mux_t * pMux, int i )  { return pMux->Edge[i].Fan > 0 && Str_MuxFanin(pMux, i)->Copy != -2;  }

void Str_MuxDelayPrint_rec( Str_Mux_t * pMux, int i )
{
    int fShowDelay = 1;
    Str_Mux_t * pFanin;
    if ( pMux->Edge[i].Fan <= 0 )
    {
        printf( "%d", -pMux->Edge[i].Fan );
        if ( fShowDelay )
            printf( "{%d}", pMux->Edge[i].FanDel );
        return;
    }
    pFanin = Str_MuxFanin( pMux, i );
    printf( "[ " );
    if ( pFanin->Edge[0].fCompl )
        printf( "!" );
    Str_MuxDelayPrint_rec( pFanin, 0 );
    printf( "|" );
    if ( pFanin->Edge[1].fCompl )
        printf( "!" );
    Str_MuxDelayPrint_rec( pFanin, 1 );
    printf( "(" );
    if ( pFanin->Edge[2].fCompl )
        printf( "!" );
    Str_MuxDelayPrint_rec( pFanin, 2 );
    printf( ")" );
    printf( " ]" );
}
int Str_MuxDelayEdge_rec( Str_Mux_t * pMux, int i )
{
    if ( pMux->Edge[i].Fan > 0 )
    {
        Str_Mux_t * pFanin = Str_MuxFanin( pMux, i );
        Str_MuxDelayEdge_rec( pFanin, 0 );
        Str_MuxDelayEdge_rec( pFanin, 1 );
        pMux->Edge[i].FanDel = Str_Delay3( pFanin->Edge[0].FanDel, pFanin->Edge[1].FanDel, pFanin->Edge[2].FanDel, pFanin->nLutSize );
    }
    return pMux->Edge[i].FanDel;
}
void Str_MuxCreate( Str_Mux_t * pTree, Str_Ntk_t * pNtk, int iMux, int nMuxes, Vec_Int_t * vDelay, int nLutSize )
{
    Str_Obj_t * pObj;
    Str_Mux_t * pMux;
    int i, k, nPis = 0;
    assert( nMuxes >= 2 );
    memset( pTree, 0, sizeof(Str_Mux_t) * (nMuxes + 1) );
    pTree->nLutSize = nLutSize;
    pTree->Edge[0].Fan = 1;
    for ( i = 1; i <= nMuxes; i++ )
    {
        pMux = pTree + i;
        pMux->Id = i;
        pMux->nLutSize = nLutSize;
        pMux->Delay = pMux->Copy = -1;
        // assign fanins
        pObj = Str_NtkObj( pNtk, iMux + nMuxes - i );
        assert( pObj->Type == STR_MUX );
        for ( k = 0; k < 3; k++ )
        {
            pMux->Edge[k].fCompl = Str_ObjFaninC(pNtk, pObj, k);
            if ( Str_ObjFaninId(pNtk, pObj, k) >= iMux )
                pMux->Edge[k].Fan = iMux + nMuxes - Str_ObjFaninId(pNtk, pObj, k);
            else
            {
                pMux->Edge[k].Fan = -nPis++; // count external inputs, including controls
                pMux->Edge[k].Copy = Str_ObjFanin(pNtk, pObj, k)->iCopy;
                pMux->Edge[k].FanDel = Vec_IntEntry( vDelay, Abc_Lit2Var(pMux->Edge[k].Copy) );
            }
        }
    }
}
int Str_MuxToGia_rec( Gia_Man_t * pNew, Str_Mux_t * pMux, int i, Vec_Int_t * vDelay )
{
    if ( pMux->Edge[i].Fan > 0 )
    {
        Str_Mux_t * pFanin = Str_MuxFanin( pMux, i );
        int iLit0 = Str_MuxToGia_rec( pNew, pFanin, 0, vDelay );
        int iLit1 = Str_MuxToGia_rec( pNew, pFanin, 1, vDelay );
        assert( pFanin->Edge[2].Fan <= 0 );
        assert( pFanin->Edge[2].fCompl == 0 );
        pMux->Edge[i].Copy = Gia_ManHashMuxReal( pNew, pFanin->Edge[2].Copy, iLit1, iLit0 );
        Str_ObjDelay( pNew, Abc_Lit2Var(pMux->Edge[i].Copy), pFanin->nLutSize, vDelay );
    }
    return Abc_LitNotCond( pMux->Edge[i].Copy, pMux->Edge[i].fCompl );
}
void Str_MuxChangeOnce( Str_Mux_t * pTree, int * pPath, int i, int k, Str_Mux_t * pBackup, Gia_Man_t * pNew, Vec_Int_t * vDelay )
{
    Str_Mux_t * pSpots[3];
    int pInds[3], MidFan, MidCom, MidDel, MidCop, c;
    int iRes, iCond, fCompl;
    // save backup
    assert( i + 1 < k );
    if ( pBackup )
    {
        pBackup[0] = pTree[ Abc_Lit2Var(pPath[k])  ];
        pBackup[1] = pTree[ Abc_Lit2Var(pPath[i+1])];
        pBackup[2] = pTree[ Abc_Lit2Var(pPath[i])  ];
    }
    // perform changes
    pSpots[0] = pTree + Abc_Lit2Var(pPath[k]); 
    pSpots[1] = pTree + Abc_Lit2Var(pPath[i+1]); 
    pSpots[2] = pTree + Abc_Lit2Var(pPath[i]); 
    pInds[0] = Abc_LitIsCompl(pPath[k]);
    pInds[1] = Abc_LitIsCompl(pPath[i+1]);
    pInds[2] = Abc_LitIsCompl(pPath[i]);
    // check
    assert( pSpots[0]->Edge[pInds[0]].Fan > 0 );
    assert( pSpots[1]->Edge[pInds[1]].Fan > 0 );
    // collect complement
    fCompl = 0;
    for ( c = i+1; c < k; c++ )
        fCompl ^= pTree[Abc_Lit2Var(pPath[c])].Edge[Abc_LitIsCompl(pPath[c])].fCompl;
    // remember bottom side
    MidFan = pSpots[2]->Edge[!pInds[2]].Fan;
    MidCom = pSpots[2]->Edge[!pInds[2]].fCompl;
    MidDel = pSpots[2]->Edge[!pInds[2]].FanDel;
    MidCop = pSpots[2]->Edge[!pInds[2]].Copy;
    // update bottom
    pSpots[2]->Edge[!pInds[2]].Fan    = pSpots[0]->Edge[pInds[0]].Fan;
    pSpots[2]->Edge[!pInds[2]].fCompl = 0;
    // update top 
    pSpots[0]->Edge[pInds[0]].Fan     = pSpots[2]->Id;
    // update middle
    pSpots[1]->Edge[pInds[1]].Fan     = MidFan;
    pSpots[1]->Edge[pInds[1]].fCompl ^= MidCom;
    pSpots[1]->Edge[pInds[1]].FanDel  = MidDel;
    pSpots[1]->Edge[pInds[1]].Copy    = MidCop;
    // update delay of the control
    for ( c = i + 1; c < k; c++ )
        pSpots[2]->Edge[2].FanDel = Str_Delay2( pSpots[2]->Edge[2].FanDel, pTree[Abc_Lit2Var(pPath[c])].Edge[2].FanDel, pTree->nLutSize );
    if ( pNew == NULL )
        return;
    // create AND gates
    iRes = 1;
    for ( c = i; c < k; c++ )
    {
        assert( pTree[Abc_Lit2Var(pPath[c])].Edge[2].fCompl == 0 );
        iCond = pTree[Abc_Lit2Var(pPath[c])].Edge[2].Copy;
        iCond = Abc_LitNotCond( iCond, !Abc_LitIsCompl(pPath[c]) );
        iRes  = Gia_ManHashAnd( pNew, iRes, iCond );
        Str_ObjDelay( pNew, Abc_Lit2Var(iRes), pTree->nLutSize, vDelay );
    }
    // complement the condition
    pSpots[2]->Edge[2].Copy = Abc_LitNotCond( iRes, !Abc_LitIsCompl(pPath[i]) );
    // complement the path
    pSpots[2]->Edge[pInds[2]].fCompl ^= fCompl;
}
void Str_MuxChangeUndo( Str_Mux_t * pTree, int * pPath, int i, int k, Str_Mux_t * pBackup )
{
    pTree[ Abc_Lit2Var(pPath[k])  ] = pBackup[0]; 
    pTree[ Abc_Lit2Var(pPath[i+1])] = pBackup[1]; 
    pTree[ Abc_Lit2Var(pPath[i])  ] = pBackup[2]; 
}
int Str_MuxFindPathEdge_rec( Str_Mux_t * pMux, int i, int * pPath, int * pnLength )
{
    extern int Str_MuxFindPath_rec( Str_Mux_t * pMux, int * pPath, int * pnLength );
    if ( pMux->Edge[i].Fan > 0 && !Str_MuxFindPath_rec(Str_MuxFanin(pMux, i), pPath, pnLength) )
        return 0;
    pPath[ (*pnLength)++ ] = Abc_Var2Lit(pMux->Id, i);
    return 1;
}
int Str_MuxFindPath_rec( Str_Mux_t * pMux, int * pPath, int * pnLength )
{
    int i, DelayMax = Abc_MaxInt( pMux->Edge[0].FanDel, Abc_MaxInt(pMux->Edge[1].FanDel, pMux->Edge[2].FanDel) );
    for ( i = 0; i < 2; i++ )
        if ( pMux->Edge[i].FanDel == DelayMax )
            return Str_MuxFindPathEdge_rec( pMux, i, pPath, pnLength );
    if ( pMux->Edge[2].FanDel == DelayMax )
        return 0;
    assert( 0 );
    return -1;
}
// return node whose both branches are non-trivial
Str_Mux_t * Str_MuxFindBranching( Str_Mux_t * pRoot, int i )
{
    Str_Mux_t * pMux;
    if ( pRoot->Edge[i].Fan <= 0 )
        return NULL;
    pMux = Str_MuxFanin( pRoot, i );
    while ( 1 )
    {
        if ( pMux->Edge[0].Fan <= 0 && pMux->Edge[1].Fan <= 0 )
            return NULL;
        if ( pMux->Edge[0].Fan > 0 && pMux->Edge[1].Fan > 0 )
            return pMux;
        if ( pMux->Edge[0].Fan > 0 )
            pMux = Str_MuxFanin( pMux, 0 );
        if ( pMux->Edge[1].Fan > 0 )
            pMux = Str_MuxFanin( pMux, 1 );
    }
    assert( 0 );
    return NULL;
}
int Str_MuxTryOnce( Gia_Man_t * pNew, Str_Ntk_t * pNtk, Str_Mux_t * pTree, Str_Mux_t * pRoot, int Edge, Vec_Int_t * vDelay, int fVerbose )
{
    int pPath[MAX_TREE];
    Str_Mux_t pBackup[3];
    int Delay, DelayBest = Str_MuxDelayEdge_rec( pRoot, Edge ), DelayInit = DelayBest;
    int i, k, nLength = 0, ForkBest = -1, nChecks = 0;
    int RetValue = Str_MuxFindPathEdge_rec( pRoot, Edge, pPath, &nLength );
    if ( RetValue == 0 )
        return 0;
    if ( fVerbose )
        printf( "Trying node %d with path of length %d.\n", pRoot->Id, nLength );
    for ( i = 0; i < nLength; i++ )
    for ( k = i+2; k < nLength; k++ )
    {
        Str_MuxChangeOnce( pTree, pPath, i, k, pBackup, NULL, NULL );
        Delay = Str_MuxDelayEdge_rec( pRoot, Edge );
        Str_MuxChangeUndo( pTree, pPath, i, k, pBackup );
        if ( DelayBest > Delay || (ForkBest > 0 && DelayBest == Delay) )
            DelayBest = Delay, ForkBest = (i << 16) | k;
        if ( fVerbose )
            printf( "%2d %2d -> %3d (%3d)\n", i, k, Delay, DelayBest );
        nChecks++;
    }
    if ( ForkBest == -1 )
    {
        if ( fVerbose )
            printf( "Did not find!\n" );
        return 0;
    }
//    Str_MuxDelayPrint_rec( pRoot, Edge ); printf( "\n" );
    Str_MuxChangeOnce( pTree, pPath, ForkBest >> 16, ForkBest & 0xFFFF, NULL, pNew, vDelay );
//    Str_MuxDelayPrint_rec( pRoot, Edge ); printf( "\n" );
    if ( fVerbose )
        printf( "Node %6d (%3d %3d) : Checks = %d. Delay: %d -> %d.\n", 
            pRoot->Id, ForkBest >> 16, ForkBest & 0xFFFF, nChecks, DelayInit, DelayBest );
    if ( fVerbose )
        printf( "\n" );
    return 1;
}
int Str_MuxRestruct_rec( Gia_Man_t * pNew, Str_Ntk_t * pNtk, Str_Mux_t * pTree, Str_Mux_t * pRoot, int Edge, Vec_Int_t * vDelay, int fVerbose )
{
    int fChanges = 0;
    Str_Mux_t * pMux = Str_MuxFindBranching( pRoot, Edge );
    if ( pMux != NULL )
        fChanges |= Str_MuxRestruct_rec( pNew, pNtk, pTree, pMux, 0, vDelay, fVerbose );
    if ( pMux != NULL )
        fChanges |= Str_MuxRestruct_rec( pNew, pNtk, pTree, pMux, 1, vDelay, fVerbose );
    fChanges |= Str_MuxTryOnce( pNew, pNtk, pTree, pRoot, Edge, vDelay, fVerbose );
    return fChanges;
}
int Str_MuxRestructure2( Gia_Man_t * pNew, Str_Ntk_t * pNtk, int iMux, int nMuxes, Vec_Int_t * vDelay, int nLutSize, int fVerbose )
{
    int Limit = MAX_TREE;
    Str_Mux_t pTree[MAX_TREE];
    int Delay, Delay2, fChanges = 0;
    if ( nMuxes >= Limit )
        return -1;
    assert( nMuxes < Limit );
    Str_MuxCreate( pTree, pNtk, iMux, nMuxes, vDelay, nLutSize );
    Delay = Str_MuxDelayEdge_rec( pTree, 0 );
    while ( 1 )
    {     
        if ( !Str_MuxRestruct_rec(pNew, pNtk, pTree, pTree, 0, vDelay, fVerbose) )
            break;
        fChanges = 1;
    }
    if ( !fChanges )
        return -1;
    Delay2 = Str_MuxDelayEdge_rec( pTree, 0 );
//    printf( "Improved delay for tree %d with %d MUXes (%d -> %d).\n", iMux, nMuxes, Delay, Delay2 );
    pNtk->DelayGain += Delay - Delay2;
    return Str_MuxToGia_rec( pNew, pTree, 0, vDelay );
}
int Str_MuxRestructure1( Gia_Man_t * pNew, Str_Ntk_t * pNtk, int iMux, int nMuxes, Vec_Int_t * vDelay, int nLutSize, int fVerbose )
{
    int Limit = MAX_TREE;
    Str_Mux_t pTree[MAX_TREE];
    int Delay, Delay2, fChanges = 0;
    if ( nMuxes >= Limit )
        return -1;
    assert( nMuxes < Limit );
    Str_MuxCreate( pTree, pNtk, iMux, nMuxes, vDelay, nLutSize );
    Delay = Str_MuxDelayEdge_rec( pTree, 0 );
    while ( 1 )
    {     
        if ( !Str_MuxTryOnce(pNew, pNtk, pTree, pTree, 0, vDelay, fVerbose) )
            break;
        fChanges = 1;
    }
    if ( !fChanges )
        return -1;
    Delay2 = Str_MuxDelayEdge_rec( pTree, 0 );
//    printf( "Improved delay for tree %d with %d MUXes (%d -> %d).\n", iMux, nMuxes, Delay, Delay2 );
    pNtk->DelayGain += Delay - Delay2;
    return Str_MuxToGia_rec( pNew, pTree, 0, vDelay );
}
int Str_MuxRestructure( Gia_Man_t * pNew, Str_Ntk_t * pNtk, int iMux, int nMuxes, Vec_Int_t * vDelay, int nLutSize, int fRecursive, int fOptArea, int fVerbose )
{
    extern int Str_MuxRestructureArea( Gia_Man_t * pNew, Str_Ntk_t * pNtk, int iMux, int nMuxes, Vec_Int_t * vDelay, int nLutSize, int fVerbose );
    if ( fOptArea )
    {
        if ( nMuxes < 2 )
            return Str_MuxRestructure1( pNew, pNtk, iMux, nMuxes, vDelay, nLutSize, fVerbose );
        return Str_MuxRestructureArea( pNew, pNtk, iMux, nMuxes, vDelay, nLutSize, fVerbose );
    }
    if ( fRecursive )
        return Str_MuxRestructure2( pNew, pNtk, iMux, nMuxes, vDelay, nLutSize, fVerbose );
    return Str_MuxRestructure1( pNew, pNtk, iMux, nMuxes, vDelay, nLutSize, fVerbose );
}

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

  Synopsis    [Perform MUX restructuring for area.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
int Str_MuxRestructAreaThree( Gia_Man_t * pNew, Str_Mux_t * pMux, Vec_Int_t * vDelay, int fVerbose )
{
    int iRes;
    Str_Mux_t * pFanin0 = Str_MuxFanin( pMux, 0 );
    Str_Mux_t * pFanin1 = Str_MuxFanin( pMux, 1 );
    assert( pMux->Copy == -1 );
    pMux->Copy = -2;
    if ( pFanin0->Edge[2].Copy == pFanin1->Edge[2].Copy )
        return 0;
    iRes = Gia_ManHashMuxReal( pNew, pMux->Edge[2].Copy, pFanin1->Edge[2].Copy, pFanin0->Edge[2].Copy );
    Str_ObjDelay( pNew, Abc_Lit2Var(iRes), pMux->nLutSize, vDelay );
    pFanin0->Edge[2].Copy = pFanin1->Edge[2].Copy = iRes;
//    printf( "Created triple\n" );
    return 0;
}
int Str_MuxRestructArea_rec( Gia_Man_t * pNew, Str_Mux_t * pTree, Str_Mux_t * pRoot, int i, Vec_Int_t * vDelay, int fVerbose )
{
    int Path[4];
    int fSkipMoving = 1;
    Str_Mux_t * pMux, * pFanin0, * pFanin1;
    int nMuxes0, nMuxes1;
    if ( pRoot->Edge[i].Fan <= 0 )
        return 0;
    pMux    = Str_MuxFanin( pRoot, i );
    nMuxes0 = Str_MuxRestructArea_rec( pNew, pTree, pMux, 0, vDelay, fVerbose );
    nMuxes1 = Str_MuxRestructArea_rec( pNew, pTree, pMux, 1, vDelay, fVerbose );
    if ( nMuxes0 + nMuxes1 < 2 )
        return 1 + nMuxes0 + nMuxes1;
    if ( nMuxes0 + nMuxes1 == 2 )
    {
        if ( nMuxes0 == 2 || nMuxes1 == 2 )
        {
            pFanin0 = Str_MuxFanin( pMux, (int)(nMuxes1 == 2) );
            assert( Str_MuxHasFanin(pFanin0, 0) != Str_MuxHasFanin(pFanin0, 1) );
            Path[2] = Abc_Var2Lit(pRoot->Id, i);
            Path[1] = Abc_Var2Lit(pMux->Id, (int)(nMuxes1 == 2) );
            Path[0] = Abc_Var2Lit(pFanin0->Id, Str_MuxHasFanin(pFanin0, 1));
            Str_MuxChangeOnce( pTree, Path, 0, 2, NULL, pNew, vDelay );
        }
        Str_MuxRestructAreaThree( pNew, Str_MuxFanin(pRoot, i), vDelay, fVerbose );
        return 0;
    }
    assert( nMuxes0 + nMuxes1 == 3 || nMuxes0 + nMuxes1 == 4 );
    assert( nMuxes0 == 2 || nMuxes1 == 2 );
    if ( fSkipMoving )
    {
        Str_MuxRestructAreaThree( pNew, pMux, vDelay, fVerbose );
        return 0;
    }
    if ( nMuxes0 == 2 )
    {
        pFanin0 = Str_MuxFanin( pMux, 0 );
        assert( Str_MuxHasFanin(pFanin0, 0) != Str_MuxHasFanin(pFanin0, 1) );
        Path[3] = Abc_Var2Lit(pRoot->Id, i);
        Path[2] = Abc_Var2Lit(pMux->Id, 0 );
        Path[1] = Abc_Var2Lit(pFanin0->Id, Str_MuxHasFanin(pFanin0, 1));
        pFanin1 = Str_MuxFanin( pFanin0, Str_MuxHasFanin(pFanin0, 1) );
        assert( !Str_MuxHasFanin(pFanin1, 0) && !Str_MuxHasFanin(pFanin1, 1) );
        Path[0] = Abc_Var2Lit(pFanin1->Id, 0);
        Str_MuxChangeOnce( pTree, Path, 0, 3, NULL, pNew, vDelay );
    }
    if ( nMuxes1 == 2 )
    {
        pFanin0 = Str_MuxFanin( pMux, 1 );
        assert( Str_MuxHasFanin(pFanin0, 0) != Str_MuxHasFanin(pFanin0, 1) );
        Path[3] = Abc_Var2Lit(pRoot->Id, i);
        Path[2] = Abc_Var2Lit(pMux->Id, 1 );
        Path[1] = Abc_Var2Lit(pFanin0->Id, Str_MuxHasFanin(pFanin0, 1));
        pFanin1 = Str_MuxFanin( pFanin0, Str_MuxHasFanin(pFanin0, 1) );
        assert( !Str_MuxHasFanin(pFanin1, 0) && !Str_MuxHasFanin(pFanin1, 1) );
        Path[0] = Abc_Var2Lit(pFanin1->Id, 0);
        Str_MuxChangeOnce( pTree, Path, 0, 3, NULL, pNew, vDelay );
    }
    Str_MuxRestructAreaThree( pNew, pMux, vDelay, fVerbose );
    return nMuxes0 + nMuxes1 - 2;
}
int Str_MuxRestructureArea( Gia_Man_t * pNew, Str_Ntk_t * pNtk, int iMux, int nMuxes, Vec_Int_t * vDelay, int nLutSize, int fVerbose )
{
    int Limit = MAX_TREE;
    Str_Mux_t pTree[MAX_TREE];
    int Result;
    if ( nMuxes >= Limit )
        return -1;
    assert( nMuxes < Limit );
    Str_MuxCreate( pTree, pNtk, iMux, nMuxes, vDelay, nLutSize );
    Result = Str_MuxRestructArea_rec( pNew, pTree, pTree, 0, vDelay, fVerbose );
    assert( Result >= 0 && Result <= 2 );
    return Str_MuxToGia_rec( pNew, pTree, 0, vDelay );
}

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


ABC_NAMESPACE_IMPL_END

