//
// 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.h
//
// cBlockRecordArray
//
#ifndef __BLOCKRECORDARRAY_H
#define __BLOCKRECORDARRAY_H

#ifndef __BLOCKFILE_H
#include "blockfile.h"
#endif
#ifndef __DEBUG_H
#include "core/debug.h"
#endif

//
// TODO -- I need to guarantee that all of the structures in this class do not have any pad bytes.
//      This is true under win32, but I am not sure if it will always be true. -- 10 feb 99 mdb
//

class eArchive;

class cBlockRecordArray
{
public:
    //-------------------------------------------------------------------------------------
    // Construction
    //-------------------------------------------------------------------------------------
    cBlockRecordArray(cBlockFile* pBlockFile, int blockNum);
    // this object is associated with a single block in a cBlockFile. It is _not_
    // asserted that the blockNum is valid for the associated file at this point,
    // since it is possible that the file has not been loaded yet
    cBlockRecordArray();
    // if this version of the ctor is called, then Init() must be called before it can be used.
    void Init(cBlockFile* pBlockFile, int blockNum);

    virtual ~cBlockRecordArray();

    //-------------------------------------------------------------------------------------
    // Initialization ... exactly one of these methods must be called before this class can be used
    //-------------------------------------------------------------------------------------
    void InitNewBlock(); //throw (eArchive)
        // this method should be called when a new block has been created, and before this
        // class operates on it.
    void InitForExistingBlock(); //throw (eArchive)
        // this method should be called to initialize this class for use with a block that has
        // previously been created and initialized with InitNewBlock().

    //-------------------------------------------------------------------------------------
    // Data Manipulation
    //-------------------------------------------------------------------------------------
    int AddItem(int8* pData, int dataSize, int mainIndex); //throw (eArchive)
        // inserts the given item into the array; returns the index that it was inserted into.
        // this asserts that there is room for the new element, and updates the avail. space and
        // max index as necessary.
    void DeleteItem(int index); //throw (eArchive)
        // deletes the specified item; this asserts that the index is valid, and updates the avail.
        // space and max index as necessary.
    int8* GetDataForReading(int index, int32& dataSize); //throw (eArchive)
        // returns a pointer to the named data. This method will assert that the address is
        // valid. The data pointer returned is guarenteed to be valid only until the next
        // method call into this class.
    int8* GetDataForWriting(int index, int32& dataSize); //throw (eArchive)
        // this is the same as the previous function, except the dirty bit for the page is also set
    bool IsItemValid(int index) const; //throw (eArchive)
        // returns true if the given index has a valid value.

    bool IsClassValid() const;
    // returns true if the class has an internally consistant state. If an archive exception
    // occurs, false is returned. This method is good for determining if a newly opened block
    // file is actually a block file

    bool Initialized() const
    {
        return mbInit;
    }
    int GetAvailableSpace() const
    {
        ASSERT(Initialized());
        return mSpaceAvailable;
    }
    int GetNumItems() const
    {
        ASSERT(Initialized());
        return mNumItems;
    }
    // returns the number of items currently being stored in the array

    ///////////////////////////////////////////////////////////////////
    // tRecordIndex -- this is what each record in the array is
    //      indexed by. These come at the beginning of the block and
    //      the record data comes at the end.
    ///////////////////////////////////////////////////////////////////
    struct tRecordIndex
    {
    private:
        int32 mOffset;    // offset from the end of the block that my data is at; offset 1 is the last byte in the block
        int32 mMainIndex; // my main array index
    public:
        // byte order safe access methods...
        //
        void SetOffset(int32 off)
        {
            mOffset = tw_htonl(off);
        }
        int32 GetOffset()
        {
            return tw_ntohl(mOffset);
        }
        void SetMainIndex(int32 idx)
        {
            mMainIndex = tw_htonl(idx);
        }
        int32 GetMainIndex()
        {
            return tw_ntohl(mMainIndex);
        }
    };
    ///////////////////////////////////////////////////////////////////
    // tHeader -- the info that appears at the beginning of the block
    ///////////////////////////////////////////////////////////////////
    struct tHeader
    {
    private:
        int32 mSpaceAvailable;
        int32 mNumItems;

    public:
        // byte order safe access methods...
        //
        void SetSpaceAvail(int32 sa)
        {
            mSpaceAvailable = tw_htonl(sa);
        }
        int32 GetSpaceAvail()
        {
            return tw_ntohl(mSpaceAvailable);
        }
        void SetNumItems(int32 ni)
        {
            mNumItems = tw_htonl(ni);
        }
        int32 GetNumItems()
        {
            return tw_ntohl(mNumItems);
        }
    };

    enum
    {
        MAX_RECORDS = cBlockFile::BLOCK_SIZE / (sizeof(tRecordIndex) + sizeof(uint32)),
        // MAX_RECORDS signifies the maximum number of records that can be stored in a single block.
        // It is rationalized like this: each used record needs a tRecordIndex plus an extra uint32
        // as a minimum storage requirement (even though it is ok to have a record that is filled in
        // with zero bytes)
        MARGIN = 10,
        // MARGIN is the min. amount of room that should exist between the indexes and the data. This
        // is used when calculating the available space, and also in assertions.
        MAX_DATA_SIZE = cBlockFile::BLOCK_SIZE - sizeof(tRecordIndex) - sizeof(tHeader) - MARGIN,
        // this is the largest single piece of data that could theoreticaly be stored (if it were the
        // only thing stored in the block). We assume that the single record index will take up space,
        // the header will take up space, and the margin will take up space.
        INVALID_INDEX = -1
        // when tRecordIndex::mMainIndex is set to this, it indicates that the index is not being used.
    };

    ///////////////////////////////////////////////////////////////////
    // tIndexArray -- the beginning of a block is treated
    //      as one of these
    ///////////////////////////////////////////////////////////////////
    struct tIndexArray
    {
        tHeader      mHeader;
        tRecordIndex maRecordIndex[MAX_RECORDS];
    };

private:
    cBlockFile* mpBlockFile;
    int         mBlockNum;       // the block that I am operating on
    int         mSpaceAvailable; // amount of storage I have left
    int         mNumItems;       // number of items currently being stored in this array
    bool        mbInit;          // has InitXXX() been called?

    void UpdateFreeSpace(cBlockFile::Block* pBlock);
    // calculates the total free space available in this array and assigns
    // it to mSpaceAvailable. Note that the value of mSpaceAvailable signifies
    // the size of the largest single item that could be added to the array.
    void WriteHeaderInfo(cBlockFile::Block* pBlock);
    // gets the header information at the top of our block in sync with our member vars
    void ReadHeaderInfo(cBlockFile::Block* pBlock);
    // reads the header info from the block and sets our member vars to what was read

    ///////////////////////////////////////////////////////////////////////////
    // Profiling / Debugging interface
    ///////////////////////////////////////////////////////////////////////////
#ifdef _BLOCKFILE_DEBUG
public:
    void TraceContents(int dl = -1) const;
    // traces out all the info we can on the current state of this class
    // dl is the debug level to trace it at; -1 means to use D_DEBUG
    void AssertValid() const;
    // ASSERTs as much as we can about the consistency of our internal state.
#endif
};


#endif //__BLOCKRECORDARRAY_H
