// Copyright 2009-present MongoDB, 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.

#pragma once

#include <mongocxx/change_stream-fwd.hpp> // IWYU pragma: export

//

#include <mongocxx/v1/change_stream.hpp> // IWYU pragma: export

#include <cstdint>
#include <iterator>
#include <memory> // IWYU pragma: keep: backward compatibility, to be removed.
#include <type_traits>
#include <utility>

#include <mongocxx/client-fwd.hpp>     // IWYU pragma: keep: backward compatibility, to be removed.
#include <mongocxx/collection-fwd.hpp> // IWYU pragma: keep: backward compatibility, to be removed.
#include <mongocxx/database-fwd.hpp>   // IWYU pragma: keep: backward compatibility, to be removed.

#include <bsoncxx/document/view.hpp>
#include <bsoncxx/stdx/optional.hpp>

#include <mongocxx/config/prelude.hpp>

namespace mongocxx {
namespace v_noabi {

///
/// A MongoDB change stream.
///
class change_stream {
   private:
    v1::change_stream _stream;
    bsoncxx::v_noabi::document::view _doc;

   public:
    /// A change stream iterator.
    class iterator;

    ///
    /// Move constructs a change_stream.
    ///
    MONGOCXX_ABI_EXPORT_CDECL_UNSTABLE() change_stream(change_stream&& other) noexcept;

    ///
    /// Move assigns a change_stream.
    ///
    MONGOCXX_ABI_EXPORT_CDECL_UNSTABLE(change_stream&) operator=(change_stream&& other) noexcept;

    ///
    /// This class is not copyable.
    ///
    change_stream(change_stream const& other) = delete;

    ///
    /// This class is not copyable.
    ///
    change_stream& operator=(change_stream const& other) = delete;

    ///
    /// Destroys a change_stream.
    ///
    MONGOCXX_ABI_EXPORT_CDECL_UNSTABLE() ~change_stream();

    ///
    /// Construct with the @ref mongocxx::v1 equivalent.
    ///
    /* explicit(false) */ change_stream(v1::change_stream stream) : _stream{std::move(stream)} {}

    ///
    /// Convert to the @ref bsoncxx::v1 equivalent.
    ///
    /// @par Postconditions:
    /// - `*this` is in an assign-or-destroy-only state.
    ///
    /// @warning Invalidates all associated iterators and views.
    ///
    explicit operator v1::change_stream() && {
        return std::move(_stream);
    }

    ///
    /// This class is not copyable.
    ///
    explicit operator v1::change_stream() const& = delete;

    ///
    /// A change_stream::iterator points to the beginning of any available notifications.
    ///
    /// The first call to begin() advances to the next available notification. Consecutive calls to begin() only advance
    /// to the next available notification at most once. The state of all iterators is tracked by the change_stream
    /// itself, so advancing one iterator advances all iterators.
    ///
    /// change_stream::begin() and increment operators may block if the current batch of documents is exhausted. They
    /// will not return until a notification is available, the max_await_time (from the options::change_stream)
    /// milliseconds have elapsed, or a server error is encountered.
    ///
    /// When change_stream.begin() == change_stream.end(), no notifications are available. Each call to
    /// change_stream.begin() checks again for newly-available notifications and advances (once) to the next available
    /// notification.
    ///
    /// @return
    ///   The change_stream::iterator
    /// @exception
    ///   Throws mongocxx::v_noabi::query_exception if the query failed.
    ///
    /// @warning This function is NOT logically const despite the const-qualifier.
    ///
    MONGOCXX_ABI_EXPORT_CDECL_UNSTABLE(iterator) begin() const;

    ///
    /// A change_stream::iterator indicating stream exhaustion, meaning that
    /// no notifications are available from the stream.
    ///
    /// @return
    ///   The change_stream::iterator indicating exhaustion
    ///
    MONGOCXX_ABI_EXPORT_CDECL_UNSTABLE(iterator) end() const;

    ///
    /// Returns a resume token for this change stream.
    ///
    /// If the change stream has not been iterated, and either resume_after or
    /// start_after was specified in the options to this change stream, the
    /// specified value will be returned by this method. If neither resume_after or
    /// start_after was set on the options for this change stream, and it has
    /// not been iterated, this method will return no token.
    ///
    /// Once this change stream has been iterated, this method will return the
    /// resume token of the most recently returned document in the stream, or a
    /// postBatchResumeToken if the current batch of documents has been exhausted.
    ///
    /// @see
    /// - https://www.mongodb.com/docs/manual/changeStreams/#resume-tokens
    ///
    /// The returned document::view is valid for the lifetime of the stream and
    /// its data may be updated if the change stream is iterated after this function.
    /// The value may be copied to extend its lifetime or preserve the
    /// current resume token.
    ///
    /// @return
    ///   The token.
    ///
    bsoncxx::v1::stdx::optional<bsoncxx::v_noabi::document::view> get_resume_token() const {
        return _stream.get_resume_token();
    }
};

///
/// A MongoDB change stream iterator.
///
class change_stream::iterator {
   public:
    // Support input-iterator (caveat of post-increment returning void)
    using difference_type = std::int64_t;
    using value_type = bsoncxx::v_noabi::document::view const;
    using pointer = std::add_pointer<value_type>::type;
    using reference = std::add_lvalue_reference<value_type>::type;
    using iterator_category = std::input_iterator_tag;

    ///
    /// Default-construct an iterator.
    /// Default-constucted iterators can be compared (all default-constructed
    /// iterators are ==), assigned, and copied.
    ///
    MONGOCXX_ABI_EXPORT_CDECL_UNSTABLE() iterator();

    ///
    /// Dereferences the view for the document currently being pointed to.
    ///
    /// The returned document::view is valid until the iterator is incremented. The value may be
    /// copied to extend its lifetime.
    ///
    /// @par Preconditions:
    /// - `*this` does not compare equal to the end iterator.
    ///
    MONGOCXX_ABI_EXPORT_CDECL_UNSTABLE(bsoncxx::v_noabi::document::view const&) operator*() const;

    ///
    /// Accesses a member of the dereferenced document currently being pointed to.
    ///
    /// The returned document::view is valid until the iterator is incremented. The value may be
    /// copied to extend its lifetime.
    ///
    /// @par Preconditions:
    /// - `*this` does not compare equal to the end iterator.
    ///
    MONGOCXX_ABI_EXPORT_CDECL_UNSTABLE(bsoncxx::v_noabi::document::view const*) operator->() const;

    ///
    /// Pre-increments the iterator to move to the next document.
    ///
    /// change_stream::begin() and increment operators may block if the current batch of documents
    /// is exhausted. They will not return until a notification is available, the max_await_time
    /// (from the options::change_stream) milliseconds have elapsed, or a server error is
    /// encountered.
    ///
    /// If no notification is available, callers may call change_stream::begin() to check for more
    /// notifications.
    ///
    /// @throws mongocxx::v_noabi::query_exception if the query failed
    ///
    MONGOCXX_ABI_EXPORT_CDECL_UNSTABLE(iterator&) operator++();

    ///
    /// Post-increments the iterator to move to the next document.
    ///
    /// change_stream::begin() and increment operators may block if the current batch of documents
    /// is exhausted. They will not return until a notification is available, the max_await_time
    /// (from the options::change_stream) milliseconds have elapsed, or a server error is
    /// encountered.
    ///
    /// If no notification is available, callers may call change_stream::begin() to check for more
    /// notifications.
    ///
    /// @throws mongocxx::v_noabi::query_exception if the query failed
    ///
    void operator++(int) {
        this->operator++();
    }

    ///
    /// @relates bsoncxx::v_noabi::change_stream::iterator
    ///
    /// Compare two iterators for (in)-equality.
    ///
    /// Iterators compare equal if they point to the same underlying change_stream or if both are
    /// exhausted.
    ///
    /// @{
    friend MONGOCXX_ABI_EXPORT_CDECL_UNSTABLE(bool) operator==(
        change_stream::iterator const& lhs,
        change_stream::iterator const& rhs) noexcept;

    friend bool operator!=(change_stream::iterator const& lhs, change_stream::iterator const& rhs) noexcept {
        return !(lhs == rhs);
    }
    /// @}
    ///

   private:
    friend change_stream;

    explicit iterator(change_stream* change_stream, bool is_end);

    bool is_exhausted() const;

    change_stream* _change_stream;
    bool _is_end;
};

///
/// Convert to the @ref mongocxx::v_noabi equivalent of `v`.
///
inline v_noabi::change_stream from_v1(v1::change_stream v) {
    return {std::move(v)};
}

///
/// Convert to the @ref mongocxx::v1 equivalent of `v`.
///
inline v1::change_stream to_v1(v_noabi::change_stream v) {
    return v1::change_stream{std::move(v)};
}

} // namespace v_noabi
} // namespace mongocxx

#include <mongocxx/config/postlude.hpp>

///
/// @file
/// Provides @ref mongocxx::v_noabi::change_stream.
///
/// @par Includes
/// - @ref mongocxx/v1/change_stream.hpp
///
