/*
 * Copyright (c) 2023 Philippe Proulx <pproulx@efficios.com>
 *
 * SPDX-License-Identifier: MIT
 */

#ifndef BABELTRACE_CPP_COMMON_BT2_MESSAGE_ITERATOR_HPP
#define BABELTRACE_CPP_COMMON_BT2_MESSAGE_ITERATOR_HPP

#include <babeltrace2/babeltrace.h>

#include "common/common.h"
#include "cpp-common/bt2s/optional.hpp"

#include "component-port.hpp"
#include "exc.hpp"
#include "message-array.hpp"
#include "shared-object.hpp"

namespace bt2 {
namespace internal {

struct MessageIteratorRefFuncs final
{
    static void get(const bt_message_iterator * const libObjPtr) noexcept
    {
        bt_message_iterator_get_ref(libObjPtr);
    }

    static void put(const bt_message_iterator * const libObjPtr) noexcept
    {
        bt_message_iterator_put_ref(libObjPtr);
    }
};

} /* namespace internal */

class MessageIterator final : public BorrowedObject<bt_message_iterator>
{
public:
    using Shared =
        SharedObject<MessageIterator, bt_message_iterator, internal::MessageIteratorRefFuncs>;

    explicit MessageIterator(const LibObjPtr libObjPtr) noexcept : _ThisBorrowedObject {libObjPtr}
    {
    }

    ConstComponent component() const noexcept
    {
        return ConstComponent {bt_message_iterator_borrow_component(this->libObjPtr())};
    }

    bt2s::optional<ConstMessageArray> next() const
    {
        bt_message_array_const libMsgsPtr;
        std::uint64_t count;
        const auto status = bt_message_iterator_next(this->libObjPtr(), &libMsgsPtr, &count);

        switch (status) {
        case BT_MESSAGE_ITERATOR_NEXT_STATUS_OK:
            /* Caller becomes the owner of the contained messages */
            return ConstMessageArray::wrapExisting(libMsgsPtr, count);
        case BT_MESSAGE_ITERATOR_NEXT_STATUS_END:
            return bt2s::nullopt;
        case BT_MESSAGE_ITERATOR_NEXT_STATUS_AGAIN:
            throw TryAgain {};
        case BT_MESSAGE_ITERATOR_NEXT_STATUS_MEMORY_ERROR:
            throw MemoryError {};
        case BT_MESSAGE_ITERATOR_NEXT_STATUS_ERROR:
            throw Error {};
        default:
            bt_common_abort();
        }
    }

    bool canSeekBeginning() const
    {
        bt_bool canSeek;

        const auto status = bt_message_iterator_can_seek_beginning(this->libObjPtr(), &canSeek);

        switch (status) {
        case BT_MESSAGE_ITERATOR_CAN_SEEK_BEGINNING_STATUS_OK:
            return static_cast<bool>(canSeek);
        case BT_MESSAGE_ITERATOR_CAN_SEEK_BEGINNING_STATUS_AGAIN:
            throw TryAgain {};
        case BT_MESSAGE_ITERATOR_CAN_SEEK_BEGINNING_STATUS_MEMORY_ERROR:
            throw MemoryError {};
        case BT_MESSAGE_ITERATOR_CAN_SEEK_BEGINNING_STATUS_ERROR:
            throw Error {};
        default:
            bt_common_abort();
        }
    }

    MessageIterator seekBeginning() const
    {
        const auto status = bt_message_iterator_seek_beginning(this->libObjPtr());

        switch (status) {
        case BT_MESSAGE_ITERATOR_SEEK_BEGINNING_STATUS_OK:
            break;
        case BT_MESSAGE_ITERATOR_SEEK_BEGINNING_STATUS_AGAIN:
            throw TryAgain {};
        case BT_MESSAGE_ITERATOR_SEEK_BEGINNING_STATUS_MEMORY_ERROR:
            throw MemoryError {};
        case BT_MESSAGE_ITERATOR_SEEK_BEGINNING_STATUS_ERROR:
            throw Error {};
        default:
            bt_common_abort();
        }

        return *this;
    }

    bool canSeekNsFromOrigin(const std::int64_t nsFromOrigin) const
    {
        bt_bool canSeek;

        const auto status =
            bt_message_iterator_can_seek_ns_from_origin(this->libObjPtr(), nsFromOrigin, &canSeek);

        switch (status) {
        case BT_MESSAGE_ITERATOR_CAN_SEEK_NS_FROM_ORIGIN_STATUS_OK:
            return static_cast<bool>(canSeek);
        case BT_MESSAGE_ITERATOR_CAN_SEEK_NS_FROM_ORIGIN_STATUS_AGAIN:
            throw TryAgain {};
        case BT_MESSAGE_ITERATOR_CAN_SEEK_NS_FROM_ORIGIN_STATUS_MEMORY_ERROR:
            throw MemoryError {};
        case BT_MESSAGE_ITERATOR_CAN_SEEK_NS_FROM_ORIGIN_STATUS_ERROR:
            throw Error {};
        default:
            bt_common_abort();
        }
    }

    MessageIterator seekNsFromOrigin(const std::int64_t nsFromOrigin) const
    {
        const auto status =
            bt_message_iterator_seek_ns_from_origin(this->libObjPtr(), nsFromOrigin);

        switch (status) {
        case BT_MESSAGE_ITERATOR_SEEK_NS_FROM_ORIGIN_STATUS_OK:
            break;
        case BT_MESSAGE_ITERATOR_SEEK_NS_FROM_ORIGIN_STATUS_AGAIN:
            throw TryAgain {};
        case BT_MESSAGE_ITERATOR_SEEK_NS_FROM_ORIGIN_STATUS_MEMORY_ERROR:
            throw MemoryError {};
        case BT_MESSAGE_ITERATOR_SEEK_NS_FROM_ORIGIN_STATUS_ERROR:
            throw Error {};
        default:
            bt_common_abort();
        }

        return *this;
    }

    bool canSeekForward() const noexcept
    {
        return static_cast<bool>(bt_message_iterator_can_seek_forward(this->libObjPtr()));
    }

    Shared shared() const noexcept
    {
        return Shared::createWithRef(*this);
    }
};

} /* namespace bt2 */

#endif /* BABELTRACE_CPP_COMMON_BT2_MESSAGE_ITERATOR_HPP */
