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

#include <vector>
#include <string>
#include <iomanip>
#include <iostream>
#include <sys/time.h>
#include <unistd.h>
#include <boost/thread/thread.hpp>

#include "blockrequestprocessor.h"
#include "blockcacheclient.h"
#include "stats.h"
#include "brm.h"
#include "logger.h"
#include "iomanager.h"

using namespace BRM;
using namespace dbbc;
using namespace std;
using namespace logging;
using namespace primitiveprocessor;

Stats* gPMStatsPtr = NULL;
bool gPMProfOn = false;
uint32_t gSession = 0;
uint32_t lastRangeListIdx = 0;
const uint32_t maxLoadBlocks(1024 * 1024);

void timespec_sub(const struct timespec& tv1,
                  const struct timespec& tv2,
                  double& tm)
{
    tm = (double)(tv2.tv_sec - tv1.tv_sec) + 1.e-9 * (tv2.tv_nsec - tv1.tv_nsec);
}

namespace primitiveprocessor
{
Logger ml;
}


class BCTest
{

public:

    struct OidRanges
    {
        OID_t oid;
        HWM_t hwm;
        LBIDRange_v ranges;
        OidRanges(const OID_t o, const HWM_t h, const LBIDRange_v r)
        {
            oid = o;
            hwm = h;
            ranges = r;
        }
    }; //struct OidRanges

    BCTest(const int cacheSz = 64 * 1024, int readThr = 2, int readAhead = 1024);

    typedef OidRanges OidRanges_t;
    typedef vector<OidRanges_t>OidRangesList_t;
    OidRangesList_t OidRangesList;

    DBRM dbrm;
    uint32_t extentSize;
    BRM::OID_t maxOid;

    int fCacheSz;
    int fReadThr;
    int fReadAhead;
    uint32_t maxBlocksAvailable;
    uint32_t fExtentSize;

    void setUp();
    int LoadOid(const OidRanges_t& o, uint32_t& loadCount);
    void LoadLbid(const BRM::LBID_t lbid, const BRM::VER_t ver);
    int ReadOidRanges(const OidRanges_t& v, uint32_t* hits, uint32_t* miss);
    void ReadOidLbids(const BRM::LBID_t lbid, const BRM::VER_t ver);

    BlockRequestProcessor BRP;

}; // class BCTest


BCTest::BCTest(int cacheSz, int readThr, int readAhead) :
    fCacheSz(cacheSz),
    fReadThr(readThr),
    fReadAhead(readAhead),
    BRP(fCacheSz, fReadThr, fReadAhead)
{
    setUp();
} // ctor

//
void BCTest::setUp()
{
    LBIDRange_v r;
    HWM_t hwm;
    OID_t oid = 1000;
    extentSize = dbrm.getExtentSize();
    maxBlocksAvailable = 0;
    int i = 0;
    fExtentSize = dbrm.getExtentSize();

    while ( oid < 5000 )
    {
        int ret = 0;
        ret = dbrm.lookup(oid, r);

        if (ret == 0 && r.size() > 0)
        {
            dbrm.getHWM(oid, hwm);
            maxBlocksAvailable += (r.size() * extentSize);
            OidRanges_t oid_range(oid, hwm, r);
            OidRangesList.push_back(oid_range);
            //cout << "Setup i: " << i++ << " o: " << oid
            //	<< " r: " << ret << " s: " << r.size()
            //	<< " m: " << maxBlocksAvailable
            //	<< endl;
            hwm = 0;
            r.clear();
        }

        oid++;
    }

    //cout << "\t" << OidRangesList.size() << " oid ranges loaded " << endl << endl;
    i = 0;
} // setUp()

int BCTest::LoadOid(const OidRanges_t& o, uint32_t& loadCount)
{
    blockCacheClient bc(BRP);
    uint32_t rCount = 0;

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

        if (r.size > 0)
        {
            bc.check(r, 0, rCount);
            //cout <<  "i: " << i << " c: " <<  rCount << " " << o.ranges[i].size << endl;
            loadCount += rCount;
        }

        rCount = 0;
    } // for

    //cout << "hwm: " << o.hwm << " tot: " << loadCount <<  " " << o.ranges.size() << endl;

    return loadCount;

} // LoadOid

int BCTest::ReadOidRanges(const OidRanges_t& v, uint32_t* hits, uint32_t* miss)
{

    blockCacheClient bc(BRP);
    uint32_t readBlocks = 0;
    uint32_t missBlocks = 0;
    uint8_t inBuff[8192];
    int ret;

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

        for (int j = r.start; j < r.start + r.size; j++)
        {
            ret = bc.read(j, 0, fb);
            FileBuffer* ptr = bc.getBlockPtr(j, 0);

            if (ptr)
            {
                readBlocks++;
                memcpy(inBuff, ptr->getData(), 8192);
            }
            else
                missBlocks++;
        }

        *hits += readBlocks;
        *miss += missBlocks;

        //cout << " -- Read range idx: " << i << " hits: " << readBlocks << " miss: " << missBlocks << " hwm: " << v.hwm << endl;
    }

    return readBlocks;

} // ReadRange

// add one block to block cache
//
void BCTest::LoadLbid(const BRM::LBID_t lbid, const BRM::VER_t ver)
{
    blockCacheClient bc(BRP);
    bool b;
    bc.check(lbid, ver, false, b);
} // LoadLbid

// get one block out of block cache
//
void BCTest::ReadOidLbids(const BRM::LBID_t lbid, const BRM::VER_t ver)
{
    uint8_t d[8192] = {'\0'};
    blockCacheClient bc(BRP);
    bc.read(lbid, ver, d);
} // ReadLbid


struct loadThr
{
    loadThr(BCTest& bc, int reps = 1) : fBC(bc), fReps(reps)
    {
    }

    void operator()()
    {
        uint32_t loadedBlocks = 0;
        uint32_t oidBlocks;
        uint32_t i = 0;
        uint32_t rc = 0;
        struct timespec tm1;
        struct timespec tm2;

        tm1.tv_sec = 0;
        tm1.tv_nsec = 0;
        tm2.tv_sec = 0;
        tm2.tv_nsec = 0;
        double tm3 = 0;
        clock_gettime(CLOCK_REALTIME, &tm1);

        for (i = 0; (loadedBlocks + (fBC.OidRangesList[i].ranges.size()*fBC.extentSize)) < fBC.fCacheSz && i < fBC.OidRangesList.size(); i++)
        {
            oidBlocks = 0;
            rc = fBC.LoadOid(fBC.OidRangesList[i], oidBlocks);
            //cout
            //	<< "-- Load " << i << " " << fBC.OidRangesList[i].oid
            //	<< " h: " << fBC.OidRangesList[i].hwm
            //	<< "/" << oidBlocks << " " << loadedBlocks
            //	<< endl;
            loadedBlocks += oidBlocks;
        } // for (i...

        lastRangeListIdx = i;

        clock_gettime(CLOCK_REALTIME, &tm2);
        timespec_sub(tm1, tm2, tm3);

        cout << "load ld: " << loadedBlocks
             << " sz: " << fBC.fCacheSz
             << " rng: " << lastRangeListIdx
             << " tm: " << right << setw(10) << fixed << tm3
             << endl;

    } // operator()

    BCTest& fBC;
    uint32_t fReps;

};

struct readThr
{

    readThr(BCTest& bc, int reps = 1) : fBC(bc), fReps(reps) {}

    void operator()()
    {
        uint32_t rc = 0;
        uint32_t readBlocks = 0;
        uint32_t hits = 0;
        uint32_t miss = 0;
        uint32_t hitsTot = 0;
        uint32_t missTot = 0;
        struct timespec tm1;
        struct timespec tm2;
        tm1.tv_sec = 0;
        tm1.tv_nsec = 0;
        tm2.tv_sec = 0;
        tm2.tv_nsec = 0;
        double tm3 = 0;
        clock_gettime(CLOCK_REALTIME, &tm1);

        for (uint32_t k = 0; k < fReps; k++)
        {
            for (uint32_t i = 0; i < lastRangeListIdx && i < fBC.OidRangesList.size() ; i++)
            {
                rc = fBC.ReadOidRanges(fBC.OidRangesList[i], &hits, &miss);
                //cout << "-- ReadTest " << fBC.OidRangesList[i].oid << " h: " << fBC.OidRangesList[i].hwm << "/" << rc << endl;
                readBlocks += rc;
                rc = 0;
            }

            hitsTot += hits;
            missTot += miss;
            hits = 0;
            miss = 0;
        }

        clock_gettime(CLOCK_REALTIME, &tm2);
        timespec_sub(tm1, tm2, tm3);

        cout << "readtest rd: " << hitsTot << "/" << missTot
             << " sz: " 		<< fBC.fCacheSz
             << " tm: " << right << setw(10) << fixed << tm3
             << endl;

    } // operator()


    BCTest& fBC;
    uint32_t fReps;

};

void usage()
{
    cout << "testbc <cacheSz/1024> <reader threads> <read ahead> <client threads> <reps>" << endl;
}

//
int main(int argc, char* argv[])
{

    int cacheSz = 128; // K number of blocks
    int thr = 1;
    int ra = 1024;
    int clients = 1;
    int reps = 1;

    if (argc > 1 && atoi(argv[1]) > 0)
        cacheSz = atoi(argv[1]) * 1024;

    if (argc > 2 && atoi(argv[2]) > 0)
        thr = atoi(argv[2]);

    if (argc > 3 && atoi(argv[3]) > 0)
        ra = atoi(argv[3]);

    if (argc > 4 && atoi(argv[4]) > 0)
        clients = atoi(argv[4]);

    if (argc > 5 && atoi(argv[5]) > 0)
        reps = atoi(argv[5]);

    BCTest bc(cacheSz, thr, ra);

    cout <<
         "Cache Size: " << cacheSz <<
         " read Threads: " << thr <<
         " read Ahead: " << ra <<
         " clients: " << clients <<
         " repetitions: " << reps <<
         " max Blocks: " << bc.maxBlocksAvailable <<
         endl;

    // loader cache
    struct loadThr loader1(bc, 1);
    struct readThr reader1(bc, reps);
    vector<boost::thread*> v;

    boost::thread* th1 = new boost::thread(loader1);
    th1->join();

    for (int i = 0; i < clients; i++)
    {
        boost::thread* rd1 = new boost::thread(reader1);
        v.push_back(rd1);
    }

    for (int i = 0; i < clients; i++)
    {
        boost::thread* rd1 = v[i];
        rd1->join();
        delete rd1;
    }

    v.clear();

    delete th1;

    return 0;

} // end main
