//-------------------------------------------------------------------------
//
//  For conditions of distribution and use, see copyright notice
//  in Flashpix.h 
// 
//  Copyright (c) 1999 Digital Imaging Group, Inc.
// 
//  Contents:   Allocation functions for MStream
//
//  Classes:    None. (defined in fat.hxx)
//
//--------------------------------------------------------------------------

#include "msfhead.cxx"


#include "h/difat.hxx"
#include "h/sstream.hxx"
#include "mread.hxx"


//+-------------------------------------------------------------------------
//
//  Method:     CFatSect::Init, public
//
//  Synopsis:   CFatSect initialization function
//
//  Effects:    [uEntries] -- Number of entries in sector
//
//  Algorithm:  Allocate an array of SECT with size uEntries from
//              the heap.
//
//--------------------------------------------------------------------------

SCODE CFatSect::Init(FSOFFSET uEntries)
{
    msfDebugOut((DEB_FAT,"In CFatSect constructor\n"));

    //This assumes that FREESECT is always 0xFFFFFFFF
    memset(GetSects(), 0xFF, uEntries * sizeof(SECT));

    msfDebugOut((DEB_FAT,"Out CFatSect constructor\n"));
    return S_OK;
}



//+-------------------------------------------------------------------------
//
//  Method:     CFatSect::InitCopy, public
//
//  Synopsis:   Initialization function for copying FatSects
//
//  Arguments:  [fsOld] -- Reference to FatSect to be copies
//
//  Returns:    S_OK if call completed successfully.
//
//  Algorithm:  Allocate a new array of SECT and copy old
//                  information in.
//
//--------------------------------------------------------------------------

SCODE CFatSect::InitCopy(USHORT uSize, CFatSect& fsOld)
{
    msfDebugOut((DEB_FAT,"In CFatSect copy constructor\n"));
    msfDebugOut((DEB_FAT,"This = %p,  fsOld = %p\n",this,&fsOld));

    msfDebugOut((DEB_FAT,"Sector size is %u sectors\n",uSize));

    memcpy(GetSects(),fsOld.GetSects(),sizeof(SECT)*uSize);
    msfDebugOut((DEB_FAT,"Out CFatSect copy constructor\n"));
    return S_OK;
}


//+-------------------------------------------------------------------------
//
//  Method:     CFat::CFat, public
//
//  Synopsis:   CFat constructor.
//
//  Arguments:  [pmsParent] -- Pointer to parent multistream.
//
//  Algorithm:  Set uFatEntries to match parent MS header info.
//              Initialize all member variables.
//
//  Notes:
//
//--------------------------------------------------------------------------

CFat::CFat(SID sid, USHORT cbSector, USHORT uSectorShift)
: _fv( sid,
       (USHORT) (cbSector >> 2),  // 4 bytes per entry
       (USHORT) (cbSector >> 2) ), 
  _pmsParent(NULL),
  _sid(sid),
  // left shift this amount for FAT
  _uFatShift((USHORT) (uSectorShift - 2) ),   
  // (# entries per sector) - 1
  _uFatMask( (USHORT) ((cbSector >> 2) - 1)), 
  _cfsTable(0),
  _ulFreeSects(0),
  _sectFirstFree( (SECT) 0),
  _sectMax(ENDOFCHAIN)
{
}




//+---------------------------------------------------------------------------
//
//  Member: CFat::Empty, public
//
//  Synopsis: Empty all the control structures of this instance
//
//  Arguments:  None.
//
//  Returns:  void.
//
//----------------------------------------------------------------------------

void CFat::Empty(void)
{
    _fv.Empty();
    _pmsParent = NULL;
    _cfsTable = 0;
    _ulFreeSects = MAX_ULONG;
    _sectFirstFree = 0;
    _sectMax = ENDOFCHAIN;
}


//+-------------------------------------------------------------------------
//
//  Method:     CFat::~CFat, public
//
//  Synopsis:   CFat Destructor
//
//  Algorithm:  delete dynamically allocated storage
//
//  Notes:
//
//--------------------------------------------------------------------------

CFat::~CFat()
{
    msfDebugOut((DEB_FAT,"In CFat destructor.  Size of fat is %lu\n",_cfsTable));

    msfDebugOut((DEB_FAT,"Exiting CFat destructor\n"));
}


//+-------------------------------------------------------------------------
//
//  Member:     CFat::GetFree, private
//
//  Synposis:   Locate and return a free sector in the FAT
//
//  Effects:    May modify full bit on full sectors
//
//  Arguments:  [psectRet] -- Pointer to return value
//
//  Returns:    S_OK if call completed successfully.
//
//  Algorithm:  Do a linear search of all tables until a free sector is
//              found.  If all tables are full, extend the FAT by one
//              sector.
//
//  Notes:
//
//---------------------------------------------------------------------------

SCODE CFat::GetFree(ULONG ulCount, SECT *psectRet)
{
    FSINDEX ipfs;
    FSOFFSET isect;
    SECT sectRetval;
    SCODE sc;
    
    SECT sectLast = ENDOFCHAIN;
    FSINDEX ipfsLast;
    FSOFFSET isectLast;
    
    *psectRet = ENDOFCHAIN;
    
    
    while (TRUE)
    {
        if (_ulFreeSects == MAX_ULONG)
        {
            msfChk(CountFree(&_ulFreeSects));
        }
#if DBG == 1
        else
        {
            ULONG ulFree;
            msfChk(CountFree(&ulFree));
            msfAssert((ulFree == _ulFreeSects) &&
                    aMsg("Free count doesn't match cached value."));
        }
#endif
        
        while (ulCount > _ulFreeSects)
        {
#if DBG == 1
            ULONG ulFree = _ulFreeSects;
#endif
            
            msfChk(Resize(_cfsTable +
                    ((ulCount - _ulFreeSects + _fv.GetSectTable() - 1) >>
                     _uFatShift)));
            
#if DBG == 1
            msfAssert(_ulFreeSects > ulFree &&
                aMsg("Number of free sectors didn't increase after Resize."));
#endif
        }
        
        FSOFFSET isectStart;
        FSINDEX ipfsStart;
        
        SectToPair(_sectFirstFree, &ipfsStart, &isectStart);
        
        for (ipfs = ipfsStart; ipfs < _cfsTable; ipfs++)
        {
            CVectBits *pfb = _fv.GetBits(ipfs);
            if ((pfb == NULL) || (!pfb->full))
            {
                CFatSect *pfs;
                msfChk(_fv.GetTable(ipfs, FB_NONE, &pfs));
                if (pfb != NULL)
                {
                    isectStart = pfb->firstfree;
                }
                
                for (isect = isectStart; isect < _fv.GetSectTable(); isect++)
                {
                    SECT sectCurrent = pfs->GetSect(isect);
                    SECT sectNew = PairToSect(ipfs, isect);
                    
                    
                    if (sectCurrent == FREESECT)
                    {
                        msfAssert(_ulFreeSects != MAX_ULONG &&
                                aMsg("Free sect count not set"));
                        
                        _ulFreeSects--;
                        
                        sectRetval = sectNew;
                        
                        if (pfb != NULL)
                        {
                            olAssert(isect+1 < (FSOFFSET)USHRT_MAX);
                            pfb->firstfree = (USHORT) (isect + 1);
                        }
                        
                        msfAssert(sectRetval >= _sectFirstFree &&
                                aMsg("Found free sector before _sectFirstFree"));
                        _sectFirstFree = sectRetval + 1;
                        
                        pfs->SetSect(isect, ENDOFCHAIN);
                        msfChkTo(Err_Rel, _fv.SetDirty(ipfs));
                        
                        if (sectLast != ENDOFCHAIN)
                        {
                            if (ipfsLast == ipfs)
                            {
                                pfs->SetSect(isectLast, sectRetval);
                            }
                            else
                            {
                                CFatSect *pfsLast;
                                
                                msfChkTo(Err_Rel, _fv.GetTable(
                                        ipfsLast,
                                        FB_DIRTY,
                                        &pfsLast));
                                
                                pfsLast->SetSect(isectLast, sectRetval);
                                _fv.ReleaseTable(ipfsLast);
                            }
                        }
                        
                        if (*psectRet == ENDOFCHAIN)
                        {
                            *psectRet = sectRetval;
                        }
                        
                        ulCount--;
                        
                        if (ulCount == 0)
                        {
                            _fv.ReleaseTable(ipfs);

                            if (sectRetval >= _sectMax)
                            {
                                _sectMax = sectRetval + 1;
                            }
                            return S_OK;
                        }
                        else
                        {
                            sectLast = sectRetval;
                            ipfsLast = ipfs;
                            isectLast = isect;
                        }
                    }
                }
                _fv.ReleaseTable(ipfs);
                if (pfb != NULL)
                {
                    pfb->full = TRUE;
                }
            }
            isectStart = 0;
        }
        if (sectRetval >= _sectMax)
        {
            _sectMax = sectRetval + 1;
        }
    }
    msfAssert(0 &&
            aMsg("GetFree exited improperly."));
    sc = STG_E_ABNORMALAPIEXIT;
    
 Err:
    return sc;
    
 Err_Rel:
    _fv.ReleaseTable(ipfs);
    return sc;
}



//+-------------------------------------------------------------------------
//
//  Member:     CFat::GetLength, public
//
//  Synposis:   Return the length of a fat chain.
//
//  Arguments:  [sect] -- Sector to begin count at.
//
//  Returns:    Length of the chain, in sectors
//
//  Algorithm:  Traverse the chain until ENDOFCHAIN is reached.
//
//  Notes:
//
//---------------------------------------------------------------------------

SCODE CFat::GetLength(SECT sect, ULONG * pulRet)
{
    msfDebugOut((DEB_FAT,"In CFat::GetLength(%lu)\n",sect));
    SCODE sc = S_OK;

    ULONG csect = 0;

    while (sect != ENDOFCHAIN)
    {
        msfChk(GetNext(sect, &sect));
        csect++;
    }

    msfDebugOut((DEB_FAT,"FAT: GetLength returned %u\n",csect));
    *pulRet =  csect;
Err:
    return sc;
}



//+-------------------------------------------------------------------------
//
//  Member:     CFat::Init, public
//
//  Synposis:   Sets up a FAT, reading data from an existing stream
//
//  Effects:    Changes all _apfsTable entries, _cfsTable, and all
//              flags fields
//
//  Arguments:  None.
//
//  Returns:    S_OK if call completed OK.
//
//  Algorithm:  Read size from first FAT in stream.
//              Resize array to necessary size.
//              Read in FAT sectors sequentially.
//
//  Notes:
//
//---------------------------------------------------------------------------

SCODE CFat::Init(CMStream *pmsParent, FSINDEX cFatSect, BOOL UNUSED_PARM(fConvert))
{
    SCODE sc;
    UNREFERENCED_PARM(fConvert);
    msfDebugOut((DEB_FAT,"CFat::setup thinks the FAT is size %lu\n",cFatSect));

    _pmsParent = pmsParent;

    msfChk(_fv.Init(_pmsParent, cFatSect));

    _cfsTable = cFatSect;

    _ulFreeSects = MAX_ULONG;

Err:
    return sc;
}


//+-------------------------------------------------------------------------
//
//  Method:     CFat::InitConvert, public
//
//  Synopsis:   Init function used for conversion
//
//  Arguments:  [sectData] -- number of sectors used by file
//
//  Returns:    S_OK if call completed OK.
//
//  Algorithm:  *Finish This*
//
//  Notes:
//
//--------------------------------------------------------------------------

SCODE CFat::InitConvert(CMStream *pmsParent, SECT sectData)
{
    SCODE sc;
    msfDebugOut((DEB_FAT,"Doing conversion\n"));
    _pmsParent = pmsParent;

    msfAssert((sectData != 0) &&
            aMsg("Attempt to convert zero length file."));

    SECT sectMax = 0;
    FSINDEX csectFat = 0;
    FSINDEX csectLast;

    if (_sid == SIDFAT)
    {
        SECT sectTotal;

        //Since the fat needs to represent itself, we can't determine
        //   the actual number of sectors needed in one pass.  We
        //   therefore loop, factoring in the number of fat sectors
        //   at each iteration, until we reach a stable state.
        //
        //As an example, consider the case where each fat sector represents
        //   128 sectors and the file being converted is 128 sectors long.
        //   There will be no DIFat - therefore, we have 128 sectors needed
        //   on the first pass, which will require 1 fat sector to
        //   represent them.  On the second pass, we discover that we
        //   actually need 2 fat sectors, since we now have 129 total
        //   sectors to allocate space for.  The third pass will result
        //   in a stable state.
        do
        {
            csectLast = csectFat;
            sectTotal = sectData + _pmsParent->GetHeader()->GetDifLength() +
                csectFat + 1;
            csectFat = (sectTotal + _fv.GetSectTable() - 1) >> _uFatShift;
        }
        while (csectLast != csectFat);
        sectMax = sectData + _pmsParent->GetHeader()->GetDifLength();
    }
    else
    {
        //The minifat doesn't need to represent itself, so we can
        //  compute the number of sectors needed in one pass.
        sectMax = sectData;
        csectFat = (sectMax + _fv.GetSectTable() -1) >> _uFatShift;
    }

    msfChk(_fv.Init(_pmsParent, csectFat));

    FSINDEX i;

    if (_sid == SIDMINIFAT)
    {
        SECT sectFirst;
        msfChk(_pmsParent->GetFat()->Allocate(csectFat, &sectFirst));

        _pmsParent->GetHeader()->SetMiniFatStart(sectFirst);

        _pmsParent->GetHeader()->SetMiniFatLength(csectFat);
    }


    for (i = 0; i < csectFat; i++)
    {
        CFatSect *pfs;

        msfChk(_fv.GetTable(i, FB_NEW, &pfs));
        if (_sid == SIDFAT)
        {
            _fv.SetSect(i, sectMax + i);
            _pmsParent->GetDIFat()->SetFatSect(i, sectMax + i);
        }
        else
        {
            SECT sect;
            msfChk(_pmsParent->GetESect(_sid, i, &sect));
            _fv.SetSect(i, sect);
        }

        _fv.ReleaseTable(i);
    }


    _cfsTable = csectFat;

    if (_sid != SIDMINIFAT)
    {

        _pmsParent->GetHeader()->SetFatLength(_cfsTable);

        SECT sect;

        if (sectData > 1)
        {
            for (sect = 0; sect < sectData - 2; sect++)
            {
                msfChk(SetNext(sect, sect + 1));
            }

            msfChk(SetNext(sectData - 2, ENDOFCHAIN));
            msfChk(SetNext(sectData - 1, 0));
        }
        else
        {
            //In the event that the file to be converted is less
            //  than one sector long, we don't need to create a
            //  real chain, just a single terminated sector.
            msfChk(SetNext(0, ENDOFCHAIN));
        }


        for (sect = sectData; sect < sectMax; sect++)
        {
            msfChk(SetNext(sect, DIFSECT));
        }

        for (USHORT j = 0; j < csectFat; j++)
        {
            msfChk(SetNext(sectMax + j, FATSECT));
        }

        //Set up directory chain.
        msfChk(SetNext(sectMax + i, ENDOFCHAIN));

        _pmsParent->GetHeader()->SetDirStart(sectMax + i);

        _ulFreeSects = (_cfsTable << _uFatShift) - (sectMax + csectFat + 1);
    }
    else
    {
        for (SECT sect = 0; sect < sectData -1; sect++)
        {
            msfChk(SetNext(sect, sect + 1));
        }
        msfChk(SetNext(sectData - 1, ENDOFCHAIN));
        _ulFreeSects = (_cfsTable << _uFatShift) - sectData;
    }

        msfChk(_pmsParent->SetSize());

Err:
    return sc;
}

//+-------------------------------------------------------------------------
//
//  Member:     CFat::InitNew, public
//
//  Synposis:   Sets up a FAT for a newly created multi-strean
//
//  Effects:    Changes all _apfsTable entries, _cfsTable, and all
//              flags fields
//
//  Arguments:  [pmsparent] -- pointer to parent Mstream
//
//  Returns:    S_OK if call completed OK.
//
//  Algorithm:  Set parent pointer.
//              Allocate 1 sector for FAT and 1 for Directory.
//
//  Notes:
//
//---------------------------------------------------------------------------

SCODE CFat::InitNew(CMStream *pmsParent)
{
    msfDebugOut((DEB_FAT,"In CFat::InitNew()\n"));
    SCODE sc;

    _pmsParent = pmsParent;

    FSINDEX count;
    if (SIDMINIFAT == _sid)
        count = _pmsParent->GetHeader()->GetMiniFatLength();
    else
        count = _pmsParent->GetHeader()->GetFatLength();

    msfDebugOut((DEB_FAT,"Setting up Fat of size %lu\n",count));

    msfChk(_fv.Init(_pmsParent, count));

    _cfsTable = count;

    if (SIDFAT == _sid)
    {
        FSINDEX ipfs;
        FSOFFSET isect;
        CFatSect *pfs;

        SectToPair(_pmsParent->GetHeader()->GetFatStart(), &ipfs, &isect);
        msfChk(_fv.GetTable(ipfs, FB_NEW, &pfs));
        _fv.SetSect(ipfs, _pmsParent->GetHeader()->GetFatStart());
        _fv.ReleaseTable(ipfs);

        msfChk(SetNext(_pmsParent->GetHeader()->GetFatStart(), FATSECT));
        msfDebugOut((DEB_ITRACE,"Set sector %lu (FAT) to ENDOFCHAIN\n",_pmsParent->GetHeader()->GetFatStart()));

        msfChk(SetNext(_pmsParent->GetHeader()->GetDirStart(), ENDOFCHAIN));
        msfDebugOut((DEB_ITRACE,"Set sector %lu (DIR) to ENDOFCHAIN\n",_pmsParent->GetHeader()->GetDirStart()));
        _ulFreeSects = (count << _uFatShift) - 2;
    }
    else
    {
        _ulFreeSects = 0;
    }

        msfChk(_pmsParent->SetSize());

    msfDebugOut((DEB_FAT,"Exiting CFat::setupnew()\n"));

Err:
    return sc;
}




//+-------------------------------------------------------------------------
//
//  Member:     CFat::Resize, private
//
//  Synposis:   Resize FAT, both in memory and in the file
//
//  Effects:    Modifies _cfsTable, _apfsTable, and all flags fields
//
//  Arguments:  [ulSize] -- New size (in # of tables) for FAT
//
//  Returns:    S_OK if call completed OK.
//
//  Algorithm:  Allocate new array of new size.
//              Copy over all old pointers.
//              Allocate new tables for any necessary.
//
//  Notes:      BUGBUG:  This routine currently cannot reduce the size of a
//              fat.  This functionality needs to be added eventually.
//
//---------------------------------------------------------------------------

SCODE CFat::Resize(ULONG ulSize)
{
    msfDebugOut((DEB_FAT,"In CFat::Resize(%lu)\n",ulSize));
    SCODE sc;

    if (ulSize == _cfsTable)
    {
        return S_OK;
    }

    ULONG csect = _cfsTable;

    msfAssert(ulSize > _cfsTable &&
            aMsg("Attempted to shrink Fat"));


    ULONG ipfs;
    SECT sectNew;

    CFat *pfat = _pmsParent->GetFat();


    if (_sid == SIDFAT)
    {

        //Make sure we have enough space for all of the sectors
        //  to be allocated.

        ULONG csectFat = ulSize - _cfsTable;
        ULONG csectPerDif = (1 << _uFatShift) - 1;
        ULONG csectDif = (csectFat + csectPerDif - 1) / csectPerDif;


        //Assuming all the free sectors are at the end of the file,
        //   we need a file csectNew sectors long to hold them.

        ULONG csectOld, csectNew;

        msfChk(FindMaxSect(&csectOld));

        csectNew = csectOld + csectFat + csectDif;

        ULARGE_INTEGER cbSize;
        
        ULISet32(cbSize, ConvertSectOffset(
                csectNew,
                0,
                _pmsParent->GetSectorShift()));

        msfHChk(_pmsParent->GetILB()->SetSize(cbSize));

        //If we are the fat, we have enough space in the file for
        //  ourselves at this point.
    }
    else
    {
        if (_cfsTable == 0)
        {
            msfChk(pfat->Allocate(ulSize, &sectNew));
            _pmsParent->GetHeader()->SetMiniFatStart(sectNew);
        }
        else
        {
            sectNew = _pmsParent->GetHeader()->GetMiniFatStart();

            SECT sectLast;
            msfChk(pfat->GetESect(sectNew, ulSize - 1, &sectLast));

        }

        msfChk(_pmsParent->SetSize());


        msfChk(pfat->GetSect(sectNew, csect, &sectNew));

        //If we are the Minifat, we have enough space in the underlying
        //  file for ourselves at this point.
    }


    _fv.Resize(ulSize);


    for (ipfs = csect; ipfs < ulSize; ipfs++)
    {
        CFatSect *pfs;
        msfChk(_fv.GetTable(ipfs, FB_NEW, &pfs));
        _cfsTable = ipfs + 1;
        _ulFreeSects += (1 << _uFatShift);

        if (_sid == SIDFAT)
        {
            msfChk(pfat->GetFree(1, &sectNew));

            msfChk(_pmsParent->GetDIFat()->SetFatSect(ipfs, sectNew));
            msfChk(pfat->SetNext(sectNew, FATSECT));
        }

        msfAssert(sectNew != ENDOFCHAIN &&
                aMsg("Bad sector returned for fatsect."));

        _fv.SetSect(ipfs, sectNew);
        _fv.ReleaseTable(ipfs);

        if (_sid == SIDMINIFAT)
        {
            msfChk(pfat->GetNext(sectNew, &sectNew));
        }
    }

    msfDebugOut((DEB_FAT,"CFat::Resize() - all new objects allocated\n"));

    if (SIDMINIFAT == _sid)
        _pmsParent->GetHeader()->SetMiniFatLength(_cfsTable);
    else
        _pmsParent->GetHeader()->SetFatLength(_cfsTable);


    //This setsize should only shrink the file.
#if DBG == 1
    STATSTG stat;

    msfHChk(_pmsParent->GetILB()->Stat(&stat, STATFLAG_NONAME));
#endif

        msfChk(_pmsParent->SetSize());

#if DBG == 1
    STATSTG statNew;

    msfHChk(_pmsParent->GetILB()->Stat(&statNew, STATFLAG_NONAME));

    msfAssert(ULIGetLow(statNew.cbSize) <= ULIGetLow(stat.cbSize));
#endif

    msfDebugOut((DEB_FAT,"Out CFat::Resize(%lu)\n",ulSize));

Err:
    return sc;
}






//+-------------------------------------------------------------------------
//
//  Member:     CFat::Extend, private
//
//  Synposis:   Increase the size of an existing chain
//
//  Effects:    Modifies ulSize sectors within the fat.  Causes one or
//              more sector writes.
//
//  Arguments:  [sect] -- Sector ID of last sector in chain to be extended
//              [ulSize] -- Number of sectors to add to chain
//
//  Requires:   sect must be at the end of a chain.
//
//  Returns:    S_OK if call completed OK.
//
//  Algorithm:  Use calls to GetFree to allocate chain.
//
//  Notes:
//
//---------------------------------------------------------------------------

SCODE CFat::Extend(SECT sect, ULONG ulSize)
{
    SCODE sc;

    msfDebugOut((DEB_FAT,"In CFat::Extend(%lu,%lu)\n",sect,ulSize));
    SECT sectTemp;

    msfChk(GetFree(ulSize, &sectTemp));
    msfChk(SetNext(sect, sectTemp));

    msfDebugOut((DEB_FAT,"Out CFat::Extend()\n"));

Err:
    return sc;
}




//+-------------------------------------------------------------------------
//
//  Member:     CFat::GetNext, public
//
//  Synposis:   Returns the next sector in a chain, given a sector
//
//  Arguments:  [sect] -- Sector ID of any sector in a chain.
//
//  Returns:    Sector ID of next sector in chain, ENDOFCHAIN if at end
//
//  Notes:
//
//---------------------------------------------------------------------------

SCODE CFat::GetNext(const SECT sect, SECT * psRet)
{
    SCODE sc;

    FSINDEX ipfs;
    FSOFFSET isect;

    msfAssert(sect <= MAXREGSECT &&
            aMsg("Called GetNext() on invalid sector"));

    SectToPair(sect, &ipfs, &isect);
    CFatSect *pfs;
    msfChk(_fv.GetTable(ipfs, FB_NONE, &pfs));

    *psRet = pfs->GetSect(isect);

    _fv.ReleaseTable(ipfs);

    msfAssert(sect != *psRet &&
            aMsg("Detected loop in fat chain."));
    return S_OK;

Err:
    return sc;
}




//+-------------------------------------------------------------------------
//
//  Member:     CFat::SetNext, private
//
//  Synposis:   Set the next sector in a chain
//
//  Effects:    Modifies a single entry within the fat.
//
//  Arguments:  [sectFirst] -- Sector ID of first sector
//              [sectNext] -- Sector ID of next sector
//
//  Returns:    void
//
//  Notes:
//
//---------------------------------------------------------------------------
SCODE CFat::SetNext(SECT sectFirst, SECT sectNext)
{
    FSINDEX ipfs;
    FSOFFSET isect;
    SCODE sc;


    //  creating infinite loops is a no-no
    msfAssert(sectFirst != sectNext &&
            aMsg("Attempted to create loop in Fat chain"));
    msfAssert(sectFirst <= MAXREGSECT &&
            aMsg("Called SetNext on invalid sector"));

    SectToPair(sectFirst, &ipfs, &isect);

    CFatSect *pfs;

    msfChk(_fv.GetTable(ipfs, FB_DIRTY, &pfs));

    pfs->SetSect(isect,sectNext);

    _fv.ReleaseTable(ipfs);

    if (sectNext == FREESECT)
    {
        CVectBits *pfb;
        pfb = _fv.GetBits(ipfs);

        if ((pfb != NULL) &&
            ((pfb->full == TRUE) || (isect < pfb->firstfree)))
        {
            pfb->full = FALSE;
            pfb->firstfree = isect;
        }

        if (sectFirst == _sectMax - 1)
        {
            _sectMax = ENDOFCHAIN;
        }
        if (sectFirst < _sectFirstFree)
        {
            _sectFirstFree = sectFirst;
        }

        if (_ulFreeSects != MAX_ULONG)
        {
                _ulFreeSects++;
        }
    }

Err:
    return sc;
}



//+-------------------------------------------------------------------------
//
//  Member:     CFat::CountFree, private
//
//  Synposis:   Count and return the number of free sectors in the Fat
//
//  Arguments:  void.
//
//  Returns:    void.
//
//  Algorithm:  Do a linear search of the Fat, counting free sectors.
//              If a FatSect has its full bit set, it is not necessary
//              to search that FatSect.
//
//  Notes:
//
//---------------------------------------------------------------------------

SCODE CFat::CountFree(ULONG * pulRet)
{
    msfDebugOut((DEB_FAT,"In CFat::CountFree()\n"));
    SCODE sc = S_OK;

    FSINDEX ipfs;
    ULONG csectFree=0;
    FSOFFSET isectStart;
    FSINDEX ipfsStart;

    SectToPair(_sectFirstFree, &ipfsStart, &isectStart);

    for (ipfs = ipfsStart; ipfs < _cfsTable; ipfs++)
    {
        CVectBits *pfb = _fv.GetBits(ipfs);

        if ((pfb == NULL) || (!pfb->full))
        {
            msfDebugOut((DEB_FAT,"Checking table %lu\n",ipfs));
            CFatSect *pfs;
            msfChk(_fv.GetTable(ipfs, FB_NONE, &pfs));

            if (pfb != NULL)
            {
                isectStart = pfb->firstfree;
            }

            FSOFFSET isect;
            for (isect = isectStart; isect < _fv.GetSectTable(); isect++)
            {
                SECT sectCurrent = pfs->GetSect(isect);
                PairToSect(ipfs, isect);
                
                if (sectCurrent == FREESECT)
                {
                    csectFree++;
                }
            }
            _fv.ReleaseTable(ipfs);
        }
        isectStart = 0;
    }
    msfDebugOut((DEB_FAT,"Countfree returned %lu\n",csectFree));
    *pulRet = csectFree;

Err:
    return sc;
}


//+-------------------------------------------------------------------------
//
//  Member:     CFat::GetSect, public
//
//  Synposis:   Return the nth sector in a chain
//
//  Arguments:  [sect] -- Sector ID of beginning of chain
//              [uNum] -- indicator of which sector is to be returned
//              [psectReturn] -- Pointer to storage for return value
//
//  Returns:    S_OK.
//
//  Algorithm:  Linearly traverse chain until numth sector
//
//  Notes:
//
//---------------------------------------------------------------------------

SCODE CFat::GetSect(SECT sect, ULONG ulNum, SECT * psectReturn)
{
    msfDebugOut((DEB_FAT,"In CFat::GetSect(%lu,%lu)\n",sect,ulNum));

    SCODE sc = S_OK;

    if (ulNum == 0)
    {
        msfDebugOut((DEB_FAT,"Out CFat::GetSect()=>%lu\n",sect));
    }
    else if ((SIDFAT == _sid) &&
             (_pmsParent->GetHeader()->GetFatStart() == sect))
    {
        msfChk(_pmsParent->GetDIFat()->GetFatSect(ulNum, &sect));
    }
    else for (USHORT i = 0; i < ulNum; i++)
    {
        msfChk(GetNext(sect, &sect));
        if (sect > MAXREGSECT)
        {
            //The stream isn't long enough, so stop.
            msfAssert(sect == ENDOFCHAIN &&
                    aMsg("Found invalid sector in fat chain."));
            break;
        }
    }

    *psectReturn = sect;
    msfDebugOut((DEB_FAT,"Out CFat::GetSect()=>%lu\n",sect));

Err:
    return sc;
}



//+-------------------------------------------------------------------------
//
//  Member:     CFat::GetESect
//
//  Synposis:   Return the nth sector in a chain, extending the chain
//              if necessary.
//
//  Effects:    Modifies fat (via Extend) if necessary
//
//  Arguments:  [sect] -- Sector ID of beginning of chain
//              [ulNum] -- Indicates which sector is to be returned
//              [psectReturn] -- Pointer to storage for return value
//
//  Returns:    S_OK if call completed OK.
//
//  Algorithm:  Linearly search chain until numth sector is found.  If
//              the chain terminates early, extend it as necessary.
//
//  Notes:
//
//---------------------------------------------------------------------------

SCODE CFat::GetESect(SECT sect, ULONG ulNum, SECT *psectReturn)
{
    msfDebugOut((DEB_FAT,"In CFat::GetESect(%lu,%lu)\n",sect,ulNum));

    SCODE sc = S_OK;

    ULONG i = 0;
    while (i < ulNum)
    {
        SECT temp;
        msfChk(GetNext(sect, &temp));

        msfAssert(temp != FREESECT &&
                aMsg("FREESECT found in chain."));

        if (temp == ENDOFCHAIN)
        {

            //The stream isn't long enough, so extend it somehow.
            ULONG need = ulNum - i;

            msfAssert((SIDMINIFAT == _sid ||
                    sect != _pmsParent->GetHeader()->GetFatStart()) &&
                    aMsg("Called GetESect on Fat chain"));
            msfChk(Extend(sect,need));
        }
        else
        {
            sect = temp;
            i++;
        }
    }

    msfDebugOut((DEB_FAT,"Exiting GetESect with result %lu\n",sect));
    *psectReturn = sect;

Err:
    return sc;
}




//+-------------------------------------------------------------------------
//
//  Member:     CFat::SetChainLength, private
//
//  Synposis:   Set the length of a fat chain.  This is used to reduce
//              the length of the chain only.  To extend a chain, use
//              Extend or GetESect
//
//  Effects:    Modifies the fat
//
//  Arguments:  [sectStart] -- Sector to begin at (head of chain)
//              [uLength] -- New length for chain
//
//  Returns:    void.
//
//  Algorithm:  Traverse chain until uLength is reached or the chain
//              terminates.  If it terminates prematurely, return with
//              no other action.  Otherwise, deallocate all remaining
//              sectors in the chain.
//
//  Notes:
//
//---------------------------------------------------------------------------

SCODE CFat::SetChainLength(SECT sectStart, ULONG ulLength)
{
    msfDebugOut((DEB_FAT,"In CFat::SetChainLength(%lu,%lu)\n",sectStart,ulLength));
    SCODE sc;

    if (sectStart == ENDOFCHAIN) return S_OK;

    for (USHORT ui = 0; ui < ulLength; ui++)
    {
        msfChk(GetNext(sectStart, &sectStart));
        if (sectStart == ENDOFCHAIN) return S_OK;
    }

    msfAssert(sectStart != ENDOFCHAIN &&
            aMsg("Called SetChainLength is ENDOFCHAIN start"));

    SECT sectEnd;
    sectEnd = sectStart;

    msfChk(GetNext(sectStart, &sectStart));
    if (ulLength != 0)
    {
        msfChk(SetNext(sectEnd, ENDOFCHAIN));
    }
    else
    {
        msfChk(SetNext(sectEnd, FREESECT));
    }

    while (sectStart != ENDOFCHAIN)
    {
        SECT sectTemp;
        msfChk(GetNext(sectStart, &sectTemp));
        msfChk(SetNext(sectStart, FREESECT));
        sectStart = sectTemp;
    }
    msfDebugOut((DEB_FAT,"Out CFat::SetChainLength()\n"));

Err:
    return sc;
}



//+-------------------------------------------------------------------------
//
//  Method:     CFat::FindLast, private
//
//  Synopsis:   Find last used sector in a fat
//
//  Returns:    Location of last used sector
//
//  Algorithm:  Perform a backward linear search until a non-free
//              sector is found.
//
//  Notes:      Used for shadow fats only.
//
//--------------------------------------------------------------------------

SCODE CFat::FindLast(SECT * psectRet)
{
    SCODE sc = S_OK;
    FSINDEX ipfs = _cfsTable;
    SECT sect = 0;

    while (ipfs > 0)
    {
        ipfs--;

        FSOFFSET isect = _fv.GetSectTable();

        CFatSect *pfs;
        msfChk(_fv.GetTable(ipfs, FB_NONE, &pfs));

        while (isect > 0)
        {
            isect--;

            SECT sectCurrent = pfs->GetSect(isect);

                
            if (sectCurrent != FREESECT)
            {
                msfDebugOut((DEB_FAT,"FindLast returns %lu\n",PairToSect(ipfs,isect)));
                sect = PairToSect(ipfs, (FSOFFSET) (isect + 1));
                break;
            }
        }

        _fv.ReleaseTable(ipfs);
        if (sect != 0)
            break;
    }

    *psectRet = sect;
Err:
    return sc;
}





//+-------------------------------------------------------------------------
//
//  Method:     CFat::FindMaxSect, private
//
//  Synopsis:   Return last used sector in current Fat.
//
//  Arguments:  None.
//
//  Returns:    Last used sector in current Fat
//
//--------------------------------------------------------------------------

SCODE CFat::FindMaxSect(SECT *psectRet)
{
    SCODE sc = S_OK;

    if (_sectMax == ENDOFCHAIN)
    {
        msfChk(FindLast(psectRet));
    }
    else
    {
#if DBG == 1
        SECT sectLast;
        msfChk(FindLast(&sectLast));
#endif
        *psectRet = _sectMax;
    }

Err:
    return sc;
}


//+-------------------------------------------------------------------------
//
//  Member:     CFat::Contig, public
//
//  Synposis:   Create contiguous sector table
//
//  Effects:    Creates new CSegment.
//
//  Arguments:  [sect] -- Starting sector for table to begin
//              [ulength] -- Runlength in sectors of table to produce
//
//  Returns:    Pointer to a Segment table
//
//  Algorithm:  Perform calls to CFat::GetNext().  Any call that is
//              1 higher than the previous represents contiguous blocks.
//              Construct the Segment table on that basis.
//
//  Notes:
//
//---------------------------------------------------------------------------

SCODE  CFat::Contig(
        SSegment STACKBASED *aseg,
        SECT sect,
        ULONG ulLength)
{
    msfDebugOut((DEB_ITRACE,"In CFat::Contig(%lu,%lu)\n",sect,ulLength));
    SCODE sc = S_OK;
    SECT stemp = sect;
    ULONG ulCount = 1;
    USHORT iseg = 0;

    msfAssert(sect != ENDOFCHAIN &&
            aMsg("Called Contig with ENDOFCHAIN start"));

    aseg[iseg].sectStart = sect;
    aseg[iseg].cSect = 1;

    while ((ulLength > 1) && (iseg < CSEG))
    {
        msfAssert(sect != ENDOFCHAIN &&
                aMsg("Contig found premature ENDOFCHAIN"));

        FSINDEX ipfs;
        FSOFFSET isect;

        SectToPair(sect, &ipfs, &isect);

        CFatSect *pfs;
        msfChk(_fv.GetTable(ipfs, FB_NONE, &pfs));
        sect = pfs->GetSect(isect);
        _fv.ReleaseTable(ipfs);

        if (sect == ENDOFCHAIN)
        {
            //Allocate new sectors.

            SECT sectNew;
            msfChk(GetFree(ulLength - 1, &sectNew));
            msfChk(SetNext(stemp, sectNew));
            sect = sectNew;
        }

        if (sect != (stemp + 1))
        {
            aseg[iseg].cSect = ulCount;
            ulCount = 1;
            iseg++;
            aseg[iseg].sectStart = sect;
            stemp = sect;
        }
        else
        {
            ulCount++;
            stemp = sect;
        }
        ulLength--;
    }

    if (iseg < CSEG)
    {
        aseg[iseg].cSect = ulCount;
        aseg[iseg + 1].sectStart = ENDOFCHAIN;
    }
    else
    {
        aseg[iseg].sectStart = FREESECT;
    }

    msfDebugOut((DEB_ITRACE,"Exiting Contig()\n"));

Err:
    return sc;
}

