// . Matt Wells, copyright Feb 2001

// . we use a big buffer, m_buf, into which we sequentially store records as 
//   they are added to the cache
// . each record we store in the big buffer has a header which consists
//   of a key96_t, recordSize(int32_t), timestamp and the record data
// . the header is as follows:
//   a collnum_t (use sizeof(collnum_t)) that identifies the collection
//   a 12 byte key96_t (actually, it is now m_cks bytes)
//   a 4 bytes timestamp (seconds since the epoch, like time_t)
//   a 4 bytes data size
//   the data
// . we have a hash table that maps a key96_t to a ptr to a record header in the
//   big buffer
// . what if we run out of room in the hash table? we delete the oldest
//   records in the big buffer so we can remove their ptrs from the hash table
// . we keep an "tail" ptr into the hash table that point to the last
//   non-overwritten record in the big buffer
// . when we run out of room in the big buffer we wrap our ptr to the start
//   and we remove any records from the hashtable that we overwrite using
//   the tail ptr
// . when a record is read from the cache we also promote it by copying it to
//   the head of m_buf, m_bufOffset. since modern day pentiums do 2.5-6.4GB/s
//   this is ok, it will not be the bottleneck by far. if the record is
//   expired we do not promote it. The advantage is we don't have ANY memory
//   fragmentation and utilize 100% of the memory. Copying a 6 Megabyte
//   list takes like 2.4ms on a new pentium, so we should allow regular
//   allocating if the record size is 256k or more. Copying 256k only
//   takes .1 ms on the P4 2.60CGHz. This is on the TODO list.

#ifndef GB_RDBCACHE_H
#define GB_RDBCACHE_H

// . TODO:
// . if size of added rec is ABOVE this, then don't use our memory buffer
// . because we copy a rec to the head of memory buffer (m_bufOffset) every 
//   time that rec is accessed and doing that for big recs is more intensive
// . the idea is that allocating/freeing memory for smaller recs is what
//   causes the memory fragmentation
// . to stay under m_maxMem we should limit each memory buffer to 1M or so
//   and free the tailing memory buffer to make room for a large unbuffered rec
//#define MEM_LIMIT (256*1024)

#include "JobScheduler.h" //job_exit_t
#include <time.h>       // time_t
#include "collnum_t.h"
#include "GbMutex.h"

class RdbList;

class RdbCache {

 public:

	friend class Rdb;

	// constructor & destructor
	 RdbCache();
	~RdbCache();

	void reset();

	// . this just clears the contents of the cache for a particular coll
	// . used by g_collectiondb.delRec() call to Rdb::delColl() to 
	//   clear out the collection's stuff in the cache
	void clear ( collnum_t collnum ) ;

	bool isInitialized () const { 
		if ( m_ptrs ) return true; 
		return false;
	}

	// . we are allowed to keep a min mem of "minCacheSize"
	// . a fixedDataSize of -1 means the dataSize varies from rec to rec
	// . set "maxNumNodes" to -1 for it to be auto determined
	// . can only do this if fixedDataSize is not -1
	bool init ( int32_t maxCacheMem   , 
		    int32_t fixedDataSize , 
		    int32_t maxCacheNodes ,
		    const char *dbname       ,
		    bool  loadFromDisk ,
		    char  cacheKeySize,
		    int32_t  numPtrsMax);

	// . a quick hack for SpiderCache.cpp
	// . if your record is always a 4 byte int32_t call this
	// . returns -1 if not found, so don't store -1 in there then
	int64_t getLongLong ( collnum_t collnum ,
				uint32_t key , int32_t maxAge , // in seconds
				bool promoteRecord );

	// this puts a int32_t in there
        void addLongLong ( collnum_t collnum ,
			   uint32_t key , int64_t value ) ;

	// . both key and data are int64_ts here
	// . returns -1 if not found
	int64_t getLongLong2 ( collnum_t collnum ,
				 uint64_t key , 
				 int32_t maxAge , // in seconds
				 bool promoteRecord );

	// this puts a int32_t in there
        void addLongLong2 ( collnum_t collnum ,
			   uint64_t key , int64_t value ) ;

	// same routines for int32_ts now, but key is a int64_t
	int32_t getLong ( collnum_t collnum ,
		       uint64_t key , int32_t maxAge , // in seconds
		       bool promoteRecord );
        void addLong ( collnum_t collnum ,
		       uint64_t key , int32_t value ) ;

	// . returns true if found, false if not found in cache
	// . sets *rec and *recSize iff found
	// . sets *cachedTime to time the rec was cached
	// . use maxAge of -1 to have no limit to the age of cached rec
	bool getRecord ( collnum_t collnum   ,
			 const char    *cacheKey   ,
			 char   **rec        ,
			 int32_t    *recSize    ,
			 bool     doCopy     ,
			 int32_t     maxAge     , // in seconds
			 bool     incCounts  ,
			 time_t  *cachedTime = NULL ,
			 bool     promoteRecord = true );

	bool getRecord ( const char    *coll       ,
			 const char    *cacheKey   ,
			 char   **rec        ,
			 int32_t    *recSize    ,
			 bool     doCopy     ,
			 int32_t     maxAge     , // in seconds
			 bool     incCounts  ,
			 time_t  *cachedTime = NULL ,
			 bool     promoteRecord = true);

	bool getRecord ( collnum_t collnum   ,
			 key96_t    cacheKey   ,
			 char   **rec        ,
			 int32_t    *recSize    ,
			 bool     doCopy     ,
			 int32_t     maxAge     , // in seconds
			 bool     incCounts  ,
			 time_t  *cachedTime = NULL,
			 bool     promoteRecord = true) {
		return getRecord (collnum,(const char *)&cacheKey,rec,recSize,doCopy,
				  maxAge,incCounts,cachedTime, promoteRecord);
	}

	bool getRecord ( const char    *coll       ,
			 key96_t    cacheKey   ,
			 char   **rec        ,
			 int32_t    *recSize    ,
			 bool     doCopy     ,
			 int32_t     maxAge     , // in seconds
			 bool     incCounts  ,
			 time_t  *cachedTime = NULL,
			 bool     promoteRecord = true) {
		return getRecord (coll,(const char *)&cacheKey,rec,recSize,doCopy,
				  maxAge,incCounts,cachedTime, promoteRecord);
	}

	// . add a list of only 1 record
	// . return false on error and set g_errno, otherwise return true
	// . recOffset is proper offset into the buffer system
	bool addRecord ( collnum_t collnum ,
			 const char *cacheKey ,
			 const char *rec      ,
			 int32_t  recSize  ,
			 int32_t  timestamp = 0 ,
			 char **retRecPtr = NULL ) ;

	bool addRecord ( const char *coll     ,
			 const char *cacheKey ,
			 const char *rec      ,
			 int32_t  recSize  ,
			 int32_t  timestamp = 0 );

	bool addRecord ( collnum_t collnum ,
			 key96_t cacheKey ,
			 const char *rec      ,
			 int32_t  recSize  ,
			 int32_t  timestamp = 0 ) {
		return addRecord(collnum,(const char *)&cacheKey,rec,recSize,
				 timestamp);
	}

	bool addRecord ( const char *coll     ,
			 key96_t cacheKey ,
			 const char *rec      ,
			 int32_t  recSize  ,
			 int32_t  timestamp = 0 ) {
		return addRecord(coll,(const char *)&cacheKey,rec,recSize,
				 timestamp);
	}

	void verify();

	// these include our mem AND our tree's mem combined
	int32_t getMemOccupied () const {
		return m_memOccupied ;
	}

	int32_t getMemAllocated() const {
		return m_memAllocated;
	}

	int32_t getMaxMem      () const { return m_maxMem; }

	// cache stats
	int64_t getNumHits() const;
	int64_t getNumMisses() const;
	int32_t getNumUsedNodes  () const { return m_numPtrsUsed; }
	int32_t getNumTotalNodes () const { return m_numPtrsMax ; }
	int64_t getNumAdds() const { return m_adds; }
	int64_t getNumDeletes() const { return m_deletes; }

	bool useDisk ( ) const { return m_useDisk; }
	bool load ( const char *dbname );
	bool save ();

	const char *getDbname () const { return m_dbname ? m_dbname : "unknown"; }

	const char *m_dbname;

	bool addRecord(collnum_t collnum,
		       const char *cacheKey,
		       const char *rec1,
		       int32_t  recSize1,
		       const char *rec2,
		       int32_t  recSize2,
		       int32_t  timestamp) {
		return addRecord(collnum,cacheKey,rec1,recSize1,rec2,recSize2,timestamp,NULL);
	}

private:
	bool save_r ( );
	bool save2_r ( int fd );
	bool load   ( );

	bool addRecord ( collnum_t collnum ,
			 const char *cacheKey ,
			 const char *rec1     ,
			 int32_t  recSize1 ,
			 const char *rec2     ,
			 int32_t  recSize2 ,
			 int32_t  timestamp ,
			 char **retRecPtr );

	// called internally by save()
	bool saveSome_r ( int fd, int32_t *iptr , int32_t *off ) ;

	bool deleteRec ( );
	void addKey     ( collnum_t collnum, const char *key, char *ptr );
	void removeKey  ( collnum_t collnum, const char *key, const char *rec );

	void markDeletedRecord(char *ptr);
	bool convertCache ( int32_t numPtrsMax , int32_t maxMem ) ;

	void incrementHits();
	void incrementMisses();

	bool m_convert;
	int32_t m_convertNumPtrsMax;
	int32_t m_convertMaxMem;

	pthread_mutex_t m_mtx; //big fat mutex protecting everything
	
	// . mem stats -- just for arrays we contain -- not in tree
	// . memory that is allocated and in use, including dataSizes
	int32_t m_memOccupied;

	// total memory allocated including dataSizes of our records
	int32_t m_memAllocated;

	// don't let m_memAllocated exceed this
	int32_t  m_maxMem;

	// . data is stored in m_bufs, an array of buffers
	// . we may have to use multiple bufs because we cannot allocate more
	//   than 128 Megabytes without pthread_create() failing
	// . we can have up to 32 bufs of 128M each, that's 4 gigs, plenty
	char      *m_bufs     [32];
	int32_t       m_bufSizes [32]; // size of the alloc'd space
	int32_t       m_numBufs;
	int32_t  m_totalBufSize; // gbpwrite() assumes 32 bits
	int32_t       m_offset; // where next rec is stored
	int32_t       m_tail;   // next rec to delete

	// the hash table, buckets are ptrs into an m_bufs[i]
	char     **m_ptrs;
	int32_t       m_numPtrsMax;
	int32_t       m_numPtrsUsed;
	int32_t       m_threshold;

	//memory labels
	char m_memoryLabelPtrs[128];
	char m_memoryLabelBufs[128];

	// cache hits and misses
	int64_t m_numHits; // includes partial hits & cached not-founds too
	int64_t m_numMisses;
	GbMutex mtx_hits_misses; //mutex protecting just hits&misses

	int32_t m_fixedDataSize;
	bool m_useDisk;  // load/save from disk?

	// have we wrapped yet?
	int8_t m_wrapped;

	// keySize of cache keys in bytes
	char m_cks;

	// count the add ops
	int64_t m_adds;
	int64_t m_deletes;

	bool m_needsSave;
	
	friend class RdbCacheLock;
};	


//Lock class to hold down an rdbcache while examining/copying or modifying a cache
class RdbCacheLock {
	RdbCacheLock(const RdbCacheLock&);
	RdbCacheLock& operator=(const RdbCacheLock&);
	RdbCache &rdc;
	bool locked;
public:
	RdbCacheLock(RdbCache &rdc_);
	~RdbCacheLock();
	void unlock();
};


#endif // GB_RDBCACHE_H