//---------------------------------------------------------------
//
//  For conditions of distribution and use, see copyright notice
//  in Flashpix.h 
// 
//  Copyright (c) 1999 Digital Imaging Group, Inc.
// 
//  Contents: Paging code for MSF
//
//  Classes:  Defined in page.hxx
//
//  Functions:  
//
//----------------------------------------------------------------

#include "msfhead.cxx"



#include "mread.hxx"

//+---------------------------------------------------------------------
//
//  Member: CMSFPage::Byteswap, public
//
//  Synopsis: Byteswap the elments of the page
//
//  Algorithm:  Call the corresponding byteswap routine depending on the 
//              actual type of the Mutli-stream.
//
//----------------------------------------------------------------------

void CMSFPage::ByteSwap(void)
{
    CPagedVector* pVect = GetVector();
    if (pVect->GetParent()->GetHeader()->DiffByteOrder())
    {
        switch (_sid) 
        {
        case SIDDIR:
            ((CDirSect *)GetData())->
                ByteSwap( ((CDirVector*)pVect)->GetSectorSize() );        
        break;
        case SIDFAT:            
        case SIDMINIFAT:
        case SIDDIF:
            ((CFatSect *)GetData())->
                ByteSwap( ((CFatVector*)pVect)->GetSectBlock() );
            break;
        default:
            break;
        }
    }
}

//+---------------------------------------------------------------------------
//
//  Member: CMSFPageTable::CMSFPageTable, public
//
//  Synopsis: CMSFPageTable constructor.
//
//  Arguments:  [pmsParent] -- Pointer to multistream for this page table.
//
//  Notes:  
//
//----------------------------------------------------------------------------

CMSFPageTable::CMSFPageTable( CMStream *const pmsParent,
                              const ULONG cMinPages,
                              const ULONG cMaxPages)
    : _pmsParent(pmsParent),
      _cbSector(pmsParent->GetSectorSize()),
      _cMinPages(cMinPages),
      _cMaxPages(cMaxPages),
      _cActivePages(0),
      _cPages(0),
      _pmpCurrent(NULL),
      _cReferences(1)    
{
}

//+---------------------------------------------------------------------------
//
//  Member: CMSFPage::CMSFPage, public
//
//  Synopsis: CMSFPage default constructor
//
//----------------------------------------------------------------------------

CMSFPage::CMSFPage(CMSFPage *pmp)
{
    if (pmp == NULL)
    {
        SetChain(this, this);
    }
    else
    {
        SetChain(pmp->GetPrev(), pmp);
        GetPrev()->SetNext(this);
        GetNext()->SetPrev(this);
    }

    SetSid(NOSTREAM);
    SetOffset(0);
    SetSect(ENDOFCHAIN);
    SetFlags(0);
    SetVector(NULL);
    _cReferences = 0;
}



//+---------------------------------------------------------------------------
//
//  Member: CMSFPageTable::~CMSFPageTable, public
//
//  Synopsis: CMSFPageTable destructor
//
//----------------------------------------------------------------------------

CMSFPageTable::~CMSFPageTable()
{
    if (_pmpCurrent != NULL)
    {
        CMSFPage *pmp = _pmpCurrent;
        CMSFPage *pmpNext;

	/*
	 * The pages are in a circular linked list. We start freeing with
	 * the current and end, when the next one is the current (again).
	 */
	do {
            pmpNext = pmp->GetNext();
#if DBG == 1
            msfAssert(!pmp->IsInUse() &&
                    aMsg("Active page left at page table destruct time."));

#endif
            free(pmp);
            pmp = pmpNext;
        } while (pmp != _pmpCurrent);
    }
}


//+---------------------------------------------------------------------------
//
//  Member: CMSFPageTable::Init, public
//
//  Synopsis: Initialize a CMSFPageTable
//
//  Arguments:  [cPages] -- Number of pages to preallocate.
//
//  Returns:  Appropriate status code
//
//  Notes:  
//
//----------------------------------------------------------------------------

SCODE CMSFPageTable::Init(void)
{
    SCODE sc = S_OK;

    msfDebugOut((DEB_ITRACE, "In  CMSFPageTable::Init:%p()\n", this));

    for (ULONG i = 0; i < _cMinPages; i++)
    {
        CMSFPage *pmp;

        msfMem(pmp = GetNewPage());
        _pmpCurrent = pmp;
    }
    _cPages = _cMinPages;
    _cActivePages = 0;

    msfDebugOut((DEB_ITRACE, "Out CMSFPageTable::Init\n"));

 Err:

    return sc;
}



//+---------------------------------------------------------------------------
//
//  Member: CMSFPageTable::FlushPage, public
//
//  Synopsis: Flush a page
//
//  Arguments:  [pmp] -- Pointer to page to flush
//
//  Returns:  Appropriate status code
//
//----------------------------------------------------------------------------

SCODE CMSFPageTable::FlushPage(CMSFPage *pmp)
{
    SCODE sc = S_OK;

    pmp->AddRef();

    CMStream *pms;
    pms = pmp->GetVector()->GetParent();

    //Flush the page, reset the dirty bit.

    msfAssert((pmp->GetSect() != ENDOFCHAIN) &&
            aMsg("Page location not set - don't know where to flush to."));

    ULONG ulRet;

    ILockBytes *pilb;
    ULARGE_INTEGER ul;
    ULISet32(ul, ConvertSectOffset(
            pmp->GetSect(),
            0,
            pms->GetSectorShift()));

    pilb = pms->GetILB();

    pmp->ByteSwap();            // convert to disk format 
                                // (if neccessary)                 
    msfHChk(pilb->WriteAt(
            ul,
            (BYTE *)(pmp->GetData()),
            _cbSector,
            &ulRet));
    
    pmp->ByteSwap();            // convert to back to machine format 
                                // (if neccessary)
    pmp->ResetDirty();

 Err:
    pmp->Release();
    return sc;
}


//+---------------------------------------------------------------------------
//
//  Member: CMSFPageTable::GetFreePage, public
//
//  Synopsis: Return a pointer to a free page.
//
//  Arguments:  [ppmp] -- Pointer to storage for return pointer
//
//  Returns:  Appropriate status code
//
//  Notes:  
//
//----------------------------------------------------------------------------

SCODE CMSFPageTable::GetFreePage(CMSFPage **ppmp)
{
    SCODE sc = S_OK;
    CMSFPage *pmp;
    if (_cPages > _cActivePages)
    {
        //We have some unused page already allocated.  Find and return it.
        pmp = _pmpCurrent;

        do
        {
            pmp = pmp->GetNext();
        }
        while ((pmp != _pmpCurrent) && (pmp->GetSid() != NOSTREAM));

        msfAssert((pmp->GetSid() == NOSTREAM) &&
                aMsg("Expected empty page, none found."));

        *ppmp = pmp;
        _cActivePages++;
    }
    else if (_cPages == _cMaxPages)
    {
        msfMem(pmp = FindSwapPage());
        msfDebugOut((DEB_IERROR, "Got swap page %p\n",pmp));

        msfAssert((pmp->GetVector() != NULL) &&
                aMsg("FindSwapPage returned unowned page."));

        msfDebugOut((DEB_IERROR, "Freeing page %lu from vector %p\n",
                pmp->GetOffset(), pmp->GetVector()));


        if (pmp->IsDirty())
        {
            msfChk(FlushPage(pmp));
            msfAssert(!pmp->IsDirty() &&
                    aMsg("Page remained dirty after flush call"));
        }

        pmp->GetVector()->FreeTable(pmp->GetOffset());
#if DBG == 1
        pmp->SetVector(NULL);
#endif
        *ppmp = pmp;
    }
    else
    {
        //Create a new page and return it.
        pmp = GetNewPage();
        if (pmp != NULL)
        {
            *ppmp = pmp;
            _cActivePages++;
            _cPages++;
        }
        else
        {
            msfMem(pmp = FindSwapPage());
            if (pmp->IsDirty())
            {
                msfChk(FlushPage(pmp));
                msfAssert(!pmp->IsDirty() &&
                        aMsg("Page remained dirty after flush call"));
            }
            pmp->GetVector()->FreeTable(pmp->GetOffset());
#if DBG == 1
            pmp->SetVector(NULL);
#endif
            *ppmp = pmp;
        }
    }

 Err:
    return sc;
}


//+---------------------------------------------------------------------------
//
//  Member: CMSFPageTable::FindPage, public
//
//  Synopsis: Find and return a given page
//
//  Arguments:  [ppv] -- Pointer to vector of page to return
//              [sid] -- SID of page to return
//              [ulOffset] -- Offset of page to return
//              [ppmp] -- Location to return pointer
//
//  Returns:  Appropriate status code
//
//  Notes:  
//
//----------------------------------------------------------------------------

SCODE CMSFPageTable::FindPage(
        CPagedVector *ppv,
        SID sid,
        ULONG ulOffset,
        CMSFPage **ppmp)
{
    SCODE sc;
    CMSFPage *pmp = _pmpCurrent;

    do
    {
        if ((pmp->GetVector() == ppv) && (pmp->GetOffset() == ulOffset))
        {
            //Bingo!

            *ppmp = pmp;
            return STG_S_FOUND;
        }

        pmp = pmp->GetNext();
    }
    while (pmp != _pmpCurrent);

    //The page isn't currently in memory.  Get a free page and
    //bring it into memory.

    msfChk(GetFreePage(&pmp));

    msfAssert((pmp->GetVector() == NULL) &&
            aMsg("Attempting to reassign owned page."));
    pmp->SetVector(ppv);
    pmp->SetSid(sid);
    pmp->SetOffset(ulOffset);
    pmp->SetSect(ENDOFCHAIN);

    *ppmp = pmp;

 Err:
    return sc;
}


//+---------------------------------------------------------------------------
//
//  Member: CMSFPageTable::GetPage, public
//
//  Synopsis: Find and return a given page
//
//  Arguments:  [sid] -- SID of page to return
//              [ulOffset] -- Offset of page to return
//              [ppmp] -- Location to return pointer
//
//  Returns:  Appropriate status code
//
//  Notes:  
//
//----------------------------------------------------------------------------

SCODE CMSFPageTable::GetPage(
        CPagedVector *ppv,
        SID sid,
        ULONG ulOffset,
        CMSFPage **ppmp)
{
    SCODE sc;

    *ppmp = NULL;
    msfChk(FindPage(ppv, sid, ulOffset, ppmp));

    (*ppmp)->AddRef();

    if (sc != STG_S_FOUND)
    {
        ULONG ulRet;
        SECT sect;

        msfChk(ppv->GetParent()->GetSect(sid, ulOffset, &sect));
        (*ppmp)->SetSect(sect);

        CMStream *pms = (*ppmp)->GetVector()->GetParent();
        
        ULARGE_INTEGER ul;
        ULISet32(ul, ConvertSectOffset(
                (*ppmp)->GetSect(),
                0,
                pms->GetSectorShift()));

        msfAssert(pms->GetILB() != NULL &&
                  aMsg("NULL ILockBytes - missing SetAccess?"));

        msfHChk(pms->GetILB()->ReadAt(ul, (BYTE *)((*ppmp)->GetData()),
                _cbSector, &ulRet));
        (*ppmp)->ByteSwap();
    }

 Err:
    if (*ppmp != NULL)
    {
        (*ppmp)->Release();
    }

    return sc;
}



//+---------------------------------------------------------------------------
//
//  Member: CMSFPageTable::ReleasePage, public
//
//  Synopsis: Release a given page
//
//  Arguments:  [sid] -- SID of page to release
//              [ulOffset] -- Offset of page to release
//
//----------------------------------------------------------------------------

void CMSFPageTable::ReleasePage(CPagedVector *ppv, SID sid, ULONG ulOffset)
{
    SCODE sc;
    CMSFPage *pmp;

    sc = FindPage(ppv, sid, ulOffset, &pmp);

    if (SUCCEEDED(sc))
    {
        pmp->Release();
    }
}


//+---------------------------------------------------------------------------
//
//  Member: CMSFPageTable::Flush, public
//
//  Synopsis: Flush dirty pages to disk
//
//  Returns:  Appropriate status code
//
//----------------------------------------------------------------------------

SCODE CMSFPageTable::Flush(void)
{
    SCODE sc = S_OK;

    CMSFPage *pmp = _pmpCurrent;

    //We use pmpLast in case FlushPage changes _pmpCurrent.
    CMSFPage *pmpLast = _pmpCurrent;

    do
    {
        if ((pmp->IsDirty()) && !(pmp->IsInUse()))
        {
            msfChk(FlushPage(pmp));
        }

        pmp = pmp->GetNext();

    }
    while (pmp != pmpLast);

 Err:
    return sc;
}



//+---------------------------------------------------------------------------
//
//  Member: CMSFPageTable::FreePages, public
//
//  Synopsis: Free all the pages associated with a vector.
//
//  Arguments:  [ppv] -- Pointer to vector to free pages for.
//
//----------------------------------------------------------------------------

void CMSFPageTable::FreePages(CPagedVector *ppv)
{
    CMSFPage *pmp = _pmpCurrent;

    do
    {
        if (pmp->GetVector() == ppv)
        {
            pmp->SetSid(NOSTREAM);
            pmp->SetVector(NULL);
            pmp->ResetDirty();
            _cActivePages--;
        }
        pmp = pmp->GetNext();
    }
    while (pmp != _pmpCurrent);

}


//+---------------------------------------------------------------------------
//
//  Member: CMSFPageTable::FindSwapPage, private
//
//  Synopsis: Find a page to swap out.
//
//  Arguments:  None.
//
//  Returns:  Pointer to page to swap out.
//
//----------------------------------------------------------------------------

CMSFPage * CMSFPageTable::FindSwapPage(void)
{
#if DBG == 1
    ULONG cpInUse = 0;
#endif

    while (TRUE)
    {
        if (!_pmpCurrent->IsInUse())
        {
            DWORD dwFlags;

            dwFlags = _pmpCurrent->GetFlags();
            _pmpCurrent->SetFlags(dwFlags & ~FB_TOUCHED);
            _pmpCurrent = _pmpCurrent->GetNext();

            if (!(dwFlags & FB_TOUCHED))
            {
                return _pmpCurrent->GetPrev();
            }
        }
        else
        {
            _pmpCurrent = _pmpCurrent->GetNext();
        }
#if DBG == 1
        cpInUse++;
        msfAssert((cpInUse < 3 * _cPages) &&
                aMsg("No swappable pages."));
#endif

    }
}



