/* 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. */

//
// C++ Implementation: bcTest
//
// Description:  A simple Test driver for the Disk Block Buffer Cache
//
//
// Author: Jason Rodriguez <jrodriguez@calpont.com>, (C) 2007
//
// Copyright: See COPYING file that comes with this distribution
//
//

#include <vector>
#include <string>
#include <iostream>
#include <sys/time.h>
#include <unistd.h>

#include "blockcacheclient.h"
#include "stats.h"
#include "brm.h"
using namespace BRM;
using namespace dbbc;
using namespace std;

Stats* gPMStatsPtr = NULL;
bool gPMProfOn = false;
uint32_t gSession = 0;

int fLoops = 1;
int thr_cnt = 1;
uint64_t bfoundTot = 0;
uint64_t bnfoundTot = 0;
uint64_t rfoundTot = 0;
uint64_t rnfoundTot = 0;
uint64_t rangeOpCountTot = 0;
uint64_t blockOpCountTot = 0;
uint64_t noOpCountTot = 0;

struct thr_wait_struct
{
  int predicate;
  pthread_mutex_t fMutex;
  pthread_cond_t fCond;
  vector<LBIDRange_v> range_thr;
};
typedef thr_wait_struct thr_wait_t;

const int32_t cacheSize = 175000;
BlockRequestProcessor BRP(cacheSize, 4, 16);
BRM::VER_t ver = 0xFFFF;
u_int64_t totBlocks = 0;

void* thr_client(void* clientArgs)
{
  blockCacheClient bc(BRP);
  uint64_t bfound = 0;
  uint64_t bnfound = 0;
  uint64_t rfound = 0;
  uint64_t rnfound = 0;
  uint64_t rangeOpCount = 0;
  uint64_t blockOpCount = 0;
  uint64_t noOpCount = 0;
  thr_wait_t* clientWait = (thr_wait_t*)clientArgs;
  struct timeval tv, tv2;
  uint32_t randstate = 0;
  randstate = static_cast<uint32_t>(tv.tv_usec);
  pthread_mutex_lock(&clientWait->fMutex);
  clientWait->predicate++;
  pthread_mutex_unlock(&clientWait->fMutex);
  vector<LBIDRange_v>& range_thr = clientWait->range_thr;

  gettimeofday(&tv, NULL);
  int j = 0;
  int s = 0;
  uint8_t fb[8192] = {0};
  LBIDRange_v& r = range_thr[0];

  for (int idx = 0; idx < fLoops; idx++)
  {
    for (int jdx = 0; jdx < range_thr.size(); jdx++)
    {
      uint32_t lbid = 0;
      bool b;
      int ret = 0;
      r = range_thr[jdx];

      for (int l = 0; l < r.size(); l++)
      {
        for (int m = r[l].start; m < r[l].start + r[l].size; m++)
        {
          // ret=bc.getBlock(m, ver, &fb, false, b);
          ret = bc.read(m, ver, &fb);

          if (ret)
          {
            bfound++;
          }
          else
          {
            bnfound++;
          }
        }
      }
    }
  }

  gettimeofday(&tv2, NULL);
  time_t tm = time(0);
  char t[50];
  ctime_r(&tm, t);
  t[strlen(t) - 1] = 0;
  uint32_t elTime = tv2.tv_sec - tv.tv_sec;
  uint64_t avgTot = 0;
  uint64_t rangeAvg = 0;
  uint64_t blkAvg = 0;

  if (elTime > 0)
  {
    avgTot = (bfound + rfound) / elTime;
    rangeAvg = (rfound) / elTime;
    blkAvg = (bfound) / elTime;
  }
  else
  {
    avgTot = bfound + rfound;
    rangeAvg = rfound;
    blkAvg = bfound;
  }

  cout << "thr(" << pthread_self() << ") tm " << t << " " << (tv2.tv_sec - tv.tv_sec) << endl
       << "\tBlk: c " << blockOpCount << " pass " << bfound << " fail " << bnfound << " Blks/Sec Blk "
       << blkAvg << endl
       << endl;

  pthread_mutex_lock(&clientWait->fMutex);
  bfoundTot += bfound;
  bnfoundTot += bnfound;
  rfoundTot += rfound;
  rnfoundTot += rnfound;
  rangeOpCountTot += rangeOpCount;
  blockOpCountTot += blockOpCount;
  noOpCountTot += noOpCount;
  clientWait->predicate--;
  pthread_cond_signal(&clientWait->fCond);
  pthread_mutex_unlock(&clientWait->fMutex);

  return NULL;

}  // end thr_client

void LoadRange(const LBIDRange_v& v, uint32_t& loadCount)
{
  blockCacheClient bc(BRP);

  uint32_t rCount = 0;

  for (uint32_t i = 0; i < v.size(); i++)
  {
    const InlineLBIDRange r = {v[i].start, v[i].size};

    if (r.size <= 1024)
    {
      bc.check(r, ver, rCount);
      loadCount += rCount;
    }

    rCount = 0;
  }
}

void ReadRange(const LBIDRange_v& v)
{
  blockCacheClient bc(BRP);
  int found = 0;
  int notfound = 0;
  int ret = 0;

  for (uint32_t i = 0; i < v.size(); i++)
  {
    const InlineLBIDRange r = {v[i].start, v[i].size};
    FileBuffer fb(-1, -1);

    for (int j = r.start; j < r.start + r.size; j++)
    {
      if (r.size > 1024)
        continue;

      ret = bc.read(j, ver, fb);

      if (ret)
        found++;
      else
        notfound++;

      ret = 0;
    }

    totBlocks += found;
    totBlocks += notfound;
    found = 0;
    notfound = 0;
  }
}

void LoadLbid(const BRM::LBID_t lbid, const BRM::VER_t ver)
{
  blockCacheClient bc(BRP);
  bool b;
  bc.check(lbid, ver, false, b);
}

void ReadLbid(const BRM::LBID_t lbid, const BRM::VER_t ver)
{
  static int found = 0, notfound = 0;
  uint8_t d[8192];
  blockCacheClient bc(BRP);
  // FileBuffer fb(-1, -1);
  // bc.read(lbid, ver, fb);
  int ret = bc.read(lbid, ver, d);

  if (ret)
    found++;
  else
    notfound++;

  if ((found + notfound) % 10000 == 0)
    cout << "found " << found << " notfound " << notfound << endl;
}

//
int main(int argc, char* argv[])
{
  if (argc >= 2)
    thr_cnt = atoi(argv[1]);

  if (argc >= 3)
    fLoops = atoi(argv[2]);

  if (thr_cnt <= 0)
    thr_cnt = 1;

  if (thr_cnt > 1024)
    thr_cnt = 1024;

  if (fLoops <= 0)
    fLoops = 1;

  LBIDRange_v r;
  vector<LBIDRange_v> ranges;
  DBRM dbrm;
  uint32_t hwm, lowfbo, highfbo, fbo, extentSize, lowlbid;
  struct timeval tv, tv2;

  cout << "Starting " << endl;
  extentSize = dbrm.getExtentSize();
  BRM::OID_t oid = 3000;
  uint32_t totalExt = 0;

  do
  {
    int ret = dbrm.lookup(oid, r);

    if (ret == 0 && r.size() > 0)
    {
      lowlbid = (r[0].start / extentSize) * extentSize;
      dbrm.lookup(r[0].start, ver, false, oid, fbo);  // need the oid
      dbrm.getHWM(oid, hwm);
      lowfbo = fbo - (r[0].start - lowlbid);
      highfbo = lowfbo + extentSize;
      r[0].start = lowlbid;

      if (hwm < highfbo)
        r[0].size = hwm - lowfbo + 1;
      else
        r[0].size = extentSize;

      for (uint32_t idx = 0; idx < r.size(); idx++)
        totalExt += r[idx].size;

      ranges.push_back(r);
    }

    oid++;
  } while ((r.size() > 0 || oid < 900000));

  cout << ranges.size() << " ranges found" << endl;

  gettimeofday(&tv, NULL);
  uint32_t blksLoaded = 0;
  int rangesLoaded = 0;

  for (uint32_t i = 0; i < ranges.size() && blksLoaded < cacheSize; i++)
  {
    LoadRange(ranges[i], blksLoaded);
    rangesLoaded++;
  }

  cout << endl;

  gettimeofday(&tv2, NULL);
  cout << "Loaded: " << blksLoaded << " blks " << rangesLoaded << " ranges sec: " << tv2.tv_sec - tv.tv_sec
       << endl;

  while (ranges.size() > rangesLoaded)
    ranges.pop_back();

#ifdef BLAH

  for (uint32_t i = 0; i < ranges; i++)
    ReadRange(ranges[i]);

  for (uint32_t i = 0; i < ranges.size(); i++)
  {
    LBIDRange_v rv = ranges[i];

    for (uint32_t j = 0; j < rv.size(); j++)
    {
      const InlineLBIDRange l = {rv[j].start, rv[j].size};

      for (uint32_t k = l.start; k < l.start + l.size; k++)
      {
        LoadLbid(k, ver);
        ReadLbid(k, ver);
      }
    }
  }

#endif

  pthread_t thr_id[thr_cnt];
  thr_wait_t thr_wait = {0, PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER, ranges};

  // start threads running
  cout << "Starting driver threads" << endl;
  gettimeofday(&tv, NULL);
  memset(thr_id, 0, thr_cnt * (sizeof(pthread_t)));

  for (int i = 0; i < thr_cnt; i++)
  {
    pthread_create(&thr_id[i], NULL, thr_client, &thr_wait);
  }

  // waiting until all threads have indicated completion
  pthread_mutex_lock(&thr_wait.fMutex);

  while (thr_wait.predicate > 0)
  {
    pthread_cond_wait(&thr_wait.fCond, &thr_wait.fMutex);
  }

  pthread_mutex_unlock(&thr_wait.fMutex);

  // join threads back to main
  for (int i = 0; i < thr_cnt; i++)
  {
    pthread_join(thr_id[i], NULL);
  }

  gettimeofday(&tv2, NULL);
  time_t tm = time(0);
  char t[50];
  ctime_r(&tm, t);
  t[strlen(t) - 1] = 0;
  uint32_t elTime = tv2.tv_sec - tv.tv_sec;
  uint64_t total = bfoundTot + rfoundTot;
  uint64_t avgTot = 0;
  uint64_t rangeAvg = 0;
  uint64_t blkAvg = 0;

  if (elTime > 0)
  {
    avgTot = (bfoundTot + rfoundTot) / elTime;
    rangeAvg = (rfoundTot) / elTime;
    blkAvg = (bfoundTot) / elTime;
  }
  else
  {
    avgTot = bfoundTot + rfoundTot;
    rangeAvg = rfoundTot;
    blkAvg = bfoundTot;
  }

  cout << "Summary tm " << t << " " << (tv2.tv_sec - tv.tv_sec) << endl
       << "\tBlk: c " << blockOpCountTot << " pass " << bfoundTot << " fail " << bnfoundTot <<
      //"\tRng: c "<< rangeOpCountTot << " pass " << rfoundTot << " fail " << rnfoundTot << endl <<
      //"\tNoOp: c " << noOpCountTot << " Total " << total << endl <<
      //"\tblks/sec Blk " << blkAvg << " Rng " << rangeAvg << " Tot " << avgTot << " Thr " << avgTot/thr_cnt
      //<<  endl << endl;
      " Blks/Sec Blk " << blkAvg << " Thr " << avgTot / thr_cnt << endl
       << endl;

  return 0;

}  // end main
