#include <ncbi_pch.hpp>
#include "datacache.hpp"
#if defined(__DB_OFFLINE__)
	#include "basealgo.hpp"
#else
	#include <shlu2/BasicUtils/basealgo.hpp>
#endif
//#include <corelib/ncbitime.hpp>
//#include <objects/general/general__.hpp>
#include <util/compress/zlib.hpp>
#include <corelib/ncbiexpt.hpp>
#include <corelib/ncbistr.hpp>
#include <sstream>
#include <iostream>
#include <zlib.h>

USING_NCBI_SCOPE;


//TDataKey:: operator string (void) const
//{
//	return MakeHandle();
//}
//void TDataKey::MakeHandle(string &handle) const
//{
//    handle = MakeHandle();
//}

//TDataKey::TDataKey(const std::string &handle): signature(k_strEmptyString), service_name(k_strEmptyString), data_name(k_strEmptyString), usr_ip4(0)
//{
//	ParseHandle(handle);
//}
//
//int TDataKey::ParseHandle(const std::string &handle)
//{
//	//vector<string> vecBuf;
//	//SplitString(handle, SIDFIELDDELIMIT, vecBuf);
//	
//	CStringTokenizer tks(handle, SIDFIELDDELIMIT);
//	
//	signature.clear();
//    service_name.clear();
//    data_id = 0;
//    data_name.clear();
//    // -- do not reset userip
//	int idx = 0;
//	try
//	{
//		signature = tks.get();
//		++idx;
//		service_name = tks.get();
//		++idx;
//		data_name = tks.get();
//        ++idx;
//        try //try with qman id
//        {
//            data_id = NStr::StringToNumeric<NUMERIC_ID_TYPE>(data_name, 0, 16);
//            data_name = tks.get();
//            ++idx;
//        }
//        catch (...){;}
//		
//	}
//	catch(...){;}
//	
//	return idx;
//}
//
//void TDataKey::MakeMstHandle(string &handle) const
//{
//	handle = MakeMstHandle();
//}
//
//string TDataKey::MakeMstHandle(void) const
//{
//	return signature + SIDFIELDDELIMIT + service_name + (0 == data_id ? k_strEmptyString : SIDFIELDDELIMIT + NStr::NumericToString<NUMERIC_ID_TYPE>(data_id, 0, 16));
//}
//
//string TDataKey::MakeHandle(void) const
//{
//	return signature + SIDFIELDDELIMIT + service_name + SIDFIELDDELIMIT + (0 == data_id ? k_strEmptyString : NStr::NumericToString<NUMERIC_ID_TYPE>(data_id, 0, 16) + SIDFIELDDELIMIT) + data_name;
//}
//
//void TDataKey::MakeHandle(string &handle) const
//{
//	handle = MakeHandle();
//}
//
//
//TDataKey:: operator string (void) const
//{
//	return MakeHandle();
//}

const char * CDataCache::m_dimStatusLits[] =
{
	"Successful",
	"Invalid key",
	"No such data",	//reqId valid, but no data under this name
    "Data out of boundary",
    "Out of memory",
    "Data Processing",
	"Incorrect cache mode",
	"Data processing error",	//compress/decompress
    "Fail to obtain data storage on cache server",	// -- fail to get store id
	"Fail to store data onto cache server",	// -- fail to transfer blobs to server
    "Too many requests submitted.<br>\nPlease goto <a href=\"//www.ncbi.nlm.nih.gov/Structure/cdd/cdd.shtml\" target=\"_blank\">CDD Homepage</a> to find out<br>\nhow to install and run blast on your local machine",
	"Unknown"
};

const char * CDataCache::MsgString(int errCode)
{
	if (errCode >= 0 && errCode <= CDataCache::eCacheErrorStop) return CDataCache::m_dimStatusLits[errCode];
	else return NULL;
}

CDataCache::CDataCache():
    m_pBuf(nullptr),
	m_ulDataEnd(0),
	m_ulReadPos(0),
    m_TotalCapacity(0)
{}

CDataCache::~CDataCache(void)
{
	delete [] m_pBuf;
}

char * CDataCache::x_Acquire(size_t n_bytes)
{
    size_t ttl_avail = m_TotalCapacity - m_ulDataEnd;
    size_t new_cap = m_TotalCapacity;
    while (ttl_avail < n_bytes)
    {
        ttl_avail += BUFFSIZE;
        new_cap += BUFFSIZE;
    }
    
    if (new_cap > m_TotalCapacity)
    {
        char * newBuf = nullptr;
        
        try
        {
            newBuf = new char[new_cap];
        }
        catch (...){;}
        
        if (nullptr == newBuf)
            throw eMemoryError;
        
        if (m_ulDataEnd > 0)
            memcpy(newBuf, m_pBuf, m_ulDataEnd * sizeof(char));
        
        delete []m_pBuf;
        m_pBuf = newBuf;
        
        m_TotalCapacity = new_cap;
    }
    return m_pBuf + m_ulDataEnd;
}


void CDataCache::ResetData(void)
{
    m_ulDataEnd = 0;
	m_ulReadPos = 0;
}

void CDataCache::ShrinkMem(void)
{
    size_t nblocks = m_ulDataEnd / BUFFSIZE + (m_ulDataEnd % BUFFSIZE ? 1 : 0);
    size_t new_cap = nblocks * BUFFSIZE;
    
    
    if (new_cap < m_TotalCapacity)
    {
        char * newBuf = nullptr;
        
        if (new_cap > 0)
        {
            try
            {
                newBuf = new char[new_cap];
            }
            catch (...){;}
            
            if (nullptr == newBuf)
                throw eMemoryError;
            
            if (m_ulDataEnd > 0)
                memcpy(newBuf, m_pBuf, m_ulDataEnd * sizeof(char));
        }
        delete []m_pBuf;
        m_pBuf = newBuf;
        
        m_TotalCapacity = new_cap;
    }
}

void CDataCache::ShiftData(void)
{
    // -- assert m_ulReadPos <= m_ulDataEnd, as it should always be.
    if (m_ulReadPos > 0)
    {
        size_t avail = m_ulDataEnd - m_ulReadPos;
        
        memmove(m_pBuf, m_pBuf + m_ulReadPos, avail);
        
        m_ulReadPos = 0;
        m_ulDataEnd = avail;
    }
}

int CDataCache::Flush(string &key)
{
    int retval = eDataOk;
    size_t n = 0;
    char * buf = x_CompressData(n, retval);
    
    if (eDataOk == retval)
        retval = x_SaveBlock(buf, n, key);
    
    if (nullptr != buf)
        delete []buf;
    
    return retval;
}

char * CDataCache::x_CompressData(size_t &n, int &status) const
{
    char *buf = nullptr;
    if (m_ulDataEnd > 0)
    {
        CZipCompression z;
        size_t est_bufsz = z.EstimateCompressionBufferSize(m_ulDataEnd);
        if (0 == est_bufsz)
            est_bufsz = m_ulDataEnd + (m_ulDataEnd << 2);
        
        // -- extra size to store the uncompressed size at the beginning
        
        try
        {
            buf = new char[est_bufsz + sizeof(size_t)];
        }
        catch (...){;}
        
        if (nullptr != buf)
        {
            memcpy(buf, &m_ulDataEnd, sizeof(size_t));
            size_t compressed_len = 0;
            
            if(z.CompressBuffer(m_pBuf, m_ulDataEnd, buf + sizeof(size_t), est_bufsz, &compressed_len))
            {
                status = eDataOk;
                n = compressed_len + sizeof(size_t);
            }
            else
            {
                status = eProcessError;
                n = 0;
                delete []buf;
                buf = nullptr;
            }
        }
        else
        {
            status = eMemoryError;
            n = 0;
        }
    }
    else
    {
        n = 0;
        status = eNoData;
    }
        
        
    return buf;
}


void CDataCache::PushData(const void* pData, size_t ulSize)
{
    if (nullptr == pData || 0 == ulSize)
        return;
    
    memcpy(x_Acquire(ulSize), (const char*) pData, ulSize * sizeof(char));
    m_ulDataEnd += ulSize;
}


void CDataCache::PushString(const string &rStr)
{
	size_t ulSize = rStr.size();
	PushData(&ulSize, sizeof(size_t));
    PushData(rStr.data(), ulSize);
}

void CDataCache::PushCString(const char *pStr)
{
	size_t ulSize = strlen(pStr);
	PushData(&ulSize, sizeof(size_t));
    PushData(pStr, ulSize);
}

int CDataCache::Load(const string & key)
{
	string data;
    int retval = x_RetrieveBlock(key, data);
    if (eDataOk == retval)
        retval = x_DeCompressData(data);
	return retval;
}

int CDataCache::x_DeCompressData(const string &data)
{
    int retval = eDataOk;
    const char * src = data.data();
    union
    {
        size_t value;
        char bytes[sizeof(size_t)];
    } decompressed_len;
    
    memcpy(decompressed_len.bytes, src, sizeof(size_t));
    
    char * dst = nullptr;
    try
    {
        dst = x_Acquire(decompressed_len.value);
    }
    catch (int e)
    {
        retval = e;
    }
    if (nullptr != dst)
    {
        CZipCompression z;
        
        if (!z.DecompressBuffer(src + sizeof(size_t), data.size() - sizeof(size_t), dst, m_TotalCapacity - m_ulDataEnd, &m_ulDataEnd))
            retval = eProcessError;
        
    }
    
    return retval;
}


void CDataCache::ReadData(void* pDst, size_t ulSize)
{
	size_t avail = m_ulDataEnd - m_ulReadPos;
    
    if (avail < ulSize)
        throw eOutOfBoundary;
	
    memcpy(pDst, m_pBuf + m_ulReadPos, ulSize * sizeof(char));
    
    m_ulReadPos += ulSize;
    
}

void CDataCache::ReadString(string& rStr)
{
	size_t ulSize = 0;
	ReadData(&ulSize, sizeof(size_t));
    size_t avail = m_ulDataEnd - m_ulReadPos;
    
    if (avail < ulSize)
        throw eOutOfBoundary;
    
	rStr.clear();
    rStr.append(m_pBuf + m_ulReadPos, ulSize);
	m_ulReadPos += ulSize;
	
}


char * CDataCache::ReadCString(void)
{
	size_t ulSize = 0;
	
	ReadData(&ulSize, sizeof(size_t));
	
	size_t avail = m_ulDataEnd - m_ulReadPos;
    
    if (avail < ulSize)
        throw eOutOfBoundary;
    
	char *pData = new char [ulSize + 1];
    memcpy(pData, m_pBuf + m_ulReadPos, ulSize * sizeof(char));
    pData[ulSize] = 0;
    m_ulReadPos += ulSize;
    
	return pData;
}


