/*************************************************************************
 *
 * Copyright 2016 Realm Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 **************************************************************************/

#ifndef REALM_UTIL_MEMORY_STREAM_HPP
#define REALM_UTIL_MEMORY_STREAM_HPP

#include <cstddef>
#include <string>
#include <istream>
#include <ostream>

namespace realm {
namespace util {

class MemoryInputStreambuf : public std::streambuf {
public:
    MemoryInputStreambuf();
    ~MemoryInputStreambuf() noexcept;

    /// Behavior is undefined if the size of the specified buffer exceeds
    /// PTRDIFF_MAX.
    void set_buffer(const char* begin, const char* end) noexcept;

private:
    const char* m_begin;
    const char* m_end;
    const char* m_curr;

    int_type underflow() override;
    int_type uflow() override;
    int_type pbackfail(int_type) override;
    std::streamsize showmanyc() override;
    pos_type seekoff(off_type, std::ios_base::seekdir, std::ios_base::openmode) override;
    pos_type seekpos(pos_type, std::ios_base::openmode) override;

    pos_type do_seekoff(off_type, std::ios_base::seekdir, std::ios_base::openmode);
};


class MemoryOutputStreambuf : public std::streambuf {
public:
    MemoryOutputStreambuf();
    ~MemoryOutputStreambuf() noexcept;

    /// Behavior is undefined if the size of the specified buffer exceeds
    /// PTRDIFF_MAX.
    void set_buffer(char* begin, char* end) noexcept;

    /// Returns the amount of data written to the buffer.
    size_t size() const noexcept;
};


class MemoryInputStream : public std::istream {
public:
    MemoryInputStream();
    ~MemoryInputStream() noexcept;

    /// \{ Behavior is undefined if the size of the specified buffer exceeds
    /// PTRDIFF_MAX.
    void set_buffer(const char* begin, const char* end) noexcept;
    template <size_t N>
    void set_buffer(const char (&buffer)[N]) noexcept;
    void set_string(const std::string&) noexcept;
    void set_c_string(const char* c_str) noexcept;
    /// \}

private:
    MemoryInputStreambuf m_streambuf;
};


class MemoryOutputStream : public std::ostream {
public:
    MemoryOutputStream();
    ~MemoryOutputStream() noexcept;

    /// \{ Behavior is undefined if the size of the specified buffer exceeds
    /// PTRDIFF_MAX.
    void set_buffer(char* begin, char* end) noexcept;
    template <size_t N>
    void set_buffer(char (&buffer)[N]) noexcept;
    /// \}

    /// Returns the amount of data written to the underlying buffer.
    size_t size() const noexcept;

private:
    MemoryOutputStreambuf m_streambuf;
};


// Implementation

inline MemoryInputStreambuf::MemoryInputStreambuf()
    : m_begin(nullptr)
    , m_end(nullptr)
    , m_curr(nullptr)
{
}

inline MemoryInputStreambuf::~MemoryInputStreambuf() noexcept {}

inline void MemoryInputStreambuf::set_buffer(const char* b, const char* e) noexcept
{
    m_begin = b;
    m_end = e;
    m_curr = b;
}


inline MemoryOutputStreambuf::MemoryOutputStreambuf() {}

inline MemoryOutputStreambuf::~MemoryOutputStreambuf() noexcept {}

inline void MemoryOutputStreambuf::set_buffer(char* b, char* e) noexcept
{
    setp(b, e);
}

inline size_t MemoryOutputStreambuf::size() const noexcept
{
    return pptr() - pbase();
}


inline MemoryInputStream::MemoryInputStream()
    : std::istream(&m_streambuf)
{
}

inline MemoryInputStream::~MemoryInputStream() noexcept {}

inline void MemoryInputStream::set_buffer(const char* b, const char* e) noexcept
{
    m_streambuf.set_buffer(b, e);
    clear();
}

template <size_t N>
inline void MemoryInputStream::set_buffer(const char (&buffer)[N]) noexcept
{
    const char* b = buffer;
    const char* e = b + N;
    set_buffer(b, e);
}

inline void MemoryInputStream::set_string(const std::string& str) noexcept
{
    const char* b = str.data();
    const char* e = b + str.size();
    set_buffer(b, e);
}

inline void MemoryInputStream::set_c_string(const char* c_str) noexcept
{
    const char* b = c_str;
    const char* e = b + traits_type::length(c_str);
    set_buffer(b, e);
}


inline MemoryOutputStream::MemoryOutputStream()
    : std::ostream(&m_streambuf)
{
}

inline MemoryOutputStream::~MemoryOutputStream() noexcept {}

inline void MemoryOutputStream::set_buffer(char* b, char* e) noexcept
{
    m_streambuf.set_buffer(b, e);
    clear();
}

template <size_t N>
inline void MemoryOutputStream::set_buffer(char (&buffer)[N]) noexcept
{
    set_buffer(buffer, buffer + N);
}

inline size_t MemoryOutputStream::size() const noexcept
{
    return m_streambuf.size();
}

} // namespace util
} // namespace realm

#endif // REALM_UTIL_MEMORY_STREAM_HPP
