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

   threaddbStream.h - Stream implementation for reading/writing package data
*/

#pragma once

/**
   \file
   \brief         Implementation of streaming capability into/from the database.
   \details       This module provides a stream implementation that allows to directly stream data into
                  and from database packages.
*/

#include "threaddbCPP.h"

#include <cstring>
#include <algorithm>
#include <ios>

namespace tdb
{
    /**
    *  @brief Input stream buffer implementation.
    */

    class istreambuf : public std::streambuf
    {
    public:
        istreambuf(uint64_t PackageID_p, tdb::database& rDatabase_p) :
            buff_sz(rDatabase_p.GetPackageSize()),
            put_back_(size_t(1)),
            buffer_(0),
            m_rDatabase(rDatabase_p),
            m_ReadHandle(rDatabase_p.Open(PackageID_p))
        {
            buffer_ = new char[std::max(buff_sz, put_back_) + put_back_];
            char *end = buffer_ + buff_sz;
            setg(end, end, end);
        }

        virtual ~istreambuf()
        {
            sync();
            delete[] buffer_;
        }

        std::streambuf::int_type underflow()
        {
            if (gptr() < egptr()) // buffer not exhausted
                return traits_type::to_int_type(*gptr());

            char *base = buffer_;
            char *start = base;

            if (eback() == base)
            {
                std::memmove(base, egptr() - put_back_, put_back_);
                start += put_back_;
            }

            size_t n = m_rDatabase.Recover(buff_sz, start, m_ReadHandle);
            if (n == 0)
                return traits_type::eof();

            setg(base, start, start + n);

            return traits_type::to_int_type(*gptr());
        }

    private:
        size_t buff_sz;
        const std::size_t put_back_;
        char *buffer_;

        tdb::database& m_rDatabase;
        tdb::ReadInfo m_ReadHandle;
    };

    /**
    *  @brief Output stream buffer implementation.
    */

    class ostreambuf : public std::streambuf
    {
    public:
        ostreambuf(uint64_t PackageID_p, tdb::database& rDatabase_p) :
            buff_sz(rDatabase_p.GetPackageSize()),
            buffer_(0),
            m_rDatabase(rDatabase_p),
            m_PackageID(PackageID_p)
        {
            buffer_ = new char[buff_sz + 1];
            memset(buffer_, 0, buff_sz);

            setp(buffer_, buffer_ + buff_sz);
        }

        virtual ~ostreambuf()
        {
            sync();
            delete[] buffer_;
        }

        virtual std::streambuf::int_type overflow(std::streambuf::int_type value)
        {
            size_t write = pptr() - pbase();
            if (write)
            {
                m_rDatabase.Store(m_PackageID, write, buffer_);

                // Write line to original buffer
                std::streamsize written = write;
                    
                if (written != write) return traits_type::eof();
            }

            setp(buffer_, buffer_ + buff_sz);
            if (!traits_type::eq_int_type(value, traits_type::eof())) sputc(value);

            return traits_type::not_eof(value);
        };

        virtual int sync()
        {
            std::streambuf::int_type result = this->overflow(traits_type::eof());
            m_rDatabase.Synchronize();
            return traits_type::eq_int_type(result, traits_type::eof()) ? -1 : 0;
        }

    private:
        size_t buff_sz;
        char *buffer_;

        uint64_t m_PackageID;
        tdb::database& m_rDatabase;
    };

    /**
    *  @brief Input stream implementation.
    */

    class istream : public std::istream
    {
    public:
        istream(uint64_t PackageID_p, tdb::database& rDatabase_p) :
            std::istream(new istreambuf(PackageID_p, rDatabase_p)) {}

        virtual ~istream()
        {
            delete rdbuf();
        }
    };

    /**
    *  @brief Output stream implementation.
    */

    class ostream : public std::ostream
    {
    public:
        ostream(uint64_t PackageID_p, tdb::database& rDatabase_p) :
            std::ostream(new ostreambuf(PackageID_p, rDatabase_p)) {}

        virtual ~ostream()
        {
            delete rdbuf();
        }
    };

}

