#include <pch.cxx>
#pragma hdrstop

#include <query.hxx>

#include "tabledbg.hxx"

CCategorize::CCategorize(
    CCategorizationSpec & rCatSpec,
    unsigned              iSpec,
    CCategorize *         pParent,
    CMutexSem &           mutex)
    : _iSpec(                 iSpec ),
      _pParent(               pParent ),
      _pChild(                0 ),
      _mutex(                 mutex ),
      _iCategoryGen(          0x40000000 + ( 0x1000 * iSpec ) ),
      _widCurrent(            WORKID_TBLBEFOREFIRST ),
      _fNotificationsEnabled( FALSE ),
      _iFindHint(             0 ),
      _aDynamicCategories(    0 ),
      _aVisibleCategories(    16 )
{
    if (rCatSpec.Type() != CATEGORIZE_UNIQUE)
        THROW(CException( E_INVALIDARG ));
} 

SCODE CCategorize::GetRows(
    HWATCHREGION            hRegion,
    WORKID                  widStart,
    CI_TBL_CHAPT            chapt,
    CTableColumnSet const & rOutColumns,
    CGetRowsParams &        rGetParams,
    WORKID &                rwidLastRowTransferred)
{
    SCODE sc = S_OK;

    TRY
    {
        if (WORKID_TBLAFTERLAST == widStart)
        {
            rwidLastRowTransferred = WORKID_TBLAFTERLAST;
            return DB_S_ENDOFROWSET;
        }
        else if (widInvalid == widStart)
        {
            Win4Assert(! "CCategorize::GetRows widStart is widInvalid");
            return E_FAIL;
        }
        else
        {
            CLock lock( _mutex );

            if ( 0 == _aVisibleCategories.Count() )
                sc = DB_S_ENDOFROWSET;
            else
            {
                unsigned iRow;
                if ( widStart != WORKID_TBLFIRST &&
                     widStart != WORKID_TBLBEFOREFIRST )
                    iRow = _FindCategory( widStart );
                else
                    iRow = 0;

                rwidLastRowTransferred = 0;

                while ( 0 != rGetParams.RowsToTransfer() &&
                        iRow < _aVisibleCategories.Count() )
                {

                    if ( ( _isCategorized() ) &&
                         ( DB_NULL_HCHAPTER != chapt ) &&
                         ( _aVisibleCategories[ iRow ].catParent != chapt ) )
                        break; 

                    BYTE* pbDst = (BYTE *) rGetParams.GetRowBuffer();

                    for ( unsigned col = 0; col < rOutColumns.Count(); col++ )
                    {
                        CTableColumn const & rDstColumn = *rOutColumns.Get(col);
                        PROPID pid = rDstColumn.GetPropId();

                        if ( pidChapter == pid || pidWorkId  == pid )
                        {
                            if (rDstColumn.GetStoredType() == VT_VARIANT)
                            {
                                Win4Assert( rDstColumn.GetValueSize() == sizeof VARIANT );
                                CTableVariant * pVarnt = (CTableVariant *) ( pbDst +
                                                         rDstColumn.GetValueOffset() );
                                pVarnt->vt = VT_UI4;
                                pVarnt->ulVal = _aVisibleCategories[iRow].catID;
                            }
                            else
                            {
                                Win4Assert( rDstColumn.GetValueSize() == sizeof CI_TBL_CHAPT );
                                RtlCopyMemory( pbDst + rDstColumn.GetValueOffset(),
                                               &( _aVisibleCategories[iRow].catID),
                                               sizeof CI_TBL_CHAPT );
                            }
                            rDstColumn.SetStatus( pbDst, CTableColumn::StoreStatusOK );
                            rDstColumn.SetStatus( pbDst, CTableColumn::StoreStatusOK );
                        }
                        else
                        {
                            _pChild->LokGetOneColumn( _aVisibleCategories[iRow].widFirst,
                                                      rDstColumn,
                                                      pbDst,
                                                      rGetParams.GetVarAllocator() );
                        }
                    }

                    rwidLastRowTransferred = _aVisibleCategories[iRow].catID;
                    rGetParams.IncrementRowCount();

                    if ( rGetParams.GetFwdFetch() )
                        iRow++;
                    else
                    {
                        if (iRow == 0)
                            break;
                        iRow--;
                    }
                }

                if ( rGetParams.RowsToTransfer() > 0 )
                {
                    if ( 0 == rGetParams.RowsTransferred() )
                        sc = DB_E_BADSTARTPOSITION;
                    else
                        sc = DB_S_ENDOFROWSET;
                }
            }
        }
    }
    CATCH( CException, e )
    {
        sc = e.GetErrorCode();

        Win4Assert( E_OUTOFMEMORY == sc ||
                    STATUS_BUFFER_TOO_SMALL == sc ); 

        if ( E_OUTOFMEMORY == sc && rGetParams.RowsTransferred() > 0)
            sc = DB_S_BLOCKLIMITEDROWS;
    }
    END_CATCH;

    return sc;
} 

void       CCategorize::RestartPosition(
    CI_TBL_CHAPT           chapt)
{
    SetCurrentPosition( chapt, WORKID_TBLBEFOREFIRST );
    CTableSource::RestartPosition( chapt );
}

SCODE CCategorize::LocateRelativeRow(
    WORKID          widStart,
    CI_TBL_CHAPT    chapt,
    DBROWOFFSET     cRowsToMove,
    WORKID &        rwidRowOut,
    DBROWOFFSET &   rcRowsResidual)
{
    CLock lock( _mutex );

    if ( widStart == WORKID_TBLBEFOREFIRST && cRowsToMove > 0 )
    {
        widStart = WORKID_TBLFIRST;
        cRowsToMove--;
    }
    else if ( widStart == WORKID_TBLAFTERLAST && cRowsToMove < 0 )
    {
        widStart = WORKID_TBLLAST;
        cRowsToMove++;
    }
    else if ( WORKID_TBLAFTERLAST   == widStart ||
              WORKID_TBLBEFOREFIRST == widStart )
    {
        rwidRowOut = widStart;
        rcRowsResidual = cRowsToMove;
        return S_OK;
    }

    ULONG iRow;

    if ( WORKID_TBLFIRST == widStart )
    {
        if ( _isCategorized() && DB_NULL_HCHAPTER != chapt )
            iRow = _FindCategory( _pParent->GetFirstWorkid( chapt ) );
        else
            iRow = 0;
    }
    else if ( WORKID_TBLLAST == widStart )
    {
        if ( _isCategorized() && DB_NULL_HCHAPTER != chapt )
        {
            iRow = _FindCategory( _pParent->GetFirstWorkid( chapt ) );
            iRow += _pParent->GetRowCount( chapt );
        }
        else
        {
            iRow = _aVisibleCategories.Count();
        }

        iRow--;
    }
    else
    {
        iRow = _FindCategory( widStart );
    }

    rcRowsResidual = cRowsToMove + iRow;

    if (rcRowsResidual < 0)
    {
        rwidRowOut = WORKID_TBLBEFOREFIRST;
        tbDebugOut(( DEB_ITRACE,
                     "category table LocateRelativeRow off beginning of table\n" ));
    }
    else if ( (ULONG) rcRowsResidual >= _aVisibleCategories.Count() )
    {
        rwidRowOut = WORKID_TBLAFTERLAST;
        rcRowsResidual -= _aVisibleCategories.Count();

        tbDebugOut(( DEB_ITRACE,
                     "category table LocateRelativeRow off end of table\n" ));
    }
    else
    {
        rwidRowOut = (WORKID) _aVisibleCategories[ (unsigned) rcRowsResidual ].catID;
        rcRowsResidual = 0;
    }

    return S_OK;
} 

unsigned CCategorize::LokAssignCategory(
    CCategParams & prm)
{
    unsigned category = chaptInvalid;
    unsigned iEntry = 0xffffffff;

    CDynArrayInPlace<CCategory> & array = _WritableArray();

    if ( widInvalid != prm.widPrev &&
         widInvalid != prm.widNext &&
         prm.catPrev == prm.catNext )
    {

        _IncrementRowCount( prm.catPrev );
        return prm.catPrev;
    }
    else if ( widInvalid == prm.widPrev &&
              widInvalid == prm.widNext )
    {

        Win4Assert( 0 == array.Count() );
        CCategory cat( prm.widRow );
        array[ 0 ] = cat;
        category = _GenCategory();
        array[ 0 ].catID = category;
        iEntry = 0;
    }
    else if ( widInvalid == prm.widPrev )
    {

        if ( prm.icmpNext > _iSpec )
        {

            category = prm.catNext;
            iEntry = _FindWritableCategory( category );
            array[ iEntry ].widFirst = prm.widRow;
            array[ iEntry ].cRows++;

            return category;
        }
        else
        {

            iEntry = _FindWritableCategory( prm.catNext );
            category = _InsertNewCategory( prm.widRow, iEntry );
        }
    }
    else if ( widInvalid == prm.widNext )
    {

        if ( prm.icmpPrev <= _iSpec )
        {

            iEntry = 1 + _FindWritableCategory( prm.catPrev );
            category = _InsertNewCategory( prm.widRow, iEntry );
        }
        else
        {

            _IncrementRowCount( prm.catPrev );
            return prm.catPrev;
        }
    }
    else
    {

        if ( prm.icmpPrev > _iSpec )
        {

            _IncrementRowCount( prm.catPrev );
            return prm.catPrev;
        }
        else if ( prm.icmpNext > _iSpec )
        {

            iEntry = _FindWritableCategory( prm.catNext );

            array[ iEntry ].widFirst = prm.widRow;
            array[ iEntry ].cRows++;

            return prm.catNext;
        }
        else
        {

            iEntry = _FindWritableCategory( prm.catNext ) ;
            category = _InsertNewCategory( prm.widRow, iEntry );
        }
    }

    if ( _isCategorized() )
    {
        Win4Assert( category != chaptInvalid );
        Win4Assert( iEntry != 0xffffffff );

        CCategParams prnt = prm;
        prnt.widRow = category;

        if ( 0 == iEntry )
            prnt.widPrev = widInvalid;
        else
        {
            prnt.widPrev = array[ iEntry - 1 ].catID;
            prnt.catPrev = array[ iEntry - 1 ].catParent;
        }

        if ( iEntry < ( array.Count() - 1 ) )
        {
            prnt.widNext = array[ iEntry + 1 ].catID;
            prnt.catNext = array[ iEntry + 1 ].catParent;
        }
        else
            prnt.widNext = widInvalid;

        array[ iEntry ].catParent = _pParent->LokAssignCategory( prnt );
    }

    return category;
} 

unsigned CCategorize::_FindCategory(
    CI_TBL_CHAPT cat )
{
    unsigned cCategories = _aVisibleCategories.Count();

    if ( _iFindHint < cCategories )
    {
        if ( cat == _aVisibleCategories[ _iFindHint ].catID )
            return _iFindHint;

        unsigned iHintPlus = _iFindHint + 1;

        if ( ( iHintPlus < cCategories ) &&
             ( cat == _aVisibleCategories[ iHintPlus ].catID ) )
        {
            _iFindHint++;
            return _iFindHint;
        }
    }

    for ( unsigned i = 0; i < cCategories; i++ )
    {
        if ( cat == _aVisibleCategories[ i ].catID )
        {
            _iFindHint = i;
            return i;
        }
    }

    THROW( CException( DB_E_BADCHAPTER ) );
    return 0;
} 

unsigned CCategorize::_FindWritableCategory(
    CI_TBL_CHAPT cat )
{
    CDynArrayInPlace<CCategory> & array = _WritableArray();

    for ( unsigned i = 0; i < array.Count(); i++ )
        if ( cat == array[ i ].catID )
            return i;

    Win4Assert( !"_FindWritableCategory failed" );
    THROW( CException( DB_E_BADCHAPTER ) );
    return 0;
} 

SCODE CCategorize::GetRowsAt(
    HWATCHREGION     hRegion,
    WORKID           widStart,
    CI_TBL_CHAPT     chapt,
    DBROWOFFSET      cRowsToMove,
    CTableColumnSet  const & rOutColumns,
    CGetRowsParams & rGetParams,
    WORKID &         rwidLastRowTransferred )
{
    CLock lock( _mutex );

    DBROWOFFSET cRowsResidual;

    SCODE scRet = LocateRelativeRow( widStart,
                                     chapt,
                                     cRowsToMove,
                                     widStart,
                                     cRowsResidual);

    Win4Assert( !FAILED( scRet ) );

    if ( cRowsResidual )
    {
        Win4Assert ( WORKID_TBLAFTERLAST   == widStart ||
                     WORKID_TBLBEFOREFIRST == widStart );
        return DB_E_BADSTARTPOSITION;
    }

    scRet = GetRows( hRegion,
                     widStart,
                     chapt,
                     rOutColumns,
                     rGetParams,
                     rwidLastRowTransferred);
    return scRet;
} 

SCODE CCategorize::GetRowsAtRatio(
    HWATCHREGION            hRegion,
    ULONG                   num,
    ULONG                   denom,
    CI_TBL_CHAPT            chapt,
    CTableColumnSet const & rOutColumns,
    CGetRowsParams &        rGetParams,
    WORKID &                rwidLastRowTransferred )
{
    CLock lock( _mutex );

    if ( 0 == denom || num > denom )
        QUIETTHROW( CException(DB_E_BADRATIO) );

    ULONG cRows;
    if ( _isCategorized() && DB_NULL_HCHAPTER != chapt )
        cRows = _pParent->GetRowCount( chapt );
    else
        cRows = _aVisibleCategories.Count();

    ULONG cRowsFromFront = (ULONG) ( ( (unsigned _int64) num *
                                       (unsigned _int64) cRows ) /
                                     ( unsigned _int64 ) denom );

    if ( cRowsFromFront == cRows )
    {

        rwidLastRowTransferred = WORKID_TBLAFTERLAST;
        return DB_S_ENDOFROWSET;
    }

    return GetRowsAt( hRegion, WORKID_TBLFIRST, chapt, (LONG) cRowsFromFront,
                      rOutColumns, rGetParams, rwidLastRowTransferred );
} 

void CCategorize::RemoveRow(
    CI_TBL_CHAPT   chapt,
    WORKID         wid,
    WORKID         widNext )
{
    CLock lock( _mutex );

    CDynArrayInPlace<CCategory> & array = _WritableArray();

    unsigned iEntry = _FindWritableCategory( chapt );
    array[ iEntry ].cRows--;

    if ( 0 == array[ iEntry ].cRows )
    {

        unsigned catParent = array[ iEntry ].catParent;

        array.Remove( iEntry );

        if ( _isCategorized() )
        {

            WORKID widNextCategory;
            if ( ( 0 == array.Count() ) ||
                 ( iEntry >= ( array.Count() - 1) ) )
                widNextCategory = widInvalid;
            else
                widNextCategory = array[ iEntry ].catID;

            _pParent->RemoveRow( catParent,
                                 chapt,
                                 widNextCategory );
        }
    }
    else if ( array[ iEntry ].widFirst == wid )
    {

        array[ iEntry ].widFirst = widNext;

        if ( array[ iEntry ].widGetNextRowsPos == wid )
            array[ iEntry ].widGetNextRowsPos = widNext;
    }
} 

SCODE CCategorize::GetApproximatePosition(
    CI_TBL_CHAPT chapt,
    CI_TBL_BMK   bmk,
    DBCOUNTITEM * piRow,
    DBCOUNTITEM * pcRows )
{
    CLock lock( _mutex );

    if (bmk == widInvalid)
        return DB_E_BADBOOKMARK;

    Win4Assert( bmk != WORKID_TBLBEFOREFIRST && bmk != WORKID_TBLAFTERLAST );

    DBCOUNTITEM iBmkPos = ULONG_MAX, cRows = 0;

    if ( _isCategorized() && DB_NULL_HCHAPTER != chapt )
        cRows = _pParent->GetRowCount( chapt );
    else
        cRows = _aVisibleCategories.Count();

    if ( WORKID_TBLFIRST == bmk )
        iBmkPos = cRows ? 1 : 0;
    else if ( WORKID_TBLLAST == bmk )
        iBmkPos = cRows;
    else
    {
        iBmkPos = _FindCategory( bmk ) + 1;

        if ( _isCategorized() && DB_NULL_HCHAPTER != chapt )
            iBmkPos -= _FindCategory( _pParent->GetFirstWorkid( chapt ) );
    }

    Win4Assert(iBmkPos <= cRows);

    *piRow = iBmkPos;
    *pcRows = cRows;

    return S_OK;
} 

void CCategorize::LokGetOneColumn(
    WORKID                    wid,
    CTableColumn const &      rOutColumn,
    BYTE *                    pbOut,
    PVarAllocator &           rVarAlloc )
{
    unsigned iRow = _FindCategory( wid );

    _pChild->LokGetOneColumn( _aVisibleCategories[iRow].widFirst,
                              rOutColumn,
                              pbOut,
                              rVarAlloc );
}
//+-------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright (C) Microsoft Corporation, 1995 - 2000.
//
//  File:       categ.cxx
//
//  Contents:   Unique categorization class
//
//  Classes:    CCategorize
//
//  History:    30 Mar 95   dlee   Created
//
//--------------------------------------------------------------------------

#include <pch.cxx>
#pragma hdrstop

#include <query.hxx>

#include "tabledbg.hxx"

//+---------------------------------------------------------------------------
//
//  Method:     CCategorize, public
//
//  Synopsis:   Constructor for categorization class
//
//  Arguments:  [rCatSpec]  -- categorization specification for this level
//              [iSpec]     -- 1-based categorization level, where smaller
//                             numbers are higher in the hierarchy.
//              [pParent]   -- categorization object that categorizes rows
//                             on this level (or 0 if none)
//              [mutex]     -- CAsyncQuery's mutex for serialization
//
//  History:    6-1-95   dlee   Created
//
//  Notes:      Category identifiers start at 0x40000000 plus 0x1000 times
//              [iSpec].  The 0x40000000 is so it is obvious to the
//              debugger that it is a category.  The 0x1000*[iSpec] is
//              so that it is obvious what level a category falls into.
//
//----------------------------------------------------------------------------

CCategorize::CCategorize(
    CCategorizationSpec & rCatSpec,
    unsigned              iSpec,
    CCategorize *         pParent,
    CMutexSem &           mutex)
    : _iSpec(                 iSpec ),
      _pParent(               pParent ),
      _pChild(                0 ),
      _mutex(                 mutex ),
      _iCategoryGen(          0x40000000 + ( 0x1000 * iSpec ) ),
      _widCurrent(            WORKID_TBLBEFOREFIRST ),
      _fNotificationsEnabled( FALSE ),
      _iFindHint(             0 ),
      _aDynamicCategories(    0 ),
      _aVisibleCategories(    16 )
{
    if (rCatSpec.Type() != CATEGORIZE_UNIQUE)
        THROW(CException( E_INVALIDARG ));
} //CCategorize


//+---------------------------------------------------------------------------
//
//  Method:     CCategorize::GetRows, public
//
//  Arguments:  [widStart]    - WORKID identifying first row to be
//                              transferred.  If WORKID_TBLFIRST is
//                              used, the transfer will start at the first
//                              row in the segment.
//              [chapt]       - Chapter from which to fetch rows (if chaptered)
//              [pOutColumns] - A CTableColumnSet that describes the
//                              output format of the data table.
//              [rGetParams]  - An CGetRowsParams structure which
//                              describes how many rows are to be fetched and
//                              other parameters of the operation.
//              [rwidLastRowTransferred] - On return, the work ID of
//                                         the last row to be transferred
//                                         from this table.  Can be used to
//                                         initialize widStart on next call.
//
//  Returns:    SCODE - status of the operation.  DB_S_ENDOFROWSET if
//                      widStart is WORKID_TBLAFTERLAST at start of
//                      transfer, or if rwidLastRowTransferred is the
//                      last row in the segment at the end of the transfer.
//
//                      STATUS_BUFFER_TOO_SMALL is returned if the available
//                      space in the out-of-line data was exhausted during
//                      the transfer.
//
//  Notes:      To transfer successive rows, as in GetNextRows, the
//              rwidLastRowTransferred must be advanced by one prior
//              to the next transfer.
//
//  History:    6-1-95   dlee   Created
//
//----------------------------------------------------------------------------

SCODE CCategorize::GetRows(
    HWATCHREGION            hRegion,
    WORKID                  widStart,
    CI_TBL_CHAPT            chapt,
    CTableColumnSet const & rOutColumns,
    CGetRowsParams &        rGetParams,
    WORKID &                rwidLastRowTransferred)
{
    SCODE sc = S_OK;

    TRY
    {
        if (WORKID_TBLAFTERLAST == widStart)
        {
            rwidLastRowTransferred = WORKID_TBLAFTERLAST;
            return DB_S_ENDOFROWSET;
        }
        else if (widInvalid == widStart)
        {
            Win4Assert(! "CCategorize::GetRows widStart is widInvalid");
            return E_FAIL;
        }
        else
        {
            CLock lock( _mutex );

            if ( 0 == _aVisibleCategories.Count() )
                sc = DB_S_ENDOFROWSET;
            else
            {
                unsigned iRow;
                if ( widStart != WORKID_TBLFIRST &&
                     widStart != WORKID_TBLBEFOREFIRST )
                    iRow = _FindCategory( widStart );
                else
                    iRow = 0;

                rwidLastRowTransferred = 0;

                while ( 0 != rGetParams.RowsToTransfer() &&
                        iRow < _aVisibleCategories.Count() )
                {
                    // at the end of the chapter when this level is categorized?

                    if ( ( _isCategorized() ) &&
                         ( DB_NULL_HCHAPTER != chapt ) &&
                         ( _aVisibleCategories[ iRow ].catParent != chapt ) )
                        break; // code below will set sc = DB_S_ENDOFROWSET

                    BYTE* pbDst = (BYTE *) rGetParams.GetRowBuffer();

                    for ( unsigned col = 0; col < rOutColumns.Count(); col++ )
                    {
                        CTableColumn const & rDstColumn = *rOutColumns.Get(col);
                        PROPID pid = rDstColumn.GetPropId();

                        if ( pidChapter == pid || pidWorkId  == pid )
                        {
                            if (rDstColumn.GetStoredType() == VT_VARIANT)
                            {
                                Win4Assert( rDstColumn.GetValueSize() == sizeof VARIANT );
                                CTableVariant * pVarnt = (CTableVariant *) ( pbDst +
                                                         rDstColumn.GetValueOffset() );
                                pVarnt->vt = VT_UI4;
                                pVarnt->ulVal = _aVisibleCategories[iRow].catID;
                            }
                            else
                            {
                                Win4Assert( rDstColumn.GetValueSize() == sizeof CI_TBL_CHAPT );
                                RtlCopyMemory( pbDst + rDstColumn.GetValueOffset(),
                                               &( _aVisibleCategories[iRow].catID),
                                               sizeof CI_TBL_CHAPT );
                            }
                            rDstColumn.SetStatus( pbDst, CTableColumn::StoreStatusOK );
                            rDstColumn.SetStatus( pbDst, CTableColumn::StoreStatusOK );
                        }
                        else
                        {
                            _pChild->LokGetOneColumn( _aVisibleCategories[iRow].widFirst,
                                                      rDstColumn,
                                                      pbDst,
                                                      rGetParams.GetVarAllocator() );
                        }
                    }

                    rwidLastRowTransferred = _aVisibleCategories[iRow].catID;
                    rGetParams.IncrementRowCount();

                    if ( rGetParams.GetFwdFetch() )
                        iRow++;
                    else
                    {
                        if (iRow == 0)
                            break;
                        iRow--;
                    }
                }

                // If we didn't transfer as many rows as requested, we must
                // have run into the end of the table or chapter.

                if ( rGetParams.RowsToTransfer() > 0 )
                {
                    if ( 0 == rGetParams.RowsTransferred() )
                        sc = DB_E_BADSTARTPOSITION;
                    else
                        sc = DB_S_ENDOFROWSET;
                }
            }
        }
    }
    CATCH( CException, e )
    {
        sc = e.GetErrorCode();

        Win4Assert( E_OUTOFMEMORY == sc ||
                    STATUS_BUFFER_TOO_SMALL == sc ); // benign?

        if ( E_OUTOFMEMORY == sc && rGetParams.RowsTransferred() > 0)
            sc = DB_S_BLOCKLIMITEDROWS;
    }
    END_CATCH;

    return sc;
} //GetRows

//+-------------------------------------------------------------------------
//
//  Member:     CCategorize::RestartPosition, public
//
//  Synopsis:   Set next fetch position for the chapter to the start
//
//  Arguments:  [chapt]    - Chapter from which to fetch rows (if chaptered)
//
//  Returns:    SCODE - status of the operation.
//
//--------------------------------------------------------------------------

void       CCategorize::RestartPosition(
    CI_TBL_CHAPT           chapt)
{
    SetCurrentPosition( chapt, WORKID_TBLBEFOREFIRST );
    CTableSource::RestartPosition( chapt );
}


//+---------------------------------------------------------------------------
//
//  Method:     LocateRelativeRow, public
//
//  Synopsis:   Finds a row in the category table.  Since there is only one
//              category segment, we are almost assured of finding the row.
//
//  Arguments:  [widStart]       -- where to start the locate
//              [chapt]          -- parent chapter in which to do the locate
//              [cRowsToMove]    -- rows to skip after [widStart]
//              [rwidRowOut]     -- wid found after locate
//              [rcRowsResidual] -- number of rows left over -- will be 0
//
//  History:    6-1-95   dlee   Created
//
//----------------------------------------------------------------------------

SCODE CCategorize::LocateRelativeRow(
    WORKID          widStart,
    CI_TBL_CHAPT    chapt,
    DBROWOFFSET     cRowsToMove,
    WORKID &        rwidRowOut,
    DBROWOFFSET &   rcRowsResidual)
{
    CLock lock( _mutex );

    if ( widStart == WORKID_TBLBEFOREFIRST && cRowsToMove > 0 )
    {
        widStart = WORKID_TBLFIRST;
        cRowsToMove--;
    }
    else if ( widStart == WORKID_TBLAFTERLAST && cRowsToMove < 0 )
    {
        widStart = WORKID_TBLLAST;
        cRowsToMove++;
    }
    else if ( WORKID_TBLAFTERLAST   == widStart ||
              WORKID_TBLBEFOREFIRST == widStart )
    {
        rwidRowOut = widStart;
        rcRowsResidual = cRowsToMove;
        return S_OK;
    }

    ULONG iRow;

    if ( WORKID_TBLFIRST == widStart )
    {
        if ( _isCategorized() && DB_NULL_HCHAPTER != chapt )
            iRow = _FindCategory( _pParent->GetFirstWorkid( chapt ) );
        else
            iRow = 0;
    }
    else if ( WORKID_TBLLAST == widStart )
    {
        if ( _isCategorized() && DB_NULL_HCHAPTER != chapt )
        {
            iRow = _FindCategory( _pParent->GetFirstWorkid( chapt ) );
            iRow += _pParent->GetRowCount( chapt );
        }
        else
        {
            iRow = _aVisibleCategories.Count();
        }

        iRow--;
    }
    else
    {
        iRow = _FindCategory( widStart );
    }

    rcRowsResidual = cRowsToMove + iRow;

    if (rcRowsResidual < 0)
    {
        rwidRowOut = WORKID_TBLBEFOREFIRST;
        tbDebugOut(( DEB_ITRACE,
                     "category table LocateRelativeRow off beginning of table\n" ));
    }
    else if ( (ULONG) rcRowsResidual >= _aVisibleCategories.Count() )
    {
        rwidRowOut = WORKID_TBLAFTERLAST;
        rcRowsResidual -= _aVisibleCategories.Count();

        tbDebugOut(( DEB_ITRACE,
                     "category table LocateRelativeRow off end of table\n" ));
    }
    else
    {
        rwidRowOut = (WORKID) _aVisibleCategories[ (unsigned) rcRowsResidual ].catID;
        rcRowsResidual = 0;
    }

    return S_OK;
} //LocateRelativeRow

//+---------------------------------------------------------------------------
//
//  Method:     CCategorize::LokAssignCategory, public
//
//  Synopsis:   Assigns a category to a row in a lower level, then calls
//              the parent categorizer to re-categorize the row's category
//              if there is a parent.
//
//  Arguments:  [prm]  -- category parameters.  See tblsink.hxx for more
//                        info about this object.
//
//  History:    6-1-95   dlee   Created
//
//  Notes:      No need to grab the CAsyncQuery lock -- bigtable grabs it
//              up front in PutRow.
//
//----------------------------------------------------------------------------

unsigned CCategorize::LokAssignCategory(
    CCategParams & prm)
{
    unsigned category = chaptInvalid;
    unsigned iEntry = 0xffffffff;

    CDynArrayInPlace<CCategory> & array = _WritableArray();

    if ( widInvalid != prm.widPrev &&
         widInvalid != prm.widNext &&
         prm.catPrev == prm.catNext )
    {
        // new row is between two rows of the same category
        // no need to call parent categorizer -- just return

        _IncrementRowCount( prm.catPrev );
        return prm.catPrev;
    }
    else if ( widInvalid == prm.widPrev &&
              widInvalid == prm.widNext )
    {
        // first row we've ever seen

        Win4Assert( 0 == array.Count() );
        CCategory cat( prm.widRow );
        array[ 0 ] = cat;
        category = _GenCategory();
        array[ 0 ].catID = category;
        iEntry = 0;
    }
    else if ( widInvalid == prm.widPrev )
    {
        // new first row in the next row's category or a new category

        if ( prm.icmpNext > _iSpec )
        {
            // new first row in this (the next row's) category

            category = prm.catNext;
            iEntry = _FindWritableCategory( category );
            array[ iEntry ].widFirst = prm.widRow;
            array[ iEntry ].cRows++;

            return category;
        }
        else
        {
            // insert new category before the next category

            iEntry = _FindWritableCategory( prm.catNext );
            category = _InsertNewCategory( prm.widRow, iEntry );
        }
    }
    else if ( widInvalid == prm.widNext )
    {
        // new category OR
        // new element in previous row's category

        if ( prm.icmpPrev <= _iSpec )
        {
            // new category after previous (may be an insert operation).
            // just because widNext is invalid doesn't mean it doesn't exist.

            iEntry = 1 + _FindWritableCategory( prm.catPrev );
            category = _InsertNewCategory( prm.widRow, iEntry );
        }
        else
        {
            // new element in previous row's category

            _IncrementRowCount( prm.catPrev );
            return prm.catPrev;
        }
    }
    else
    {
        // good rows on either side in different categories, one of either:
        //     new member of previous row's category       OR
        //     new first member of next rows's category    OR
        //     new category

        if ( prm.icmpPrev > _iSpec )
        {
            // new member of previous row's category

            _IncrementRowCount( prm.catPrev );
            return prm.catPrev;
        }
        else if ( prm.icmpNext > _iSpec )
        {
            // new first member of next rows's category

            iEntry = _FindWritableCategory( prm.catNext );

            array[ iEntry ].widFirst = prm.widRow;
            array[ iEntry ].cRows++;

            return prm.catNext;
        }
        else
        {
            // new category

            iEntry = _FindWritableCategory( prm.catNext ) ;
            category = _InsertNewCategory( prm.widRow, iEntry );
        }
    }

    // Not all cases above get to this point.  Several return early if
    // there is no way a parent would care about the operation.

    if ( _isCategorized() )
    {
        Win4Assert( category != chaptInvalid );
        Win4Assert( iEntry != 0xffffffff );

        // Get the parent category.  Use a different CCategParams so original
        // is intact.

        CCategParams prnt = prm;
        prnt.widRow = category;

        if ( 0 == iEntry )
            prnt.widPrev = widInvalid;
        else
        {
            prnt.widPrev = array[ iEntry - 1 ].catID;
            prnt.catPrev = array[ iEntry - 1 ].catParent;
        }

        if ( iEntry < ( array.Count() - 1 ) )
        {
            prnt.widNext = array[ iEntry + 1 ].catID;
            prnt.catNext = array[ iEntry + 1 ].catParent;
        }
        else
            prnt.widNext = widInvalid;

        array[ iEntry ].catParent = _pParent->LokAssignCategory( prnt );
    }

    return category;
} //LokAssignCategory

//+---------------------------------------------------------------------------
//
//  Method:     _FindCategory, private
//
//  Synopsis:   Finds a category in the category array
//
//  Arguments:  [cat]  -- category
//
//  Returns:    index into the category array
//
//  History:    6-1-95   dlee   Created
//
//----------------------------------------------------------------------------

unsigned CCategorize::_FindCategory(
    CI_TBL_CHAPT cat )
{
    unsigned cCategories = _aVisibleCategories.Count();

    // first try the hint and the hint + 1

    if ( _iFindHint < cCategories )
    {
        if ( cat == _aVisibleCategories[ _iFindHint ].catID )
            return _iFindHint;

        unsigned iHintPlus = _iFindHint + 1;

        if ( ( iHintPlus < cCategories ) &&
             ( cat == _aVisibleCategories[ iHintPlus ].catID ) )
        {
            _iFindHint++;
            return _iFindHint;
        }
    }

    // linear search for the category

    for ( unsigned i = 0; i < cCategories; i++ )
    {
        if ( cat == _aVisibleCategories[ i ].catID )
        {
            _iFindHint = i;
            return i;
        }
    }

    THROW( CException( DB_E_BADCHAPTER ) );
    return 0;
} //_FindCategory

//+---------------------------------------------------------------------------
//
//  Method:     _FindWritableCategory, private
//
//  Synopsis:   Finds a category in the updatable category array
//
//  Arguments:  [cat]  -- category
//
//  Returns:    index into the category array
//
//  History:    6-1-95   dlee   Created
//
//  PERFPERF:   Linear search -- we may want to put a hash table over this.
//              How many categories do we expect?
//              Another alternative is to cache the last array entry
//              referenced and use it and the prev/next entries as first
//              guesses.
//
//----------------------------------------------------------------------------

unsigned CCategorize::_FindWritableCategory(
    CI_TBL_CHAPT cat )
{
    CDynArrayInPlace<CCategory> & array = _WritableArray();

    for ( unsigned i = 0; i < array.Count(); i++ )
        if ( cat == array[ i ].catID )
            return i;

    Win4Assert( !"_FindWritableCategory failed" );
    THROW( CException( DB_E_BADCHAPTER ) );
    return 0;
} //_FindWritableCategory

//+---------------------------------------------------------------------------
//
//  Method:     GetRowsAt, public
//
//  Synopsis:   Retrieves rows at a specified location
//
//  Arguments:  [widStart]    -- where to start retrieving rows
//              [chapt]       -- in which rows are retrieved
//              [cRowsToMove] -- offset from widStart
//              [rOutColumns] -- description of output columns
//              [rGetParams]  -- info about the get operation
//              [rwidLastRowTransferred] -- last row retrieved
//
//  Returns:    SCODE
//
//  History:    6-1-95   dlee   Created
//
//----------------------------------------------------------------------------

SCODE CCategorize::GetRowsAt(
    HWATCHREGION     hRegion,
    WORKID           widStart,
    CI_TBL_CHAPT     chapt,
    DBROWOFFSET      cRowsToMove,
    CTableColumnSet  const & rOutColumns,
    CGetRowsParams & rGetParams,
    WORKID &         rwidLastRowTransferred )
{
    CLock lock( _mutex );

    DBROWOFFSET cRowsResidual;

    SCODE scRet = LocateRelativeRow( widStart,
                                     chapt,
                                     cRowsToMove,
                                     widStart,
                                     cRowsResidual);

    Win4Assert( !FAILED( scRet ) );

    if ( cRowsResidual )
    {
        Win4Assert ( WORKID_TBLAFTERLAST   == widStart ||
                     WORKID_TBLBEFOREFIRST == widStart );
        return DB_E_BADSTARTPOSITION;
    }

    scRet = GetRows( hRegion,
                     widStart,
                     chapt,
                     rOutColumns,
                     rGetParams,
                     rwidLastRowTransferred);
    return scRet;
} //GetRowsAt

//+---------------------------------------------------------------------------
//
//  Method:     GetRowsAtRatio, public
//
//  Synopsis:   Retrieves rows at a specified location.  Nothing fuzzy about
//              this -- they're all in memory so be as exact as possible.
//
//  Arguments:  [num]         -- numerator of starting point fraction
//              [denom]       -- denominator of starting point fraction
//              [chapt]       -- in which rows are retrieved
//              [rOutColumns] -- description of output columns
//              [rGetParams]  -- info about the get operation
//              [rwidLastRowTransferred] -- last row retrieved
//
//  Returns:    SCODE
//
//  History:    6-1-95   dlee   Created
//
//----------------------------------------------------------------------------

SCODE CCategorize::GetRowsAtRatio(
    HWATCHREGION            hRegion,
    ULONG                   num,
    ULONG                   denom,
    CI_TBL_CHAPT            chapt,
    CTableColumnSet const & rOutColumns,
    CGetRowsParams &        rGetParams,
    WORKID &                rwidLastRowTransferred )
{
    CLock lock( _mutex );

    if ( 0 == denom || num > denom )
        QUIETTHROW( CException(DB_E_BADRATIO) );

    ULONG cRows;
    if ( _isCategorized() && DB_NULL_HCHAPTER != chapt )
        cRows = _pParent->GetRowCount( chapt );
    else
        cRows = _aVisibleCategories.Count();

    ULONG cRowsFromFront = (ULONG) ( ( (unsigned _int64) num *
                                       (unsigned _int64) cRows ) /
                                     ( unsigned _int64 ) denom );

    if ( cRowsFromFront == cRows )
    {
        // The user is asking to retrieve past the end of table.

        rwidLastRowTransferred = WORKID_TBLAFTERLAST;
        return DB_S_ENDOFROWSET;
    }

    return GetRowsAt( hRegion, WORKID_TBLFIRST, chapt, (LONG) cRowsFromFront,
                      rOutColumns, rGetParams, rwidLastRowTransferred );
} //GetRowsAtRatio

//+---------------------------------------------------------------------------
//
//  Method:     RemoveRow, public
//
//  Synopsis:   Removes a row from the categorization
//
//  Arguments:  [chapt]     -- of row being removed
//              [wid]       -- of removed row in categorized child table
//              [widNext]   -- of row following removed row
//
//  History:    6-1-95   dlee   Created
//
//----------------------------------------------------------------------------

void CCategorize::RemoveRow(
    CI_TBL_CHAPT   chapt,
    WORKID         wid,
    WORKID         widNext )
{
    CLock lock( _mutex );

    CDynArrayInPlace<CCategory> & array = _WritableArray();

    unsigned iEntry = _FindWritableCategory( chapt );
    array[ iEntry ].cRows--;

    if ( 0 == array[ iEntry ].cRows )
    {
        // in case we need parent category later, save it

        unsigned catParent = array[ iEntry ].catParent;

        // last member of category -- remove the category

        array.Remove( iEntry );

        if ( _isCategorized() )
        {
           // notify the parent that a row was deleted

            WORKID widNextCategory;
            if ( ( 0 == array.Count() ) ||
                 ( iEntry >= ( array.Count() - 1) ) )
                widNextCategory = widInvalid;
            else
                widNextCategory = array[ iEntry ].catID;

            _pParent->RemoveRow( catParent,
                                 chapt,
                                 widNextCategory );
        }
    }
    else if ( array[ iEntry ].widFirst == wid )
    {
        // new first member of the category

        array[ iEntry ].widFirst = widNext;

        // removed the GetNextRows() current position row -- fixup

        if ( array[ iEntry ].widGetNextRowsPos == wid )
            array[ iEntry ].widGetNextRowsPos = widNext;
    }
} //RemoveRow

//+---------------------------------------------------------------------------
//
//  Method:     GetApproximatePosition, public
//
//  Synopsis:   Returns the offset of a bookmark in the table/chapter and
//              the number of rows in that table/chapter
//
//  Arguments:  [chapt]   -- of row being queried
//              [bmk]     -- of row being queried
//              [piRow]   -- returns index of row in table/chapter
//              [pcRows]  -- returns count of rows in table/chapter
//
//  History:    6-29-95   dlee   Created
//
//----------------------------------------------------------------------------

SCODE CCategorize::GetApproximatePosition(
    CI_TBL_CHAPT chapt,
    CI_TBL_BMK   bmk,
    DBCOUNTITEM * piRow,
    DBCOUNTITEM * pcRows )
{
    CLock lock( _mutex );

    if (bmk == widInvalid)
        return DB_E_BADBOOKMARK;

    Win4Assert( bmk != WORKID_TBLBEFOREFIRST && bmk != WORKID_TBLAFTERLAST );

    DBCOUNTITEM iBmkPos = ULONG_MAX, cRows = 0;

    if ( _isCategorized() && DB_NULL_HCHAPTER != chapt )
        cRows = _pParent->GetRowCount( chapt );
    else
        cRows = _aVisibleCategories.Count();

    if ( WORKID_TBLFIRST == bmk )
        iBmkPos = cRows ? 1 : 0;
    else if ( WORKID_TBLLAST == bmk )
        iBmkPos = cRows;
    else
    {
        iBmkPos = _FindCategory( bmk ) + 1;

        // position is relative to first member of the category (if any)

        if ( _isCategorized() && DB_NULL_HCHAPTER != chapt )
            iBmkPos -= _FindCategory( _pParent->GetFirstWorkid( chapt ) );
    }

    Win4Assert(iBmkPos <= cRows);

    *piRow = iBmkPos;
    *pcRows = cRows;

    return S_OK;
} //GetApproximatePosition

//+---------------------------------------------------------------------------
//
//  Method:     LokGetOneColumn, public
//
//  Synopsis:   Returns column data for the first item in a category.
//
//  Arguments:  [wid]        -- workid or chapter of the row to be queried
//              [rOutColumn] -- layout of the output data
//              [pbOut]      -- where to write the column data
//              [rVarAlloc]  -- variable data allocator to use
//
//  History:    22-Aug-95   dlee   Created
//
//----------------------------------------------------------------------------

void CCategorize::LokGetOneColumn(
    WORKID                    wid,
    CTableColumn const &      rOutColumn,
    BYTE *                    pbOut,
    PVarAllocator &           rVarAlloc )
{
    unsigned iRow = _FindCategory( wid );

    _pChild->LokGetOneColumn( _aVisibleCategories[iRow].widFirst,
                              rOutColumn,
                              pbOut,
                              rVarAlloc );
} //LokGetOneColumn


