//
// The developer of the original code and/or files is Tripwire, Inc.
// Portions created by Tripwire, Inc. are copyright (C) 2000-2018 Tripwire,
// Inc. Tripwire is a registered trademark of Tripwire, Inc.  All rights
// reserved.
//
// This program is free software.  The contents of this file are subject
// to the terms of the GNU General Public License as published by the
// Free Software Foundation; either version 2 of the License, or (at your
// option) any later version.  You may redistribute it and/or modify it
// only in compliance with the GNU General Public License.
//
// This program is distributed in the hope that it will be useful.
// However, this program is distributed AS-IS WITHOUT ANY
// WARRANTY; INCLUDING THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS
// FOR A PARTICULAR PURPOSE.  Please see the GNU General Public License
// for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
// USA.
//
// Nothing in the GNU General Public License or any other license to use
// the code or files shall permit you to use Tripwire's trademarks,
// service marks, or other intellectual property without Tripwire's
// prior written consent.
//
// If you have any questions, please contact Tripwire, Inc. at either
// info@tripwire.org or www.tripwire.org.
//
///////////////////////////////////////////////////////////////////////////////
// blockrecordarray.cpp

#include "stddb.h"

#include "blockrecordarray.h"


///////////////////////////////////////////////////////////////////////////////
// util_GetIndexArray -- returns a reference to the index array inside a block
//      Right now, this is just a cast, but if the blocks ever had headers, this
//      would also take that offset into account
///////////////////////////////////////////////////////////////////////////////
inline cBlockRecordArray::tIndexArray& util_GetIndexArray(cBlockFile::Block* pBlock)
{
    return (*reinterpret_cast<cBlockRecordArray::tIndexArray*>(pBlock->GetData()));
}

///////////////////////////////////////////////////////////////////////////////
// util_OffsetToAddr -- given an offset from the end of a block (such as
//      is stored in tRecordIndex) return a pointer to memory inside the block
//      that corresponds to the given offset
///////////////////////////////////////////////////////////////////////////////
inline int8* util_OffsetToAddr(cBlockFile::Block* pBlock, int offset)
{
    return (pBlock->GetData() + (cBlockFile::BLOCK_SIZE - offset));
}


///////////////////////////////////////////////////////////////////////////////
// ctor
///////////////////////////////////////////////////////////////////////////////
cBlockRecordArray::cBlockRecordArray(cBlockFile* pBlockFile, int blockNum)
    : mpBlockFile(pBlockFile), mBlockNum(blockNum), mSpaceAvailable(-1), mNumItems(0), mbInit(false)
{
}

cBlockRecordArray::cBlockRecordArray() : mpBlockFile(0), mBlockNum(-1), mSpaceAvailable(-1), mNumItems(0), mbInit(false)
{
}

void cBlockRecordArray::Init(cBlockFile* pBlockFile, int blockNum)
{
    ASSERT(!pBlockFile);

    mpBlockFile     = pBlockFile;
    mBlockNum       = blockNum;
    mSpaceAvailable = -1;
    mNumItems       = 0;
    mbInit          = false;
}


///////////////////////////////////////////////////////////////////////////////
// dtor
///////////////////////////////////////////////////////////////////////////////
cBlockRecordArray::~cBlockRecordArray()
{
}

///////////////////////////////////////////////////////////////////////////////
// InitNewBlock
///////////////////////////////////////////////////////////////////////////////
void cBlockRecordArray::InitNewBlock() //throw (eArchive)
{
    ASSERT(mpBlockFile != 0);
    ASSERT((mBlockNum >= 0) && (mBlockNum < mpBlockFile->GetNumBlocks()));
#ifdef _BLOCKFILE_DEBUG
    mpBlockFile->AssertValid();
#endif

    cBlockFile::Block* pBlock = mpBlockFile->GetBlock(mBlockNum);
    tIndexArray&       array  = util_GetIndexArray(pBlock);
    //
    // set everything to zero..
    //
    memset(pBlock->GetData(), 0, cBlockFile::BLOCK_SIZE);
    pBlock->SetDirty();
    //
    // set up the guard byte for the index array...
    //
    array.maRecordIndex[0].SetOffset(-1);
    array.maRecordIndex[0].SetMainIndex(INVALID_INDEX);
    //
    // remember that we have been initialized
    //
    mbInit = true;
    //
    // update the free space and max index variables...
    //
    mNumItems = 0;
    UpdateFreeSpace(pBlock);
    WriteHeaderInfo(pBlock);
}

///////////////////////////////////////////////////////////////////////////////
// InitForExistingBlock
///////////////////////////////////////////////////////////////////////////////
void cBlockRecordArray::InitForExistingBlock() //throw (eArchive)
{
    ASSERT(mpBlockFile != 0);
    ASSERT((mBlockNum >= 0) && (mBlockNum < mpBlockFile->GetNumBlocks()));
    ASSERT(!mbInit);
    //
    // read in our header info...
    //
    cBlockFile::Block* pBlock = mpBlockFile->GetBlock(mBlockNum);
    ReadHeaderInfo(pBlock);
    //
    // remember that we have been initialized
    //
    mbInit = true;
    //
    // some debug out...
    //
    cDebug d("cBlockRecordArray::InitForExistingBlock");
    d.TraceDetail(" Executed for block %d; NumItems=%d, SpaceAvail=%d\n", mBlockNum, mNumItems, mSpaceAvailable);
}


///////////////////////////////////////////////////////////////////////////////
// WriteHeaderInfo
///////////////////////////////////////////////////////////////////////////////
void cBlockRecordArray::WriteHeaderInfo(cBlockFile::Block* pBlock)
{
    ASSERT(mpBlockFile != 0);
    ASSERT(Initialized());

    util_GetIndexArray(pBlock).mHeader.SetNumItems(mNumItems);
    util_GetIndexArray(pBlock).mHeader.SetSpaceAvail(mSpaceAvailable);

    pBlock->SetDirty();
}

///////////////////////////////////////////////////////////////////////////////
// ReadHeaderInfo
///////////////////////////////////////////////////////////////////////////////
void cBlockRecordArray::ReadHeaderInfo(cBlockFile::Block* pBlock)
{
    ASSERT(mpBlockFile != 0);

    mNumItems       = util_GetIndexArray(pBlock).mHeader.GetNumItems();
    mSpaceAvailable = util_GetIndexArray(pBlock).mHeader.GetSpaceAvail();
}


///////////////////////////////////////////////////////////////////////////////
// UpdateFreeSpace
///////////////////////////////////////////////////////////////////////////////
void cBlockRecordArray::UpdateFreeSpace(cBlockFile::Block* pBlock)
{
    ASSERT(Initialized());
    ASSERT(mpBlockFile != 0);
    // first, take into account the size of all of the index structures. Note that
    // we add one to the current number because we want to account for the new one
    // that might be added if another item were added to the array
    //
    int availSpace = cBlockFile::BLOCK_SIZE - sizeof(tHeader) - ((GetNumItems() + 1) * sizeof(tRecordIndex));
    //
    // always take the MARGIN away, to give ourselves some breathing room between the indexes and
    // the data
    //
    availSpace -= MARGIN;
    //
    // subtract from availSpace the amount of room that the actual data is taking up...
    // note that it is always true that the top index points to a valid value
    //
    if (GetNumItems() > 0)
    {
        int topOffset = util_GetIndexArray(pBlock).maRecordIndex[GetNumItems() - 1].GetOffset();
        availSpace -= topOffset;
    }

    mSpaceAvailable = availSpace < 0 ? 0 : availSpace;

#ifdef _BLOCKFILE_DEBUG
    AssertValid();
#endif
}

///////////////////////////////////////////////////////////////////////////////
// IsItemValid
///////////////////////////////////////////////////////////////////////////////
bool cBlockRecordArray::IsItemValid(int index) const //throw (eArchive)
{
    ASSERT(Initialized());

    if ((index < 0) || (index >= GetNumItems()))
        return false;

    cBlockFile::Block* pBlock = mpBlockFile->GetBlock(mBlockNum);

    return (util_GetIndexArray(pBlock).maRecordIndex[index].GetMainIndex() != INVALID_INDEX);
}


///////////////////////////////////////////////////////////////////////////////
// AddItem
///////////////////////////////////////////////////////////////////////////////
int cBlockRecordArray::AddItem(int8* pData, int dataSize, int mainIndex) //throw (eArchive)
{
    // make ourselves initialized, if we are not right now...
    //
    if (!Initialized())
    {
        InitForExistingBlock();
    }


    ASSERT(dataSize <= GetAvailableSpace());
    ASSERT(GetNumItems() <= MAX_RECORDS);
    ASSERT(dataSize > 0); // I am not sure if this is the right thing to do !
    ASSERT(mpBlockFile != 0);
    ASSERT(mainIndex != INVALID_INDEX);
    ASSERT(Initialized());
#ifdef _BLOCKFILE_DEBUG
    AssertValid();
    mpBlockFile->AssertValid();
#endif
    cDebug d("cBlockRecordArray::AddItem");
    d.TraceDetail("Entering... adding item of size %d to block %d\n", dataSize, mBlockNum);
    //
    // get the block we need and a reference into the index array....
    //
    cBlockFile::Block* pBlock     = mpBlockFile->GetBlock(mBlockNum);
    tIndexArray&       indexArray = util_GetIndexArray(pBlock);
    //
    // find an unused entry to put the item into...
    //
    tRecordIndex* pIndex   = indexArray.maRecordIndex;
    int           newIndex = 0;
    for (; newIndex < mNumItems; newIndex++, pIndex++)
    {
        if (pIndex->GetMainIndex() == INVALID_INDEX)
            break;
    }
    d.TraceDetail("Adding new item to index %d (max index + 1 = %d)\n", newIndex, mNumItems);
    // write the data we know for sure at this point...
    //
    pIndex->SetMainIndex(mainIndex);
    pBlock->SetDirty();
    //
    // calculate the offset for the new data
    //
    int prevOffset = (newIndex == 0 ? 0 : indexArray.maRecordIndex[newIndex - 1].GetOffset());
    pIndex->SetOffset(prevOffset + dataSize);

    if (newIndex == mNumItems)
    {
        //
        // increment the max number of indexes...
        //
        mNumItems++;
        indexArray.maRecordIndex[mNumItems].SetOffset(-1);
        indexArray.maRecordIndex[mNumItems].SetMainIndex(INVALID_INDEX);
    }
    else
    {
        // we are going to have to shift up the data that is above us...
        //
        int   topOffset = indexArray.maRecordIndex[GetNumItems() - 1].GetOffset();
        int8* pTop      = util_OffsetToAddr(pBlock, topOffset);
        int   amtToMove = topOffset - prevOffset;
        ASSERT(amtToMove >= 0);

        memmove(pTop - dataSize, pTop, amtToMove);
        //
        // now, we need to iterate over all of the existing entries, updating their offsets...
        //
        tRecordIndex* pMovedIndex = pIndex + 1;
        for (int i = newIndex + 1; i < mNumItems; i++, pMovedIndex++)
        {
            pMovedIndex->SetOffset(pMovedIndex->GetOffset() + dataSize);
        }
    }
    //
    // write the data...
    //
    memcpy(util_OffsetToAddr(pBlock, pIndex->GetOffset()), pData, dataSize);
    //
    // update the free space...
    //
    indexArray.maRecordIndex[mNumItems].SetOffset(-1);
    indexArray.maRecordIndex[mNumItems].SetMainIndex(INVALID_INDEX);
    UpdateFreeSpace(pBlock);
    WriteHeaderInfo(pBlock);

#ifdef _BLOCKFILE_DEBUG
    AssertValid();
    mpBlockFile->AssertValid();
    d.TraceDetail("*** Add Complete; here are the new contents ***\n");
    TraceContents(cDebug::D_NEVER);
#endif

    return newIndex;
}

///////////////////////////////////////////////////////////////////////////////
// DeleteItem
///////////////////////////////////////////////////////////////////////////////
void cBlockRecordArray::DeleteItem(int index) //throw (eArchive)
{
    // make ourselves initialized, if we are not right now...
    //
    if (!Initialized())
    {
        InitForExistingBlock();
    }


    ASSERT(mpBlockFile != 0);
    ASSERT(IsItemValid(index));
    ASSERT(Initialized());
#ifdef _BLOCKFILE_DEBUG
    AssertValid();
    mpBlockFile->AssertValid();
#endif
    cDebug d("cBlockRecordArray::DeleteItem");
    d.TraceDetail("Entering... Removing item index %d from block %d\n", index, mBlockNum);

    //
    // get the block we need and a reference into the index array....
    //
    cBlockFile::Block* pBlock     = mpBlockFile->GetBlock(mBlockNum);
    tIndexArray&       indexArray = util_GetIndexArray(pBlock);
    pBlock->SetDirty();
    //
    // if this is the last entry, then our job is easy...
    //
    if ((index + 1) == mNumItems)
    {
        mNumItems--;
        // we need to clean out all the entries that are empty at the end of the index list...
        //
        while ((mNumItems > 0) && (indexArray.maRecordIndex[mNumItems - 1].GetMainIndex() == INVALID_INDEX))
        {
            mNumItems--;
        }
        //
        // set the guard bytes...
        //
        indexArray.maRecordIndex[mNumItems].SetOffset(-1);
        indexArray.maRecordIndex[mNumItems].SetMainIndex(INVALID_INDEX);
    }
    else
    {
        // we are going to shift all entries above the deleted entry down;
        // first, figure out how far we are shifting and the source address...
        //
        int sizeToShift =
            indexArray.maRecordIndex[GetNumItems() - 1].GetOffset() - indexArray.maRecordIndex[index].GetOffset();
        int distToShift = indexArray.maRecordIndex[index].GetOffset();
        if (index > 0)
        {
            distToShift -= indexArray.maRecordIndex[index - 1].GetOffset();
        }
        int8* pSrc = util_OffsetToAddr(pBlock, indexArray.maRecordIndex[GetNumItems() - 1].GetOffset());
        //
        // copy the data..
        //
        ASSERT(pBlock->IsValidAddr(pSrc) && pBlock->IsValidAddr(pSrc + distToShift + sizeToShift - 1));
        ASSERT(sizeToShift >= 0);
        ASSERT(distToShift >= 0);
        memmove(pSrc + distToShift, pSrc, sizeToShift);
        //
        // mark this entry as unused...
        // note that the offset is set to be the same as the item before it.
        //
        tRecordIndex* pIndex = &indexArray.maRecordIndex[index];
        pIndex->SetMainIndex(INVALID_INDEX);
        pIndex->SetOffset((index == 0 ? 0 : indexArray.maRecordIndex[index - 1].GetOffset()));
        //
        // now, iterate over the items, moving their offsets down...
        //
        pIndex++;
        for (int i = index + 1; i < GetNumItems(); i++, pIndex++)
        {
            pIndex->SetOffset(pIndex->GetOffset() - distToShift);
        }
    }

    UpdateFreeSpace(pBlock);
    WriteHeaderInfo(pBlock);

#ifdef _BLOCKFILE_DEBUG
    AssertValid();
    mpBlockFile->AssertValid();
    d.TraceDetail("*** Removal Complete; here are the new contents ***\n");
    TraceContents(cDebug::D_NEVER);
#endif
}

///////////////////////////////////////////////////////////////////////////////
// GetDataForReading
///////////////////////////////////////////////////////////////////////////////
int8* cBlockRecordArray::GetDataForReading(int index, int32& dataSize) //throw (eArchive)
{
    // make ourselves initialized, if we are not right now...
    //
    if (!Initialized())
    {
        InitForExistingBlock();
    }

    ASSERT(IsItemValid(index));
    cBlockFile::Block* pBlock     = mpBlockFile->GetBlock(mBlockNum);
    tIndexArray&       indexArray = util_GetIndexArray(pBlock);
    //
    // figure out the size of the block
    //
    dataSize = indexArray.maRecordIndex[index].GetOffset();
    if (index != 0)
    {
        dataSize -= indexArray.maRecordIndex[index - 1].GetOffset();
    }

#ifdef _BLOCKFILE_DEBUG
    AssertValid();
    mpBlockFile->AssertValid();
#endif


    return (util_OffsetToAddr(pBlock, indexArray.maRecordIndex[index].GetOffset()));
}

///////////////////////////////////////////////////////////////////////////////
// GetDataForWriting
///////////////////////////////////////////////////////////////////////////////
int8* cBlockRecordArray::GetDataForWriting(int index, int32& dataSize) //throw (eArchive)
{
    // make ourselves initialized, if we are not right now...
    //
    if (!Initialized())
    {
        InitForExistingBlock();
    }

    ASSERT(IsItemValid(index));
    cBlockFile::Block* pBlock     = mpBlockFile->GetBlock(mBlockNum);
    tIndexArray&       indexArray = util_GetIndexArray(pBlock);
    pBlock->SetDirty();
    //
    // figure out the size of the block
    //
    dataSize = indexArray.maRecordIndex[index].GetOffset();
    if (index != 0)
    {
        dataSize -= indexArray.maRecordIndex[index - 1].GetOffset();
    }

#ifdef _BLOCKFILE_DEBUG
    AssertValid();
    mpBlockFile->AssertValid();
#endif

    return (util_OffsetToAddr(pBlock, indexArray.maRecordIndex[index].GetOffset()));
}


///////////////////////////////////////////////////////////////////////////////
// IsClassValid
///////////////////////////////////////////////////////////////////////////////
#define BRA_ASSERT(x)  \
    if (x)             \
        ;              \
    else               \
    {                  \
        ASSERT(false); \
        return false;  \
    }

bool cBlockRecordArray::IsClassValid() const
{
    ASSERT(mpBlockFile != 0);
    cDebug d("cBlockRecordArray::AssertValid");
    if (!mbInit)
    {
        // if we haven't been initialized yet, then I assume that we are valid...
        //
        return true;
    }

    //
    // get the block we need and a reference into the index array....
    //
    try
    {
        cBlockFile::Block* pBlock = mpBlockFile->GetBlock(mBlockNum);
        tIndexArray&       array  = util_GetIndexArray(pBlock);
        //
        // make sure that the num items and available space make sense...
        //
        BRA_ASSERT((mNumItems >= 0) && (mNumItems <= MAX_RECORDS));
        BRA_ASSERT((mSpaceAvailable >= 0) && (mSpaceAvailable <= MAX_DATA_SIZE));
        //
        // assert that the top index points to a valid value...
        //
        if (mNumItems != 0)
        {
            BRA_ASSERT(array.maRecordIndex[mNumItems - 1].GetMainIndex() != INVALID_INDEX);
            BRA_ASSERT(array.maRecordIndex[mNumItems - 1].GetOffset() != -1);
        }

        int prevOff = 0;
        int i;
        for (i = 0; i < mNumItems; i++)
        {
            BRA_ASSERT(array.maRecordIndex[i].GetOffset() >= prevOff);
            prevOff = array.maRecordIndex[i].GetOffset();
        }
        // make sure the guard byte is there...
        //
        BRA_ASSERT(array.maRecordIndex[i].GetOffset() == -1);
        //
        // make sure the final offset is less than the highest record index offset.
        //
        BRA_ASSERT((int8*)&array.maRecordIndex[i] < util_OffsetToAddr(pBlock, prevOff));
        //
        // TODO -- is there anything else that is worth checking?
    }
    catch (eArchive& e)
    {
        d.TraceError("*** cBlockFile::GetBlock threw an eArchive: %d %s\n", e.GetID(), e.GetMsg().c_str());
        BRA_ASSERT(false);
    }
    // if we got here, then everything worked ok.
    //
    return true;
}


#ifdef _BLOCKFILE_DEBUG

///////////////////////////////////////////////////////////////////////////////
// TraceContents
///////////////////////////////////////////////////////////////////////////////
void cBlockRecordArray::TraceContents(int dl) const
{
    if (dl == -1)
        dl = cDebug::D_DEBUG;

    cDebug d("cBlockRecordArray::TraceContents");

    d.Trace(dl, "---------- Block Record Array ----------\n");
    d.Trace(dl, "----------------------------------------\n");
    d.Trace(dl, "Block Number    %d\n", mBlockNum);
    d.Trace(dl, "Space Available %d\n", mSpaceAvailable);
    d.Trace(dl, "Num Entries     %d\n", mNumItems);

    cBlockFile::Block* pBlock     = mpBlockFile->GetBlock(mBlockNum);
    tIndexArray&       indexArray = util_GetIndexArray(pBlock);

    for (int i = 0; i < mNumItems; i++)
    {
        d.Trace(dl,
                "(%d)\tOffset %d\tMain Idx %d\n",
                i,
                indexArray.maRecordIndex[i].GetOffset(),
                indexArray.maRecordIndex[i].GetMainIndex());
    }

    d.Trace(dl, "----------------------------------------\n");
}

///////////////////////////////////////////////////////////////////////////////
// AssertValid
///////////////////////////////////////////////////////////////////////////////
void cBlockRecordArray::AssertValid() const
{
    ASSERT(IsClassValid());
}

#endif
