/*-
 * Copyright (c) 2009, 2020 Oracle and/or its affiliates.  All rights reserved.
 *
 * See the file LICENSE for license information.
 *
 * $Id$
 */

#ifndef _DB_STL_DBT_H
#define _DB_STL_DBT_H

#include <assert.h>
#include <string>

#include "dbstl_common.h"
#include "dbstl_exception.h"
#include "dbstl_utility.h"

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
//
// DataItem class template definition
//
// 1. DataItem is a Dbt wrapper, it provides both typed data to/from memory
// chunk mapping as well as iostream support. Note that iostream functionality
// is not yet implemented.
// 2. DataItem is used inside dbstl to provide consistent Dbt object memory
// management.
// 3. DataItem is not only capable of mapping fixed size objects, but also
// varying length objects and objects not located in a consecutive chunk of
// memory, with the condition that user configures the required methods in
// DbstlElemTraits.
// 4. DataItem can not be a class template because inside it, the "member
// function template override" support is needed.
//
START_NS(dbstl)

using std::string;
#ifdef HAVE_WSTRING
using std::wstring;
#endif

class DataItem
{
private:
	typedef DataItem self;

	////////////////////////////////////////////////////////////////////
	////////////////////////////////////////////////////////////////////
	//
	// DataItem memory management
	//
	// The dbt_ member is the current dbt, data is stored in the dbt's 
	// referenced memory, it may
	// deep copy from constructor and from other Dbt, depending on
	// the constructors "onstack" parameter --- if true, this object
	// is only used as a stack object inside a function,
	// so do shallow copy; otherwise do deep copy.
	// There is always a DB_DBT_USERMEM flag set to the dbt,
	// its ulen data member stores the length of referenced memory,
	// its size data member stores the actual size of data;
	// If onstack is true, its dlen is INVALID_DLEN, and freemem()
	// will not free such memory because this object only reference
	// other object's memory, its the referenced object's responsibility
	// to free their memory.
	//
	// A DataItem object is not used everywhere, so it is impossible for
	// such an object to have two kinds of usages as above at the same
	// time, so we are safe doing so.
	Dbt dbt_;

	// Free dbt_'s referenced memory if that memory is allocated in heap
	// and owned by dbt_.
	inline void freemem()
	{
		void *buf = dbt_.get_data();

		if (buf != NULL && (dbt_.get_flags() & DB_DBT_USERMEM) != 0
		    && dbt_.get_dlen() != INVALID_DLEN)
			free(buf);
		memset(&dbt_, 0, sizeof(dbt_));
	}

public:

	// Deep copy, because dbt2.data pointed memory may be short lived.
	inline void set_dbt(const DbstlDbt&dbt2, bool onstack)
	{
		void *buf;
		u_int32_t s1, s2;
		DBT *pdbt2, *pdbt;
	       
		pdbt2 = (DBT *)&dbt2;
		pdbt = (DBT *)&dbt_;

		if (!onstack) {
			buf = pdbt->data;
			s1 = pdbt->ulen;
			s2 = pdbt2->size;
			if(s2 > s1) {
				buf = DbstlReAlloc(buf, s2);
				pdbt->size = s2;
				pdbt->data = buf;
				pdbt->ulen = s2;
				pdbt->flags |= DB_DBT_USERMEM;
			} else
				pdbt->size = s2;
			memcpy(buf, pdbt2->data, s2);
		} else {
			freemem();
			dbt_ = (const Dbt)dbt2;
			pdbt->dlen = (INVALID_DLEN);
		}
	}

	// Deep copy, because dbt2.data pointed memory may be short lived.
	inline void set_dbt(const Dbt&dbt2, bool onstack)
	{
		void *buf;
		u_int32_t s1, s2;
		DBT *pdbt2, *pdbt;
	       
		pdbt2 = (DBT *)&dbt2;
		pdbt = (DBT *)&dbt_;

		if (!onstack) {
			buf = pdbt->data;
			s1 = pdbt->ulen;
			s2 = pdbt2->size;
			if(s2 > s1) {
				buf = DbstlReAlloc(buf, s2);
				pdbt->size = s2;
				pdbt->data = buf;
				pdbt->ulen = s2;
				pdbt->flags |= DB_DBT_USERMEM;
			} else
				pdbt->size = s2;
			memcpy(buf, pdbt2->data, s2);
		} else {
			freemem();
			dbt_ = dbt2;
			pdbt->dlen = (INVALID_DLEN);
		}
	}

	inline void set_dbt(const DBT&dbt2, bool onstack)
	{
		void *buf;
		u_int32_t s1, s2;
		DBT *pdbt = (DBT *)&dbt_;

		if (!onstack) {
			buf = pdbt->data;
			s1 = pdbt->ulen;
			s2 = dbt2.size;
			if(s2 > s1) {
				buf = DbstlReAlloc(buf, s2);
				pdbt->size = s2;
				pdbt->data = buf;
				pdbt->ulen = s2;
				pdbt->flags |= DB_DBT_USERMEM;
			} else
				pdbt->size = s2;
			memcpy(buf, dbt2.data, s2);
		} else {
			freemem();
			// The following is right because Dbt derives from
			// DBT with no extra members or any virtual functions.
			memcpy(&dbt_, &dbt2, sizeof(dbt2));
			pdbt->dlen = INVALID_DLEN;
		}
	}

	// Return to the initial state.
	inline void reset()
	{
		void *buf = dbt_.get_data();
		if (buf) {
			memset(buf, 0, dbt_.get_ulen());
			dbt_.set_size(0);
		}
	}

	inline Dbt& get_dbt()
	{
		return dbt_;
	}

	// Return data of this object. If no data return -1, if it has data
	// return 0.
	//
	// !!!XXX Note that the type parameter T can only be in this function
	// because "template type parameter overload" applies only to a
	// functions template argument list, rather than that of classes.
	// If you put the "template<Typename T>" to this class's declaration,
	// making it a class template, then when T is any of Dbt, DBT, or
	// DataItem<T>, there will be two copies of this function. One will be
	// this function's instantiated version, the other one is one of the
	// three functions defined below.
	//
	template <Typename T>
	inline int get_data(T& data) const
	{
		int ret;
		typedef DbstlElemTraits<T> EM;
		typename EM::ElemRstoreFunct restore;
		void *pdata = NULL;

		if ((pdata = dbt_.get_data()) != NULL) {
			if ((restore = EM::instance()->
			    get_restore_function()) != NULL)
				restore(data, pdata);
			else
				data = *((T*)pdata);
			ret = 0;
		} else
			ret = -1;
		return ret;
	}

	////////////////////////////////////////////////////////////////////
	//
	// Begin functions supporting direct naked string storage.
	//
	// Always store the data, rather than the container object.
	//
	// The returned string lives no longer than the next iterator
	// movement call.
	//
	inline int get_data(char*& data) const
	{
		data = (char*)dbt_.get_data();
		return 0;
	}

	inline int get_data(string &data) const
	{
		data = (string::pointer) dbt_.get_data();
		return 0;
	}

	inline int get_data(wchar_t*& data) const
	{
		data = (wchar_t*)dbt_.get_data();
		return 0;
	}

#ifdef HAVE_WSTRING
	inline int get_data(wstring &data) const
	{
		data = (wstring::pointer) dbt_.get_data();
		return 0;
	}
#endif

	////////////////////////////////////////////////////////////////////

	// Supporting storing arbitrary type of sequence.
	template <Typename T>
	inline int get_data(T*& data) const
	{
		data = (T*)dbt_.get_data();
		return 0;
	}

	inline int get_data(DataItem& data) const
	{
		int ret;

		if (dbt_.get_data()) {
			data.set_dbt(dbt_, false);
			ret = 0;
		} else
			ret = -1;
		return ret;
	}

	////////////////////////////////////////////////////////////////////
	//
	// Begin functions supporting Dbt storage.
	//
	// This member function allows storing a Dbt type, so that user can
	// store the varying length data into Dbt.
	//
	// This method is required to copy a data element's bytes to another 
	// Dbt object, used inside by dbstl.
	// If there is no data return -1, if it has data return 0.
	//
	inline int get_data(Dbt& data) const
	{
		int ret;
		void *addr;
		u_int32_t sz;
		DBT *pdbt = (DBT *)&dbt_, *pdata = (DBT *)&data;

		if (pdbt->data) {
			addr = pdata->data;
			sz = pdbt->size;
			if (pdata->ulen < sz) {
				pdata->data = DbstlReAlloc(addr, sz);
				pdata->size = sz;
				pdata->ulen = sz;
				pdata->flags |= DB_DBT_USERMEM;
			} else
				pdata->size = sz;
			memcpy(pdata->data, pdbt->data, sz);
			ret = 0;
		} else
			ret = -1;
		return ret;
	}

	inline int get_data(DBT& data) const
	{
		int ret;
		void*addr;
		u_int32_t sz;

		if (dbt_.get_data()) {
			addr = data.data;
			if (data.ulen < (sz = dbt_.get_size())) {
				data.data = DbstlReAlloc(addr, sz);
				// User need to free this memory
				data.flags = data.flags | DB_DBT_USERMEM;
				data.size = sz;
				data.ulen = sz;
			} else
				data.size = sz;
			memcpy(data.data, dbt_.get_data(), sz);
			ret = 0;
		} else
			ret = -1;
		return ret;
	}

	inline int get_data(DbstlDbt& data) const
	{
		int ret;
		void *addr;
		u_int32_t sz;
		DBT *pdbt = (DBT *)&dbt_, *pdata = (DBT *)&data;

		if (pdbt->data) {
			addr = pdata->data;
			sz = pdbt->size;
			if (pdata->ulen < sz) {
				pdata->data = DbstlReAlloc(addr, sz);
				pdata->size = sz;
				pdata->ulen = sz;
				pdata->flags |= DB_DBT_USERMEM;
			} else
				pdata->size = sz;
			memcpy(pdata->data, pdbt->data, sz);
			ret = 0;
		} else
			ret = -1;
		return ret;
	}

	////////////////////////////////////////////////////////////////////

	// Deep copy in assignment and copy constructor.
	inline const DbstlDbt& operator=(const DbstlDbt& t2)
	{
		set_dbt(t2, false);
		return t2;
	}

	// Deep copy in assignment and copy constructor.
	inline const Dbt& operator=(const Dbt& t2)
	{
		set_dbt(t2, false);
		return t2;
	}

	// Deep copy in assignment and copy constructor.
	inline const DBT& operator=(const DBT& t2)
	{
		set_dbt(t2, false);
		return t2;
	}

	// Deep copy in assignment and copy constructor.
	template <Typename T>
	inline const T& operator = (const T&dt)
	{

		make_dbt(dt, false);
		return dt;
	}

	// Generic way of storing an object or variable. Note that DataItem
	// is not a class template but a class with function templates.
	// Variable t locates on a consecutive chunk of memory, and objects 
	// of T have the same size. 
	//
	template <Typename T>
	void make_dbt(const T& dt, bool onstack)
	{
		typedef DbstlElemTraits<T> EM;
		u_int32_t sz;
		typename EM::ElemSizeFunct sizef;
		typename EM::ElemCopyFunct copyf;
		DBT *pdbt = (DBT *)&dbt_;

		if ((sizef = EM::instance()->get_size_function()) != NULL)
			sz = sizef(dt);
		else
			sz = sizeof(dt);

		copyf = EM::instance()->get_copy_function();

		if (onstack &&  copyf == NULL) {
			freemem();
			pdbt->data = ((void*)&dt);
			// We have to set DB_DBT_USERMEM for DB_THREAD to work.
			pdbt->flags = (DB_DBT_USERMEM);
			pdbt->size = (sz);
			pdbt->ulen = (sz);
			// This is a flag that this memory can't be freed
			// because it is on stack.
			pdbt->dlen = (INVALID_DLEN);
			return;
		}

		// Not on stack, allocate enough space and "copy" the object
		// using shall copy or customized copy.
		if (pdbt->ulen < sz) {
			pdbt->data = (DbstlReAlloc(pdbt->data, sz));
			assert(pdbt->data != NULL);
			pdbt->size = (sz);
			pdbt->ulen = (sz);
			pdbt->flags = (DB_DBT_USERMEM);
		} else
			pdbt->size = (sz);

		if (copyf != NULL)
			copyf(pdbt->data, dt);
		else
			memcpy(pdbt->data, &dt, sz);
	}

	inline const char*&operator = (const char*&dt)
	{
		make_dbt(dt, false);
		return dt;
	}

	inline const wchar_t*&operator = (const wchar_t*&dt)
	{
		make_dbt(dt, false);
		return dt;
	}

	inline const string &operator=(const string &dt)
	{
		make_dbt(dt, false);
		return dt;
	}

#ifdef HAVE_WSTRING
	inline const wstring &operator=(const wstring &dt)
	{
		make_dbt(dt, false);
		return dt;
	}
#endif

	template <Typename T>
	inline const T*&operator = (const T*&dt)
	{
		make_dbt(dt, false);
		return dt;
	}

	inline const self& operator=(const self&dbt1)
	{
		ASSIGNMENT_PREDCOND(dbt1)
		this->set_dbt(dbt1.dbt_, false);
		return dbt1;
	}

	// Deep copy.
	inline DataItem(const self&dbt1)
	{
		set_dbt(dbt1.dbt_, false);
	}


	inline DataItem(u_int32_t sz)
	{
		void *buf;
		DBT *pdbt = (DBT *)&dbt_;

		buf = NULL;
		buf = DbstlMalloc(sz);
		memset(buf, 0, sz);
		pdbt->size = sz;
		pdbt->ulen = sz;
		pdbt->data = buf;
		pdbt->flags = DB_DBT_USERMEM;
	}

	// Deep copy. The onstack parameter means whether the object referenced
	// by this DataItem is on used with a function call where this DataItem
	// object is used. If so, we don't deep copy the object, simply refer
	// to its memory location. The meaining is the same for this parameter
	// in constructors that follow.
	inline DataItem(const Dbt&dbt2, bool onstack)
	{
		set_dbt(dbt2, onstack);
	}
	
	inline DataItem(const DbstlDbt&dbt2, bool onstack)
	{
		set_dbt(dbt2, onstack);
	}
	
	inline DataItem(const DBT&dbt2, bool onstack)
	{
		set_dbt(dbt2, onstack);
	}

	// Deep copy. There is a partial specialization for char*/wchar_t*/
	// string/wstring.
	template<Typename T>
	inline DataItem(const T& dt, bool onstack)
	{
		make_dbt(dt, onstack);
	}

	inline ~DataItem(void)
	{
		freemem();
	}

protected:

	// Store a char*/wchar_t* string. Need four versions for char*
	// and wchar_t* respectively to catch all
	// possibilities otherwise the most generic one will be called.
	// Note that the two const decorator matters when doing type
	// matching.
	inline void make_dbt_chars(const char *t, bool onstack)
	{
		DBT *d = (DBT *)&dbt_;
		u_int32_t sz;
		sz = ((t == NULL) ? 
		    sizeof(char) : 
		    (u_int32_t)((strlen(t) + 1) * sizeof(char)));
		if (!onstack) {
			if (d->ulen < sz) {
				d->flags |= DB_DBT_USERMEM;
				d->data = DbstlReAlloc(d->data, sz);
				d->ulen = sz;
			}
			d->size = sz;
			if (t != NULL)
				strcpy((char*)d->data, t);
			else
				memset(d->data, '\0', sizeof(char));
		} else {
			freemem();
			d->data = ((t == NULL) ? (void *)"" : (void *)t);
			d->size = sz;
			d->ulen = sz;
			d->flags = (DB_DBT_USERMEM);
			d->dlen = (INVALID_DLEN);
		}
	}

	inline void make_dbt_wchars(const wchar_t *t, bool onstack)
	{
		DBT *d = (DBT *)&dbt_;
		u_int32_t sz;
		sz = ((t == NULL) ? 
		    sizeof(wchar_t) : 
		    (u_int32_t)((wcslen(t) + 1) * sizeof(wchar_t)));
		if (!onstack) {					
			if (d->ulen < sz) {
				d->flags |= DB_DBT_USERMEM;
				d->data = DbstlReAlloc(d->data, sz);
				d->ulen = sz;
			}
			d->size = sz;
			if (t != NULL)
				wcscpy((wchar_t*)d->data, t);
			else
				memset(d->data, L'\0', sizeof(wchar_t));
		} else {
			freemem();
			d->data = ((t == NULL) ? (void *)L"" : (void *)t);
			d->size = sz;
			d->ulen = sz;
			d->flags = (DB_DBT_USERMEM);
			d->dlen = (INVALID_DLEN);
		}
	}

	inline void make_dbt(const char*& t, bool onstack)
	{
		make_dbt_chars(t, onstack);
	}

	inline void make_dbt(const char* const& t, bool onstack)
	{
		make_dbt_chars(t, onstack);
	}

	inline void make_dbt(char*& t, bool onstack)
	{
		make_dbt_chars(t, onstack);
	}

	inline void make_dbt(char* const& t, bool onstack)
	{
		make_dbt_chars(t, onstack);
	}

	inline void make_dbt(const string& t, bool onstack)
	{
		make_dbt_chars(t.c_str(), onstack);
	}

	inline void make_dbt(const wchar_t*& t, bool onstack)
	{
		make_dbt_wchars(t, onstack);
	}

	inline void make_dbt(const wchar_t* const& t, bool onstack)
	{
		make_dbt_wchars(t, onstack);
	}

	inline void make_dbt(wchar_t*& t, bool onstack)
	{
		make_dbt_wchars(t, onstack);
	}

	inline void make_dbt(wchar_t* const& t, bool onstack)
	{
		make_dbt_wchars(t, onstack);
	}

#ifdef HAVE_WSTRING
	inline void make_dbt(const wstring& t, bool onstack)
	{
		make_dbt_wchars(t.c_str(), onstack);
	}
#endif

	template <Typename T>
	void make_dbt_internal(const T*t, bool onstack)
	{
		typedef DbstlElemTraits<T> EM;
		u_int32_t i, sz, totalsz, sql;
		DBT *pdbt = (DBT *)&dbt_;
		typename EM::ElemSizeFunct szf = NULL;
		typename EM::SequenceLenFunct seqlenf = NULL;
		typename EM::SequenceCopyFunct seqcopyf = NULL;

		szf = EM::instance()->get_size_function();
		seqlenf = EM::instance()->get_sequence_len_function();
		seqcopyf = EM::instance()->get_sequence_copy_function();

		assert(seqlenf != NULL);
		sql = sz = (u_int32_t)seqlenf(t);
		if (szf)
			for (i = 0, totalsz = 0; i < sz; i++)
				totalsz += szf(t[i]);
		else
			totalsz = sz * sizeof(T);

		sz = totalsz;

		if (onstack && seqcopyf == NULL) {
			freemem();
			pdbt->data = (void *)t;
			pdbt->size = sz;
			pdbt->ulen = sz;
			pdbt->flags = DB_DBT_USERMEM;
			pdbt->dlen = INVALID_DLEN; // onstack flag;
		} else {
			// ulen stores the real length of the pointed memory.
			if (pdbt->ulen < sz) {
				pdbt->data = DbstlReAlloc(pdbt->data, sz);
				pdbt->ulen = sz;
				pdbt->flags |= DB_DBT_USERMEM;
			}
			pdbt->size = sz;

			EM::instance()->copy((T *)pdbt->data, t, sql);
		}
	}

	// Store a sequence of base type T. Need four versions to catch all
	// possibilities otherwise the most generic one will be called.
	template <Typename T>
	inline void make_dbt(const T*const&tt, bool onstack)
	{
		make_dbt_internal((const T*)tt, onstack);
	}
	template <Typename T>
	inline void make_dbt(T*const&tt, bool onstack)
	{
		make_dbt_internal((const T*)tt, onstack);
	}
	template <Typename T>
	inline void make_dbt(T*&tt, bool onstack)
	{
		make_dbt_internal((const T*)tt, onstack);
	}
	template <Typename T>
	inline void make_dbt(const T*&tt, bool onstack)
	{
		make_dbt_internal((const T*)tt, onstack);
	}


public:
	inline DataItem(const char*& t, bool onstack)
	{
		make_dbt_chars(t, onstack);
	}

	inline DataItem(const char* const& t, bool onstack)
	{
		make_dbt_chars(t, onstack);
	}

	inline DataItem(char*& t, bool onstack)
	{
		make_dbt_chars(t, onstack);
	}

	inline DataItem(char* const& t, bool onstack)
	{
		make_dbt_chars(t, onstack);
	}

	inline DataItem(const string& t, bool onstack)
	{
		make_dbt_chars(t.c_str(), onstack);
	}

	inline DataItem(const wchar_t*& t, bool onstack)
	{
		make_dbt_wchars(t, onstack);
	}

	inline DataItem(const wchar_t* const& t, bool onstack)
	{
		make_dbt_wchars(t, onstack);
	}

	inline DataItem(wchar_t*& t, bool onstack)
	{
		make_dbt_wchars(t, onstack);
	}

	inline DataItem(wchar_t* const& t, bool onstack)
	{
		make_dbt_wchars(t, onstack);
	}

#ifdef HAVE_WSTRING
	inline DataItem(const wstring& t, bool onstack)
	{
		make_dbt_wchars(t.c_str(), onstack);
	}
#endif
	template<Typename T>
	inline DataItem(T*&tt, bool onstack)
	{
		make_dbt_internal((const T*)tt, onstack);
	}

	template<Typename T>
	inline DataItem(const T*&tt, bool onstack)
	{
		make_dbt_internal((const T*)tt, onstack);
	}

	template<Typename T>
	inline DataItem(T*const&tt, bool onstack)
	{
		make_dbt_internal((const T*)tt, onstack);
	}

	template<Typename T>
	inline DataItem(const T*const&tt, bool onstack)
	{
		make_dbt_internal((const T*)tt, onstack);
	}


}; // DataItem<>

bool operator==(const Dbt&d1, const Dbt&d2);
bool operator==(const DBT&d1, const DBT&d2);
END_NS

#endif // !_DB_STL_DBT_H
