/* 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: we_dbfileop.cpp 4737 2013-08-14 20:45:46Z bwilkinson $

/** @file */

#include <unistd.h>
#include <stdio.h>
#include <cstring>
using namespace std;

#include "we_chunkmanager.h"

#include "we_dbfileop.h"

#include "we_stats.h"
#include "IDBDataFile.h"
using namespace idbdatafile;

using namespace BRM;

namespace WriteEngine
{
/**
 * Constructor
 */
DbFileOp::DbFileOp() : m_chunkManager(NULL)
{
}

/**
 * Default Destructor
 */
DbFileOp::~DbFileOp()
{
}

/***********************************************************
 * DESCRIPTION:
 *    flush the cache
 * PARAMETERS:
 *    none
 * RETURN:
 *    NO_ERROR if success, otherwise if fail
 ***********************************************************/
int DbFileOp::flushCache()
{
  BlockBuffer* curBuf;

  if (!Cache::getUseCache())
    return NO_ERROR;

  for (CacheMapIt it = Cache::m_writeList->begin(); it != Cache::m_writeList->end(); it++)
  {
    curBuf = it->second;
    RETURN_ON_ERROR(writeDBFile((*curBuf).cb.file.pFile, (*curBuf).block.data, (*curBuf).block.lbid));
  }

  RETURN_ON_ERROR(Cache::flushCache());
  return NO_ERROR;
}

/***********************************************************
 * DESCRIPTION:
 *    get an entry within a sub block
 *    NOTE: the difference with readSubBlockEntry is that
 *          getSubBlockEntry only works for buffer while
 *          readSubBlockEntry works for file and block
 * PARAMETERS:
 *    blockBuf - the block buffer
 *    sbid - sub block id
 *    entryNo - entry no within sub block
 *    width - width in bytes
 *    pStruct - sturcture pointer
 * RETURN:
 *    none
 ***********************************************************/
void DbFileOp::getSubBlockEntry(unsigned char* blockBuf, const int sbid, const int entryNo, const int width,
                                void* pStruct)
{
  unsigned char* pBlock;

  pBlock = blockBuf + BYTE_PER_SUBBLOCK * sbid + entryNo * MAX_COLUMN_BOUNDARY;
  memcpy(pStruct, pBlock, width);
}

/***********************************************************
 * DESCRIPTION:
 *    Read a block from a file at specified location
 * PARAMETERS:
 *    pFile - file handle
 *    readBuf - read buffer
 *    fbo - file block offset
 * RETURN:
 *    NO_ERROR if success
 *    other number if something wrong
 ***********************************************************/
int DbFileOp::readDBFile(IDBDataFile* pFile, unsigned char* readBuf, const uint64_t lbid, const bool isFbo)
{
  long long fboOffset = 0;

  if (!isFbo)
  {
    RETURN_ON_ERROR(setFileOffsetBlock(pFile, lbid));
  }
  else
  {
    fboOffset = (lbid) * (long)BYTE_PER_BLOCK;
    RETURN_ON_ERROR(setFileOffset(pFile, fboOffset));
  }

  return readFile(pFile, readBuf, BYTE_PER_BLOCK);
}

int DbFileOp::readDBFile(IDBDataFile* pFile, DataBlock* block, const uint64_t lbid, const bool isFbo)
{
  block->dirty = false;
  block->no = lbid;

  Stats::incIoBlockRead();

  return readDBFile(pFile, block->data, lbid, isFbo);
}

int DbFileOp::readDBFile(CommBlock& cb, unsigned char* readBuf, const uint64_t lbid)
{
  CacheKey key;

  if (Cache::getUseCache())
  {
    if (Cache::cacheKeyExist(cb.file.oid, lbid))
    {
      key = Cache::getCacheKey(cb.file.oid, lbid);
      RETURN_ON_ERROR(Cache::loadCacheBlock(key, readBuf));
      return NO_ERROR;
    }
  }

  RETURN_ON_ERROR(readDBFile(cb.file.pFile, readBuf, lbid));

  if (Cache::getUseCache())
  {
    int fbo = lbid;

    uint16_t dbRoot;
    uint32_t partition;
    uint16_t segment;
    RETURN_ON_ERROR(BRMWrapper::getInstance()->getFboOffset(lbid, dbRoot, partition, segment, fbo));

    if (Cache::getListSize(FREE_LIST) == 0)
    {
      if (isDebug(DEBUG_1))
      {
        printf("\nBefore flushing cache ");
        Cache::printCacheList();
      }

      // flush cache to give up more space
      RETURN_ON_ERROR(flushCache());

      if (isDebug(DEBUG_1))
      {
        printf("\nAfter flushing cache ");
        Cache::printCacheList();
      }
    }

    RETURN_ON_ERROR(Cache::insertLRUList(cb, lbid, fbo, readBuf));
  }

  return NO_ERROR;
}

/***********************************************************
 * DESCRIPTION: No change, old signature 10/17/06
 *    Read an entry within a sub block from a file
 *    NOTE: the difference with getSubBlockEntry is that
 *          getSubBlockEntry only works for buffer while
 *          readSubBlockEntry works for file and block
 * PARAMETERS:
 *    pFile - file handler
 *    block - the block structure
 *    fbo - file block offset
 *    sbid - sub block id
 *    entryNo - entry no within sub block
 *    width - width in bytes
 *    pStruct - sturcture pointer
 * RETURN:
 *    NO_ERROR if success
 *    other number if something wrong
 ***********************************************************/
int DbFileOp::readSubBlockEntry(IDBDataFile* pFile, DataBlock* block, const uint64_t lbid, const int sbid,
                                const int entryNo, const int width, void* pStruct)
{
  RETURN_ON_ERROR(readDBFile(pFile, block->data, lbid));
  getSubBlockEntry(block->data, sbid, entryNo, width, pStruct);

  return NO_ERROR;
}

int DbFileOp::readSubBlockEntry(CommBlock& cb, DataBlock* block, const uint64_t lbid, const int sbid,
                                const int entryNo, const int width, void* pStruct)
{
  RETURN_ON_ERROR(readDBFile(cb, block->data, lbid));
  getSubBlockEntry(block->data, sbid, entryNo, width, pStruct);

  return NO_ERROR;
}

/***********************************************************
 * DESCRIPTION:
 *    Set an entry within a sub block
 *    NOTE: the difference with writeSubBlockEntry is that
 *          setSubBlockEntry only works for buffer while
 *          writeSubBlockEntry works for file and block
 * PARAMETERS:
 *    blockBuf - the block buffer
 *    sbid - sub block id
 *    entryNo - entry no within sub block
 *    width - width in bytes
 *    pStruct - sturcture pointer
 * RETURN:
 *    none
 ***********************************************************/
void DbFileOp::setSubBlockEntry(unsigned char* blockBuf, const int sbid, const int entryNo, const int width,
                                const void* pStruct)
{
  unsigned char* pBlock;

  pBlock = blockBuf + BYTE_PER_SUBBLOCK * sbid + entryNo * MAX_COLUMN_BOUNDARY;
  memcpy(pBlock, pStruct, width);
}

/***********************************************************
 * DESCRIPTION:
 *    Write a number of blocks to the file at specified location
 * PARAMETERS:
 *    pFile - file handle
 *    writeBuf - write buffer
 *    fbo - file block offset
 *    numOfBlock - total number of file block offset
 * RETURN:
 *    NO_ERROR if success
 *    other number if something wrong
 ***********************************************************/
int DbFileOp::writeDBFile(CommBlock& cb, const unsigned char* writeBuf, const uint64_t lbid,
                          const int numOfBlock)
{
  CacheKey key;
  int ret;
  if (Cache::getUseCache())
  {
    if (Cache::cacheKeyExist(cb.file.oid, lbid))
    {
      key = Cache::getCacheKey(cb.file.oid, lbid);
      RETURN_ON_ERROR(Cache::modifyCacheBlock(key, writeBuf));
      return NO_ERROR;
    }
  }

  if (BRMWrapper::getUseVb())
  {
    RETURN_ON_ERROR(writeVB(cb.file.pFile, cb.file.oid, lbid));
  }

  ret = writeDBFile(cb.file.pFile, writeBuf, lbid, numOfBlock);

  if (BRMWrapper::getUseVb())
  {
    LBIDRange_v ranges;
    LBIDRange range;
    range.start = lbid;
    range.size = 1;
    ranges.push_back(range);
    BRMWrapper::getInstance()->writeVBEnd(getTransId(), ranges);
  }

  return ret;
}

int DbFileOp::writeDBFileNoVBCache(CommBlock& cb, const unsigned char* writeBuf, const int fbo,
                                   const int numOfBlock)
{
  return writeDBFileNoVBCache(cb.file.pFile, writeBuf, fbo, numOfBlock);
}

/***********************************************************
 * DESCRIPTION:
 *    Core function for writing data w/o using VB cache
 *    (bulk load dictionary store inserts)
 ***********************************************************/
int DbFileOp::writeDBFileNoVBCache(IDBDataFile* pFile, const unsigned char* writeBuf, const int /*fbo*/,
                                   const int numOfBlock)
{
#ifdef PROFILE
  // This function is only used by bulk load for dictionary store files,
  // so we log as such.
  Stats::startParseEvent(WE_STATS_WRITE_DCT);
#endif

  for (int i = 0; i < numOfBlock; i++)
  {
    Stats::incIoBlockWrite();
    RETURN_ON_ERROR(writeFile(pFile, writeBuf, BYTE_PER_BLOCK));
  }

#ifdef PROFILE
  Stats::stopParseEvent(WE_STATS_WRITE_DCT);
#endif

  return NO_ERROR;
}

/***********************************************************
 * DESCRIPTION:
 *    Core function for writing data using VB cache
 ***********************************************************/
int DbFileOp::writeDBFile(IDBDataFile* pFile, const unsigned char* writeBuf, const uint64_t lbid,
                          const int numOfBlock)
{
  RETURN_ON_ERROR(setFileOffsetBlock(pFile, lbid));

  for (int i = 0; i < numOfBlock; i++)
  {
    Stats::incIoBlockWrite();
    RETURN_ON_ERROR(writeFile(pFile, writeBuf, BYTE_PER_BLOCK));
  }

  return NO_ERROR;
}

// just don't have a good solution to consolidate with above functions
// Note: This is used with absolute FBO, no lbid involved
int DbFileOp::writeDBFileFbo(IDBDataFile* pFile, const unsigned char* writeBuf, const uint64_t fbo,
                             const int numOfBlock)
{
  long long fboOffset = 0;

  fboOffset = (fbo) * (long)BYTE_PER_BLOCK;
  RETURN_ON_ERROR(setFileOffset(pFile, fboOffset));

  for (int i = 0; i < numOfBlock; i++)
  {
    Stats::incIoBlockWrite();
    RETURN_ON_ERROR(writeFile(pFile, writeBuf, BYTE_PER_BLOCK));
  }

  return NO_ERROR;
}

/***********************************************************
 * DESCRIPTION:
 *    Write an entry within a sub block to a file
 *    NOTE: the difference with getSubBlockEntry is that
 *          setSubBlockEntry only works for buffer while
 *          writeSubBlockEntry works for file and block
 * PARAMETERS:
 *    pFile - file handler
 *    block - the block structure
 *    fbo - file block offset
 *    sbid - sub block id
 *    entryNo - entry no within sub block
 *    width - width in bytes
 *    pStruct - sturcture pointer
 * RETURN:
 *    NO_ERROR if success
 *    other number if something wrong
 ***********************************************************/
int DbFileOp::writeSubBlockEntry(IDBDataFile* pFile, DataBlock* block, const uint64_t lbid, const int sbid,
                                 const int entryNo, const int width, void* pStruct)
{
  setSubBlockEntry(block->data, sbid, entryNo, width, pStruct);
  block->dirty = false;

  return writeDBFile(pFile, block->data, lbid);
}

int DbFileOp::writeSubBlockEntry(CommBlock& cb, DataBlock* block, const uint64_t lbid, const int sbid,
                                 const int entryNo, const int width, void* pStruct)
{
  setSubBlockEntry(block->data, sbid, entryNo, width, pStruct);
  block->dirty = false;

  return writeDBFile(cb, block->data, lbid);
}

/***********************************************************
 * DESCRIPTION:
 *    Write to version buffer
 * PARAMETERS:
 *    oid - file oid
 *    lbid - lbid
 * RETURN:
 *    NO_ERROR if success
 *    other number if something wrong
 ***********************************************************/
int DbFileOp::writeVB(IDBDataFile* pFile, const OID oid, const uint64_t lbid)
{
  if (!BRMWrapper::getUseVb())
    return NO_ERROR;

  int rc;
  const TxnID transId = getTransId();

  if (transId != ((TxnID)INVALID_NUM))
  {
    rc = BRMWrapper::getInstance()->writeVB(pFile, (VER_t)transId, oid, lbid, this);
    //@Bug 4671. The error is already logged by worker node.
    /*        if (rc != NO_ERROR)
            {
                char msg[2048];
                snprintf(msg, 2048,
                         "we_dbfileop->BRMWrapper::getInstance()->writeVB "
                         "transId %i oid %i lbid "
    #if __LP64__
                         "%lu"
    #else
                         "%llu"
    #endif
                         " Error Code %i", transId, oid, lbid, rc);
                puts(msg);
                {
                    logging::MessageLog ml(logging::LoggingID(19));
                    logging::Message m;
                    logging::Message::Args args;
                    args.add(msg);
                    m.format(args);
                    ml.logCriticalMessage(m);
                }
                return rc;
            } */
    return rc;
  }

  return NO_ERROR;
}

int DbFileOp::readDbBlocks(IDBDataFile* pFile, unsigned char* readBuf, uint64_t fbo, size_t n)
{
  if (m_chunkManager)
  {
    return m_chunkManager->readBlocks(pFile, readBuf, fbo, n);
  }

  if (setFileOffset(pFile, fbo * BYTE_PER_BLOCK, SEEK_SET) != NO_ERROR)
    return -1;

  return pFile->read(readBuf, BYTE_PER_BLOCK * n) / BYTE_PER_BLOCK;
}

int DbFileOp::restoreBlock(IDBDataFile* pFile, const unsigned char* writeBuf, uint64_t fbo)
{
  if (m_chunkManager)
    return m_chunkManager->restoreBlock(pFile, writeBuf, fbo);

  if (setFileOffset(pFile, fbo * BYTE_PER_BLOCK, SEEK_SET) != NO_ERROR)
    return -1;

  return pFile->write(writeBuf, BYTE_PER_BLOCK);
}

// @bug 5572 - HDFS usage: add *.tmp file backup flag
IDBDataFile* DbFileOp::getFilePtr(const Column& column, bool useTmpSuffix)
{
  string filename;
  return m_chunkManager->getFilePtr(column, column.dataFile.fDbRoot, column.dataFile.fPartition,
                                    column.dataFile.fSegment, filename, "r+b", column.colWidth, useTmpSuffix);
}

}  // namespace WriteEngine
