/* Copyright (C) 2014 InfiniDB, Inc.

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License
   as published by the Free Software Foundation; version 2 of
   the License.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  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., 51 Franklin Street, Fifth Floor, Boston,
   MA 02110-1301, USA. */

/******************************************************************************
 * $Id: vbbm.h 1926 2013-06-30 21:18:14Z wweeks $
 *
 *****************************************************************************/

/** @file
 * class XXX interface
 */

#pragma once

#include <vector>
//#define NDEBUG
#include <cassert>
#include <boost/thread.hpp>

#include "brmshmimpl.h"

#include "shmkeys.h"
#include "brmtypes.h"
#include "undoable.h"
#include "mastersegmenttable.h"

// These config parameters need to be loaded

// will get a small hash function performance boost by using powers of 2
#define VBSTORAGE_INITIAL_COUNT 100000
#define VBSTORAGE_INITIAL_SIZE (VBSTORAGE_INITIAL_COUNT * sizeof(VBBMEntry))
#define VBSTORAGE_INCREMENT_COUNT 10000
#define VBSTORAGE_INCREMENT (VBSTORAGE_INCREMENT_COUNT * sizeof(VBBMEntry))

// (average list length = 4)
#define VBTABLE_INITIAL_SIZE (25000 * sizeof(int))
#define VBTABLE_INCREMENT (2500 * sizeof(int))

#define VBBM_INCREMENT (VBTABLE_INCREMENT + VBSTORAGE_INCREMENT)

#define VBBM_SIZE(files, entries)                                                                   \
  ((entries * sizeof(VBBMEntry)) + (entries / 4 * sizeof(int)) + (files * sizeof(VBFileMetadata)) + \
   sizeof(VBShmsegHeader))

#define EXPORT

namespace idbdatafile
{
class IDBDataFile;
}

namespace BRM
{
class VSS;

struct VBFileMetadata
{
  OID_t OID;
  uint64_t fileSize;
  uint64_t nextOffset;
};

struct VBBMEntry
{
  LBID_t lbid;
  VER_t verID;
  OID_t vbOID;
  uint32_t vbFBO;
  int next;
  EXPORT VBBMEntry();
};

struct VBShmsegHeader
{
  int nFiles;
  int vbCapacity;
  int vbCurrentSize;
  int vbLWM;
  int numHashBuckets;

  // the rest of the overlay looks like this
  // 	VBFileMetadata files[nFiles];
  // 	int hashBuckets[numHashBuckets];
  // 	VBBMEntry storage[vbCapacity];
};

class VBBMImpl
{
 public:
  static VBBMImpl* makeVBBMImpl(unsigned key, off_t size, bool readOnly = false);

  inline void grow(unsigned key, off_t size)
#ifdef NDEBUG
  {
    fVBBM.grow(key, size);
  }
#else
  {
    int rc = fVBBM.grow(key, size);
    idbassert(rc == 0);
  }
#endif
  inline void makeReadOnly()
  {
    fVBBM.setReadOnly();
  }
  inline void clear(unsigned key, off_t size)
  {
    fVBBM.clear(key, size);
  }
  inline void swapout(BRMShmImpl& rhs)
  {
    fVBBM.swap(rhs);
    rhs.destroy();
  }
  inline unsigned key() const
  {
    return fVBBM.key();
  }

  inline VBShmsegHeader* get() const
  {
    return reinterpret_cast<VBShmsegHeader*>(fVBBM.fMapreg.get_address());
  }

 private:
  VBBMImpl(unsigned key, off_t size, bool readOnly = false);
  ~VBBMImpl();
  VBBMImpl(const VBBMImpl& rhs);
  VBBMImpl& operator=(const VBBMImpl& rhs);

  BRMShmImpl fVBBM;

  static boost::mutex fInstanceMutex;
  static VBBMImpl* fInstance;
};

/** @brief The Version Buffer Block Map (VBBM)
 *
 * At a high level, the VBBM maintains a table describing the contents of
 * the Version Buffer.  For every entry in the Version Buffer, it associates its
 * <LBID, VerID> identifier with the OID and offset it is stored at.
 *
 * As implemented, it is a hash table and a set of lists that exist in
 * shared memory.  The hash table is keyed by <LBID, VerID>, and
 * each valid entry points to the head of a unique list.  Each list element
 * contains the LBID, VerID, VB OID, and VB offset that encapsulate "an entry in the
 * VBBM table".  Every list contains all elements that collide on that hash table
 * entry that points to it, "load factor" has no bearing on performance,
 * and lists can grow arbitrarily large.
 * Technically lookups are O(n), but in normal circumstances it'll
 * be constant time.  As things are right now, we expect there to be about
 * 100k VBBM entries.  The hash table is sized such that on average there will be 4
 * entries per list when it's at capacity.
 *
 * The memory management & structure manipulation code is nearly identical
 * to that in the VSS, so any bugs found here are likely there as well.
 *
 * Shared memory is managed using code similar to the ExtentMap & VSS.  When
 * the shared memory segment needs to grow, it is write-locked, a new one
 * is created, the contents are reinserted to the new one, the key is
 * registered, and the old segment is destroyed when the last reference to it
 * is detached.
 */

class VBBM : public Undoable
{
 public:
  enum OPS
  {
    NONE,
    READ,
    WRITE
  };

  EXPORT VBBM();
  EXPORT ~VBBM();

  EXPORT void lock(OPS op);
  EXPORT void release(OPS op);
  EXPORT int lookup(LBID_t lbid, VER_t ver, OID_t& oid, uint32_t& fbo) const;
  EXPORT void insert(LBID_t lbid, VER_t ver, OID_t oid, uint32_t fbo, bool loading = false);
  EXPORT void getBlocks(int num, OID_t vbOID, std::vector<VBRange>& vbRanges, VSS& vss, bool flushPMCache);
  EXPORT void removeEntry(LBID_t, VER_t ver);

  EXPORT int size() const;
  EXPORT bool hashEmpty() const;
  EXPORT int checkConsistency() const;
  EXPORT void setReadOnly();

  EXPORT void clear();
  EXPORT void load(std::string filename);
  EXPORT void loadVersion2(idbdatafile::IDBDataFile* in);
  EXPORT void save(std::string filename);

#ifdef BRM_DEBUG
  EXPORT int getShmid() const;
#endif

 private:
  VBBM(const VBBM&);
  VBBM& operator=(const VBBM&);

  VBShmsegHeader* vbbm;
  VBFileMetadata* files;
  int* hashBuckets;
  VBBMEntry* storage;

  key_t currentVBBMShmkey;
  int vbbmShmid;
  bool r_only;
  MSTEntry* vbbmShminfo;
  MasterSegmentTable mst;
  static boost::mutex mutex;  // @bug5355 - made mutex static
  static const int MAX_IO_RETRIES = 10;

  key_t chooseShmkey() const;
  void growVBBM(bool addAFile = false);
  void growForLoad(int count);
  void copyVBBM(VBShmsegHeader* dest);
  void initShmseg(int nFiles);

  void _insert(VBBMEntry& e, VBShmsegHeader* dest, int* destTable, VBBMEntry* destStorage,
               bool loading = false);
  int getIndex(LBID_t lbid, VER_t verID, int& prev, int& bucket) const;
  ShmKeys fShmKeys;
  VBBMImpl* fPVBBMImpl;

  /* Shared nothing mods */
  uint64_t currentFileSize;
  void setCurrentFileSize();
  uint32_t addVBFileIfNotExists(OID_t vbOID);
};

}  // namespace BRM

#undef EXPORT
