/*
   Copyright (c) 2019 by The ThreadDB Project
   All Rights Reserved.

   ThreadDB undergoes the BSD License 2.0. You should have received a copy along with this program; if not, write to the ThreadDB Project.
   To obtain a full unlimited version contact thethreaddbproject(at)gmail.com.

   threaddbCpp.h - C++ Interface to the ThreadDB memory container database
*/

#pragma once

/**
   \file
   \brief         Interface C++ wrapper classes.
   \details       Wrapper classes to the threadDB interface functions.
*/

#include "threaddbC.h"
#include <stdexcept>
#include <string>
#include <limits>

#define threadDB_throw(a)\
        if((a)) \
            throw std::runtime_error(ThreadDB_GetErrorMessage(m_pThreadDB));

namespace tdb
{

    /**
    *  @brief C++ wrapper class to the @ref threadDB_LinkInfo control structure.
    */

    class LinkInfo : public threadDB_LinkInfo
    {
    public:
        LinkInfo() : threadDB_LinkInfo({ 0, std::numeric_limits<uint32_t>::max(), 0 })
        {
        }

        ~LinkInfo()
        {
        }
    };


    /**
    *  @brief C++ wrapper class to the @ref threadDB_ItemInfo control structure.
    */

    class ItemInfo : public threadDB_ItemInfo
    {
    public:
        ItemInfo() : threadDB_ItemInfo({ 0, std::numeric_limits<uint32_t>::max(), 0, std::numeric_limits<uint32_t>::max() })
        {
        }

        ~ItemInfo()
        {
        }

        bool Unregistered() const { return m_PackageSize != std::numeric_limits<uint32_t>::max(); }
    };

    /**
    *  @brief C++ wrapper class to the @ref threadDB_ReadInfo control structure.
    */

    class ReadInfo
    {
    public:
        ReadInfo(threadDB_ReadInfo* pReadInfo_p, void* pThreadDB_p) :
            m_pReadInfo(pReadInfo_p),
            m_pThreadDB(pThreadDB_p)
        {
        }
        ~ReadInfo()
        {
            // Note: the return code is ignored to prevent that an exception is generated within the destructor
            ThreadDB_Close(&m_pReadInfo, m_pThreadDB);
        }
        threadDB_ReadInfo* Get() const
        {
            return m_pReadInfo;
        }

    private:
        threadDB_ReadInfo* m_pReadInfo;
        void* m_pThreadDB;
    };

    /**
    *  @brief C++ wrapper class of the threadDB file mapped memory container extension.
    */

    class database
    {
    public:

        /**
        *  @brief C++ wrapper to construct a threadDB database object see @ref ThreadDB_Create.
        */
        
        database(size_t PackageSize_p = 4096, size_t PackageCacheLimit_p = std::numeric_limits<size_t>::max(), const std::string& rPackagesPath_p = "") :
            m_pThreadDB(0)
        {
            if (ThreadDB_Create(&m_pThreadDB, PackageSize_p, PackageCacheLimit_p, rPackagesPath_p.empty()?0:rPackagesPath_p.c_str()))
            {
                throw std::runtime_error("Error creating the database.");
            }
        }

        /**
        *  @brief C++ wrapper to construct a threadDB database object from file see @ref ThreadDB_Import.
        */

        database(const std::string& rIndexFileUTF8_p, size_t PackageCacheLimit_p = std::numeric_limits<size_t>::max()) :
            m_pThreadDB(0)
        {
            if (ThreadDB_Import(&m_pThreadDB, rIndexFileUTF8_p.c_str(), PackageCacheLimit_p))
            {
                throw std::runtime_error("Error creating the database.");
            }
        }

        /**
        *  @brief C++ wrapper to destruct a threadDB database object see @ref ThreadDB_Destroy.
        */

        ~database()
        {
            ThreadDB_Destroy(m_pThreadDB);
        }

        /**
        *  @brief C++ wrapper to retrieve the version information of the current installation see @ref ThreadDB_GetVersionInfo.
        */

        const char* GetVersionInfo() const
        {
           return ThreadDB_GetVersionInfo(m_pThreadDB);
        }

        /**
        *  @brief C++ wrapper to save the contents of a threadDB database see @ref ThreadDB_Save.
        */

        void Save(const char* pIndexFileUTF8_p)
        {
            threadDB_throw(ThreadDB_Save(pIndexFileUTF8_p, m_pThreadDB));
        }
        
        /**
        *  @brief C++ wrapper to create and register a threadDB worker thread see @ref ThreadDB_NewThread.
        */

        const char* NewThread(const char* pDataFolder_p, size_t MaxFileSize_p = std::numeric_limits<size_t>::max())
        {
            const char* pFileName_p = 0;
            threadDB_throw(ThreadDB_NewThread(&pFileName_p,pDataFolder_p, MaxFileSize_p, m_pThreadDB));
            return pFileName_p;
        }

        /**
        *  @brief C++ wrapper to create and register a threadDB data package see @ref ThreadDB_NewPackage.
        */

        uint64_t NewPackage()
        {
            uint64_t packageID = 0;
            threadDB_throw(ThreadDB_NewPackage(&packageID,m_pThreadDB));
            return packageID;
        }

        /**
        *  @brief C++ wrapper to retrieve the number of running threads see @ref ThreadDB_GetThreadCount.
        */

        size_t GetThreadCount() const
        {
            return ThreadDB_GetThreadCount(m_pThreadDB);
        }

        /**
        *  @brief C++ wrapper to retrieve the number of registered packages see @ref ThreadDB_GetPackageCount.
        */

        size_t GetPackageCount() const
        {
            return ThreadDB_GetPackageCount(m_pThreadDB);
        }

        /**
        *  @brief C++ wrapper to retrieve the number of registered temporary database files see @ref ThreadDB_GetFileCount.
        */

        size_t GetFileCount() const
        {
            return ThreadDB_GetFileCount(m_pThreadDB);
        }

        /**
        *  @brief C++ wrapper to retrieve the size of the internally allocated package buffers see @ref ThreadDB_GetPackageSize.
        */

        size_t GetPackageSize() const
        {
            return ThreadDB_GetPackageSize(m_pThreadDB);
        }

        /**
        *  @brief C++ wrapper to derive the name of the temporary database file with index @p FileIndex_p @ref ThreadDB_GetDatabaseFileName.
        */

        const char* GetDatabaseFilename(size_t FileIndex_p) const
        {
            return ThreadDB_GetDatabaseFileName(FileIndex_p, m_pThreadDB);
        }

        /**
        *  @brief C++ wrapper to move a file to a different location or disk see @ref ThreadDB_RelocateFileTo.
        */

        void RelocateFileTo(size_t FileID_p, const char* pFilePathUTF8_p, threadDB_RelocationType RelocationType_p) const
        {
            threadDB_throw(ThreadDB_RelocateFileTo(FileID_p, pFilePathUTF8_p, RelocationType_p, m_pThreadDB));
        }

        /**
        *  @brief C++ wrapper to store a data item in the selected package see @ref ThreadDB_Store.
        */

        void Store(uint64_t Package_p, size_t Size_p, const char pData_p[], threadDB_ItemInfo* pItemHandle_p = 0)
        {
            threadDB_throw(ThreadDB_Store(Package_p, Size_p, pData_p, pItemHandle_p, m_pThreadDB));
        }

        /**
        *  @brief C++ wrapper to synchronize the package buffers prior reading see @ref ThreadDB_Synchronize.
        */

        void Synchronize(uint64_t Package_p)
        {
            threadDB_throw(ThreadDB_SynchronizePackage(Package_p, m_pThreadDB));
        }

        /**
        *  @brief C++ wrapper to synchronize the package buffers prior reading see @ref ThreadDB_Synchronize.
        */

        void Synchronize()
        {
            threadDB_throw(ThreadDB_Synchronize(m_pThreadDB));
        }

        /**
        *  @brief C++ wrapper to open a package for stream reading see @ref ThreadDB_Open.
        *
        *  return The @ref threadDB_ReadInfo handle.
        */

        tdb::ReadInfo Open(uint64_t Package_p)
        {
            threadDB_ReadInfo* pReadInfo = 0;
            threadDB_throw(ThreadDB_Open(&pReadInfo, Package_p, m_pThreadDB));
            return tdb::ReadInfo(pReadInfo, m_pThreadDB);
        }

        /**
        *  @brief C++ wrapper to open a package for stream reading see @ref ThreadDB_OpenItem.
        *
        *  return The @ref threadDB_ReadInfo handle.
        */

        tdb::ReadInfo Open(const tdb::ItemInfo& rItemHandle_p)
        {
            threadDB_ReadInfo* pReadInfo = 0;
            threadDB_throw(ThreadDB_OpenItem(&pReadInfo, &rItemHandle_p, m_pThreadDB));
            return tdb::ReadInfo(pReadInfo, m_pThreadDB);
        }

        /**
        *  @brief C++ wrapper for stream reading of data items see @ref ThreadDB_RecoverContinous.
        *
        *  returns The number of read bytes.
        */

        size_t Recover(size_t Size_p, char pData_p[], tdb::ReadInfo& rReadInfo_p)
        {
            size_t readBytes = 0;
            threadDB_throw(ThreadDB_RecoverContinous(&readBytes,Size_p, pData_p, rReadInfo_p.Get(), m_pThreadDB));
            return readBytes;
        }

        /**
        *  @brief C++ wrapper for stream end indicator see @ref ThreadDB_PackageEnd.
        *
        *  returns flag if package end reached.
        */

        bool End(const tdb::ReadInfo& rReadInfo_p) const
        {
            return ThreadDB_PackageEnd(rReadInfo_p.Get(), m_pThreadDB);
        }

        /**
        *  @brief C++ wrapper for random reading of data items see @ref ThreadDB_RecoverRandom.
        *
        *  returns The @ref threadDB_ItemInfo handle.
        */

        void Recover(size_t Size_p, char pData_p[], const tdb::ItemInfo& rItemHandle_p, uint64_t Package_p)
        {
            threadDB_throw(ThreadDB_RecoverRandom(Size_p, pData_p, &rItemHandle_p, Package_p, m_pThreadDB));
        }

        /**
        *  @brief C++ wrapper for replacing the contents of a data item see @ref ThreadDB_Replace.
        */

        void Replace(size_t Size_p, const char pData_p[], tdb::ItemInfo& rItemHandle_p)
        {
            threadDB_throw(ThreadDB_Replace(Size_p, pData_p, &rItemHandle_p, m_pThreadDB));
        }

    private:
        database(const database&) = delete;
        void operator=(const database&) = delete;

        void* m_pThreadDB;
    };
}

