// -*- C++ -*-
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef _LIBCPP_SYNCSTREAM
#define _LIBCPP_SYNCSTREAM

/*
    syncstream synopsis

#include <ostream>  // see [ostream.syn]

namespace std {
    template<class charT, class traits, class Allocator>
    class basic_syncbuf;

    // [syncstream.syncbuf.special], specialized algorithms
    template<class charT, class traits, class Allocator>
      void swap(basic_syncbuf<charT, traits, Allocator>&,
                basic_syncbuf<charT, traits, Allocator>&);

    using syncbuf = basic_syncbuf<char>;
    using wsyncbuf = basic_syncbuf<wchar_t>;

    template<class charT, class traits, class Allocator>
    class basic_osyncstream;

    using osyncstream = basic_osyncstream<char>;
    using wosyncstream = basic_osyncstream<wchar_t>;

    template<class charT, class traits, class Allocator>
    class basic_syncbuf : public basic_streambuf<charT, traits> {
    public:
        using char_type      = charT;
        using int_type       = typename traits::int_type;
        using pos_type       = typename traits::pos_type;
        using off_type       = typename traits::off_type;
        using traits_type    = traits;
        using allocator_type = Allocator;

        using streambuf_type = basic_streambuf<charT, traits>;

        // [syncstream.syncbuf.cons], construction and destruction
        basic_syncbuf()
          : basic_syncbuf(nullptr) {}
        explicit basic_syncbuf(streambuf_type* obuf)
          : basic_syncbuf(obuf, Allocator()) {}
        basic_syncbuf(streambuf_type*, const Allocator&);
        basic_syncbuf(basic_syncbuf&&);
        ~basic_syncbuf();

        // [syncstream.syncbuf.assign], assignment and swap
        basic_syncbuf& operator=(basic_syncbuf&&);
        void swap(basic_syncbuf&);

        // [syncstream.syncbuf.members], member functions
        bool emit();
        streambuf_type* get_wrapped() const noexcept;
        allocator_type get_allocator() const noexcept;
        void set_emit_on_sync(bool) noexcept;

    protected:
        // [syncstream.syncbuf.virtuals], overridden virtual functions
        int sync() override;

    private:
        streambuf_type* wrapped;    // exposition only
        bool emit_on_sync{};        // exposition only
    };

    // [syncstream.syncbuf.special], specialized algorithms
    template<class charT, class traits, class Allocator>
    void swap(basic_syncbuf<charT, traits, Allocator>&,
              basic_syncbuf<charT, traits, Allocator>&);

    template<class charT, class traits, class Allocator>
    class basic_osyncstream : public basic_ostream<charT, traits> {
    public:
        using char_type   = charT;
        using int_type    = typename traits::int_type;
        using pos_type    = typename traits::pos_type;
        using off_type    = typename traits::off_type;
        using traits_type = traits;

        using allocator_type = Allocator;
        using streambuf_type = basic_streambuf<charT, traits>;
        using syncbuf_type   = basic_syncbuf<charT, traits, Allocator>;

        // [syncstream.osyncstream.cons], construction and destruction
        basic_osyncstream(streambuf_type*, const Allocator&);
        explicit basic_osyncstream(streambuf_type* obuf)
          : basic_osyncstream(obuf, Allocator()) {}
        basic_osyncstream(basic_ostream<charT, traits>& os, const Allocator& allocator)
          : basic_osyncstream(os.rdbuf(), allocator) {}
        explicit basic_osyncstream(basic_ostream<charT, traits>& os)
          : basic_osyncstream(os, Allocator()) {}
        basic_osyncstream(basic_osyncstream&&) noexcept;
        ~basic_osyncstream();

        // [syncstream.osyncstream.assign], assignment
        basic_osyncstream& operator=(basic_osyncstream&&);

        // [syncstream.osyncstream.members], member functions
        void emit();
        streambuf_type* get_wrapped() const noexcept;
        syncbuf_type* rdbuf() const noexcept { return const_cast<syncbuf_type*>(addressof(sb)); }

    private:
        syncbuf_type sb;    // exposition only
    };
}

*/

#if __cplusplus < 201103L && defined(_LIBCPP_USE_FROZEN_CXX03_HEADERS)
#  include <__cxx03/__config>
#else
#  include <__config>

// standard-mandated includes

// [syncstream.syn]
#  include <ostream>

#  if _LIBCPP_HAS_LOCALIZATION

#    include <__mutex/lock_guard.h>
#    include <__utility/move.h>
#    include <ios>
#    include <iosfwd> // required for declaration of default arguments
#    include <streambuf>
#    include <string>
#    if _LIBCPP_HAS_THREADS
#      include <map>
#      include <shared_mutex>
#    endif

#    if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
#      pragma GCC system_header
#    endif

_LIBCPP_PUSH_MACROS
#    include <__undef_macros>

_LIBCPP_BEGIN_NAMESPACE_STD

#    if _LIBCPP_STD_VER >= 20 && _LIBCPP_HAS_EXPERIMENTAL_SYNCSTREAM

// [syncstream.syncbuf.overview]/1
//   Class template basic_syncbuf stores character data written to it,
//   known as the associated output, into internal buffers allocated
//   using the object's allocator. The associated output is transferred
//   to the wrapped stream buffer object *wrapped when emit() is called
//   or when the basic_syncbuf object is destroyed. Such transfers are
//   atomic with respect to transfers by other basic_syncbuf objects
//   with the same wrapped stream buffer object.
//
// This helper singleton is used to implement the required
// synchronisation guarantees.
#      if _LIBCPP_HAS_THREADS
class __wrapped_streambuf_mutex {
  _LIBCPP_HIDE_FROM_ABI __wrapped_streambuf_mutex() = default;

public:
  __wrapped_streambuf_mutex(const __wrapped_streambuf_mutex&)            = delete;
  __wrapped_streambuf_mutex& operator=(const __wrapped_streambuf_mutex&) = delete;

  _LIBCPP_HIDE_FROM_ABI void __inc_reference([[maybe_unused]] void* __ptr) {
    _LIBCPP_ASSERT_INTERNAL(__ptr != nullptr, "non-wrapped streambufs are never written to");
    unique_lock __lock{__mutex_};
    ++__lut_[reinterpret_cast<uintptr_t>(__ptr)].__count;
  }

  // pre: __ptr is in __lut_
  _LIBCPP_HIDE_FROM_ABI void __dec_reference([[maybe_unused]] void* __ptr) noexcept {
    unique_lock __lock{__mutex_};

    auto __it = __get_it(__ptr);
    if (__it->second.__count == 1)
      __lut_.erase(__it);
    else
      --__it->second.__count;
  }

  // TODO
  // This function causes emit() aquire two mutexes:
  // - __mutex_ shared
  // _ __get_it(__ptr)->second.__mutex exclusive
  //
  // Instead store a pointer to __get_it(__ptr)->second.__mutex when
  // calling __inc_reference.
  //
  // pre: __ptr is in __lut_
  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI lock_guard<mutex> __get_lock([[maybe_unused]] void* __ptr) noexcept {
    shared_lock __lock{__mutex_};
    return lock_guard{__get_it(__ptr)->second.__mutex};
  }

  // This function is used for testing.
  //
  // It is allowed to call this function with a non-registered pointer.
  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI size_t __get_count([[maybe_unused]] void* __ptr) noexcept {
    _LIBCPP_ASSERT_INTERNAL(__ptr != nullptr, "non-wrapped streambufs are never written to");
    shared_lock __lock{__mutex_};

    auto __it = __lut_.find(reinterpret_cast<uintptr_t>(__ptr));
    return __it != __lut_.end() ? __it->second.__count : 0;
  }

  [[nodiscard]] static _LIBCPP_HIDE_FROM_ABI __wrapped_streambuf_mutex& __instance() noexcept {
    static __wrapped_streambuf_mutex __result;
    return __result;
  }

private:
  struct __value {
    mutex __mutex;
    size_t __count{0};
  };

  shared_mutex __mutex_;
  map<uintptr_t, __value> __lut_;

  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI map<uintptr_t, __value>::iterator __get_it(void* __ptr) noexcept {
    _LIBCPP_ASSERT_INTERNAL(__ptr != nullptr, "non-wrapped streambufs are never written to");

    auto __it = __lut_.find(reinterpret_cast<uintptr_t>(__ptr));
    _LIBCPP_ASSERT_INTERNAL(__it != __lut_.end(), "using a wrapped streambuf that has not been registered");
    _LIBCPP_ASSERT_INTERNAL(__it->second.__count >= 1, "found an inactive streambuf wrapper");
    return __it;
  }
};
#      endif // _LIBCPP_HAS_THREADS

// basic_syncbuf

// The class uses a basic_string<_CharT, _Traits, _Allocator> as
// internal buffer. Per [syncstream.syncbuf.cons]/4
//   Remarks: A copy of allocator is used to allocate memory for
//   internal buffers holding the associated output.
//
// Therefore the allocator used in the constructor is passed to the
// basic_string. The class does not keep a copy of this allocator.
template <class _CharT, class _Traits, class _Allocator>
class basic_syncbuf : public basic_streambuf<_CharT, _Traits> {
public:
  using char_type      = _CharT;
  using traits_type    = _Traits;
  using int_type       = typename traits_type::int_type;
  using pos_type       = typename traits_type::pos_type;
  using off_type       = typename traits_type::off_type;
  using allocator_type = _Allocator;

  using streambuf_type = basic_streambuf<_CharT, _Traits>;

  // [syncstream.syncbuf.cons], construction and destruction

  _LIBCPP_HIDE_FROM_ABI basic_syncbuf() : basic_syncbuf(nullptr) {}

  _LIBCPP_HIDE_FROM_ABI explicit basic_syncbuf(streambuf_type* __obuf) : basic_syncbuf(__obuf, _Allocator()) {}

  _LIBCPP_HIDE_FROM_ABI basic_syncbuf(streambuf_type* __obuf, _Allocator const& __alloc)
      : __wrapped_(__obuf), __str_(__alloc) {
    __inc_reference();
  }

  _LIBCPP_HIDE_FROM_ABI basic_syncbuf(basic_syncbuf&& __other)
      : __wrapped_(__other.get_wrapped()), __str_(std::move(__other.__str_)), __emit_on_sync_(__other.__emit_on_sync_) {
    __move_common(__other);
  }

  _LIBCPP_HIDE_FROM_ABI ~basic_syncbuf() {
#      if _LIBCPP_HAS_EXCEPTIONS
    try {
#      endif // _LIBCPP_HAS_EXCEPTIONS
      emit();
#      if _LIBCPP_HAS_EXCEPTIONS
    } catch (...) {
    }
#      endif // _LIBCPP_HAS_EXCEPTIONS
    __dec_reference();
  }

  // [syncstream.syncbuf.assign], assignment and swap

  _LIBCPP_HIDE_FROM_ABI basic_syncbuf& operator=(basic_syncbuf&& __other) {
    // The function is specified to call emit. This call should
    // propagate the exception thrown.
    emit();
    __dec_reference();

    __wrapped_      = __other.get_wrapped();
    __str_          = std::move(__other.__str_);
    __emit_on_sync_ = __other.__emit_on_sync_;

    __move_common(__other);

    return *this;
  }

  _LIBCPP_HIDE_FROM_ABI void swap(basic_syncbuf& __other) {
    _LIBCPP_ASSERT_COMPATIBLE_ALLOCATOR(
        allocator_traits<_Allocator>::propagate_on_container_swap::value || get_allocator() == __other.get_allocator(),
        "violates the mandated swap precondition");

    basic_syncbuf __tmp(std::move(__other));
    __other = std::move(*this);
    *this   = std::move(__tmp);
  }

  // [syncstream.syncbuf.members], member functions

  _LIBCPP_HIDE_FROM_ABI bool emit() { return emit(false); }

  _LIBCPP_HIDE_FROM_ABI streambuf_type* get_wrapped() const noexcept { return __wrapped_; }

  _LIBCPP_HIDE_FROM_ABI allocator_type get_allocator() const noexcept { return __str_.get_allocator(); }

  _LIBCPP_HIDE_FROM_ABI void set_emit_on_sync(bool __b) noexcept { __emit_on_sync_ = __b; }

protected:
  // [syncstream.syncbuf.virtuals], overridden virtual functions

  _LIBCPP_HIDE_FROM_ABI_VIRTUAL
  int sync() override {
    if (__emit_on_sync_ && !emit(true))
      return -1;
    return 0;
  }

  _LIBCPP_HIDE_FROM_ABI_VIRTUAL
  int_type overflow(int_type __c = traits_type::eof()) override {
    if (traits_type::eq_int_type(__c, traits_type::eof()))
      return traits_type::not_eof(__c);

    if (this->pptr() == this->epptr()) {
#      if _LIBCPP_HAS_EXCEPTIONS
      try {
#      endif
        size_t __size = __str_.size();
        __str_.resize(__str_.capacity() + 1);
        _LIBCPP_ASSERT_INTERNAL(__str_.size() > __size, "the buffer hasn't grown");

        char_type* __p = static_cast<char_type*>(__str_.data());
        this->setp(__p, __p + __str_.size());
        this->pbump(__size);

#      if _LIBCPP_HAS_EXCEPTIONS
      } catch (...) {
        return traits_type::eof();
      }
#      endif
    }

    return this->sputc(traits_type::to_char_type(__c));
  }

private:
  streambuf_type* __wrapped_;

  // TODO Use a more generic buffer.
  // That buffer should be light with almost no additional headers. Then
  // it can be use here, the __retarget_buffer, and place that use
  // the now removed get_temporary_buffer

  basic_string<_CharT, _Traits, _Allocator> __str_;
  bool __emit_on_sync_{false};

  _LIBCPP_HIDE_FROM_ABI bool emit(bool __flush) {
    if (!__wrapped_)
      return false;

#      if _LIBCPP_HAS_THREADS
    lock_guard<mutex> __lock = __wrapped_streambuf_mutex::__instance().__get_lock(__wrapped_);
#      endif

    bool __result = true;
    if (this->pptr() != this->pbase()) {
      _LIBCPP_ASSERT_INTERNAL(this->pbase() && this->pptr() && this->epptr(), "all put area pointers shold be valid");

      // The __str_ does not know how much of its buffer is used. This
      // information is extracted from the information of the base class.
      __result &= (__wrapped_->sputn(this->pbase(), this->pptr() - this->pbase()) != -1);
      // Clears the buffer, but keeps the contents (and) size of the
      // internal buffer.
      this->setp(this->pbase(), this->epptr());
    }

    if (__flush)
      __result &= (__wrapped_->pubsync() != -1);

    return __result;
  }

  _LIBCPP_HIDE_FROM_ABI void __move_common(basic_syncbuf& __other) {
    // Adjust the put area pointers to our buffer.
    char_type* __p = static_cast<char_type*>(__str_.data());
    this->setp(__p, __p + __str_.size());
    this->pbump(__other.pptr() - __other.pbase());

    // Clear __other_ so the destructor will act as a NOP.
    __other.setp(nullptr, nullptr);
    __other.__wrapped_ = nullptr;
  }

  _LIBCPP_HIDE_FROM_ABI void __inc_reference() {
#      if _LIBCPP_HAS_THREADS
    if (__wrapped_)
      __wrapped_streambuf_mutex::__instance().__inc_reference(__wrapped_);
#      endif
  }

  _LIBCPP_HIDE_FROM_ABI void __dec_reference() noexcept {
#      if _LIBCPP_HAS_THREADS
    if (__wrapped_)
      __wrapped_streambuf_mutex::__instance().__dec_reference(__wrapped_);
#      endif
  }
};

using std::syncbuf;
#      if _LIBCPP_HAS_WIDE_CHARACTERS
using std::wsyncbuf;
#      endif

// [syncstream.syncbuf.special], specialized algorithms
template <class _CharT, class _Traits, class _Allocator>
_LIBCPP_HIDE_FROM_ABI void
swap(basic_syncbuf<_CharT, _Traits, _Allocator>& __lhs, basic_syncbuf<_CharT, _Traits, _Allocator>& __rhs) {
  __lhs.swap(__rhs);
}

// basic_osyncstream

template <class _CharT, class _Traits, class _Allocator>
class basic_osyncstream : public basic_ostream<_CharT, _Traits> {
public:
  using char_type   = _CharT;
  using traits_type = _Traits;
  using int_type    = typename traits_type::int_type;
  using pos_type    = typename traits_type::pos_type;
  using off_type    = typename traits_type::off_type;

  using allocator_type = _Allocator;
  using streambuf_type = basic_streambuf<char_type, traits_type>;
  using syncbuf_type   = basic_syncbuf<char_type, traits_type, allocator_type>;

  // [syncstream.osyncstream.cons], construction and destruction

  _LIBCPP_HIDE_FROM_ABI basic_osyncstream(streambuf_type* __obuf, allocator_type const& __alloc)
      : basic_ostream<_CharT, _Traits>(std::addressof(__sb_)), __sb_(__obuf, __alloc) {}

  _LIBCPP_HIDE_FROM_ABI explicit basic_osyncstream(streambuf_type* __obuf)
      : basic_osyncstream(__obuf, allocator_type()) {}

  _LIBCPP_HIDE_FROM_ABI basic_osyncstream(basic_ostream<char_type, traits_type>& __os, allocator_type const& __alloc)
      : basic_osyncstream(__os.rdbuf(), __alloc) {}

  _LIBCPP_HIDE_FROM_ABI explicit basic_osyncstream(basic_ostream<char_type, traits_type>& __os)
      : basic_osyncstream(__os, allocator_type()) {}

  _LIBCPP_HIDE_FROM_ABI basic_osyncstream(basic_osyncstream&& __other) noexcept
      : basic_ostream<_CharT, _Traits>(std::addressof(__sb_)), __sb_(std::move(__other.__sb_)) {
    this->set_rdbuf(std::addressof(__sb_));
  }

  // [syncstream.osyncstream.assign], assignment

  _LIBCPP_HIDE_FROM_ABI basic_osyncstream& operator=(basic_osyncstream&& __other) = default;

  // [syncstream.osyncstream.members], member functions

  _LIBCPP_HIDE_FROM_ABI void emit() {
    // The basic_ostream::put places the sentry in a try
    // catch, this does not match the wording of the standard
    // [ostream.unformatted]
    // TODO validate other unformatted output functions.
    typename basic_ostream<char_type, traits_type>::sentry __s(*this);
    if (__s) {
#      if _LIBCPP_HAS_EXCEPTIONS
      try {
#      endif

        if (__sb_.emit() == false)
          this->setstate(ios::badbit);
#      if _LIBCPP_HAS_EXCEPTIONS
      } catch (...) {
        this->__set_badbit_and_consider_rethrow();
      }
#      endif
    }
  }

  _LIBCPP_HIDE_FROM_ABI streambuf_type* get_wrapped() const noexcept { return __sb_.get_wrapped(); }

  _LIBCPP_HIDE_FROM_ABI syncbuf_type* rdbuf() const noexcept {
    return const_cast<syncbuf_type*>(std::addressof(__sb_));
  }

private:
  syncbuf_type __sb_;
};

using std::osyncstream;
#      if _LIBCPP_HAS_WIDE_CHARACTERS
using std::wosyncstream;
#      endif

#    endif // _LIBCPP_STD_VER >= 20 && _LIBCPP_HAS_EXPERIMENTAL_SYNCSTREAM

_LIBCPP_END_NAMESPACE_STD

_LIBCPP_POP_MACROS

#  endif // _LIBCPP_HAS_LOCALIZATION
#endif   // __cplusplus < 201103L && defined(_LIBCPP_USE_FROZEN_CXX03_HEADERS)

#endif // _LIBCPP_SYNCSTREAM
