#ifndef CATCH_UTILS_STRINGMAKERS_H
#define CATCH_UTILS_STRINGMAKERS_H

#include <tango/tango.h>

#include <iostream>

#include <catch2/catch_tostring.hpp>

#include "callback_mock_helpers.h"
#include "test_server.h"

#include <tango/internal/stl_corba_helpers.h>

/*
 * Guidelines for string makers in Catch2:
 *
 * - Add string maker implementations for all Tango classes used in test assertions
 * - These implementations should accept a `const T&`
 * - If the class internally holds a pointer to another class, use TangoTest::detail::string_maker_pointer and
 *   implement a string maker for the class directly
 * - Prefer CATCH_REGISTER_ENUM for enumerations (plain enums and enum classes)
 * - Use string_maker_sequence for sequences
 *
 */

CATCH_REGISTER_ENUM(Tango::AttrMemorizedType,
                    Tango::AttrMemorizedType::NOT_KNOWN,
                    Tango::AttrMemorizedType::NONE,
                    Tango::AttrMemorizedType::MEMORIZED,
                    Tango::AttrMemorizedType::MEMORIZED_WRITE_INIT)

CATCH_REGISTER_ENUM(Tango::AttrWriteType,
                    Tango::AttrWriteType::READ,
                    Tango::AttrWriteType::READ_WITH_WRITE,
                    Tango::AttrWriteType::READ_WRITE,
                    Tango::AttrWriteType::WT_UNKNOWN)

CATCH_REGISTER_ENUM(Tango::AttrDataFormat,
                    Tango::AttrDataFormat::SCALAR,
                    Tango::AttrDataFormat::SPECTRUM,
                    Tango::AttrDataFormat::IMAGE,
                    Tango::AttrDataFormat::FMT_UNKNOWN)

CATCH_REGISTER_ENUM(Tango::DispLevel,
                    Tango::DispLevel::OPERATOR,
                    Tango::DispLevel::EXPERT,
                    Tango::DispLevel::DL_UNKNOWN)

CATCH_REGISTER_ENUM(Tango::AttrQuality,
                    Tango::AttrQuality::ATTR_INVALID,
                    Tango::AttrQuality::ATTR_ALARM,
                    Tango::AttrQuality::ATTR_CHANGING,
                    Tango::AttrQuality::ATTR_WARNING)

CATCH_REGISTER_ENUM(Tango::EventSubMode,
                    Tango::EventSubMode::Sync,
                    Tango::EventSubMode::SyncRead,
                    Tango::EventSubMode::Async,
                    Tango::EventSubMode::AsyncRead,
                    Tango::EventSubMode::Stateless)

CATCH_REGISTER_ENUM(Tango::EventReason,
                    Tango::EventReason::Unknown,
                    Tango::EventReason::SubFail,
                    Tango::EventReason::SubSuccess,
                    Tango::EventReason::Update)

namespace TangoTest::detail
{

/// separator between entries
const std::string sep{", "};

/// opening curly brace
const std::string opc{"{ "};

// closing curly brace
const std::string clc{" }"};

template <typename T>
std::string string_maker_pointer(T *value)
{
    if(value == nullptr)
    {
        return Catch::StringMaker<std::nullptr_t>::convert(nullptr);
    }

    return Catch::StringMaker<T>::convert(*value);
}

/// @tparam Sequence                    sequence type (deduced)
/// @tparam Element                     type (must be provided)
/// @tparam dereference_element_pointer pointer dereferencing (true) or not (false) for elements
template <typename Sequence, typename Element, bool dereference_element_pointer>
std::string string_maker_sequence(Sequence const &seq)
{
    using TangoTest::detail::clc;
    using TangoTest::detail::opc;
    using TangoTest::detail::sep;

    auto length = size(seq);

    std::ostringstream os;

    if(length == 0)
    {
        os << opc;
        os << clc;
        return os.str();
    }

    TANGO_ASSERT(length > 0);
    auto end_it = end(seq);

    os << opc;
    for(auto it = begin(seq); it < end_it; it++)
    {
        Element elem = *it;
        if constexpr(dereference_element_pointer && std::is_pointer_v<Element>)
        {
            os << string_maker_pointer(elem);
        }
        else
        {
            static_assert(!dereference_element_pointer,
                          "dereference_element_pointer must be false with non-pointer elements");
            os << Catch::StringMaker<Element>::convert(*it);
        }

        if(std::distance(it, end_it) > 1)
        {
            os << sep;
        }
    }
    os << clc;

    return os.str();
}

} // namespace TangoTest::detail

namespace Catch
{
template <>
struct StringMaker<Tango::DeviceInfo>
{
    static std::string convert(Tango::DeviceInfo const &info);
};

template <>
struct StringMaker<Tango::DeviceProxy>
{
    static std::string convert(const Tango::DeviceProxy &dev);
};

template <>
struct StringMaker<Tango::DeviceAttribute>
{
    static std::string convert(const Tango::DeviceAttribute &da);
};

template <>
struct StringMaker<Tango::DeviceData>
{
    static std::string convert(const Tango::DeviceData &dd);
};

template <>
struct StringMaker<Tango::TimeVal>
{
    static std::string convert(Tango::TimeVal const &value);
};

template <>
struct StringMaker<Tango::EventData>
{
    static std::string convert(Tango::EventData const &value);
};

template <>
struct StringMaker<Tango::EventDataList>
{
    static std::string convert(Tango::EventDataList const &value)
    {
        using VectorType = Tango::EventDataList;
        using ElementType = VectorType::value_type;

        return TangoTest::detail::string_maker_sequence<VectorType, ElementType, true>(value);
    }
};

template <>
struct StringMaker<Tango::DataReadyEventData>
{
    static std::string convert(Tango::DataReadyEventData const &value);
};

template <>
struct StringMaker<Tango::DataReadyEventDataList>
{
    static std::string convert(Tango::DataReadyEventDataList const &value)
    {
        using VectorType = Tango::DataReadyEventDataList;
        using ElementType = VectorType::value_type;

        return TangoTest::detail::string_maker_sequence<VectorType, ElementType, true>(value);
    }
};

template <>
struct StringMaker<TangoTest::AttrReadEventCopyable>
{
    static std::string convert(TangoTest::AttrReadEventCopyable const &value);
};

template <>
struct StringMaker<TangoTest::AttrWrittenEventCopyable>
{
    static std::string convert(TangoTest::AttrWrittenEventCopyable const &value);
};

template <>
struct StringMaker<TangoTest::CmdDoneEventCopyable>
{
    static std::string convert(TangoTest::CmdDoneEventCopyable const &value);
};

template <>
struct StringMaker<Tango::DevIntrChangeEventData>
{
    static std::string convert(Tango::DevIntrChangeEventData const &value);
};

template <>
struct StringMaker<Tango::DevIntrChangeEventDataList>
{
    static std::string convert(Tango::DevIntrChangeEventDataList const &value)
    {
        using VectorType = Tango::DevIntrChangeEventDataList;
        using ElementType = VectorType::value_type;

        return TangoTest::detail::string_maker_sequence<VectorType, ElementType, true>(value);
    }
};

template <>
struct StringMaker<Tango::CommandInfo>
{
    static std::string convert(Tango::CommandInfo const &value);
};

template <>
struct StringMaker<Tango::AttributeAlarmInfo>
{
    static std::string convert(Tango::AttributeAlarmInfo const &value);
};

template <>
struct StringMaker<Tango::ChangeEventInfo>
{
    static std::string convert(Tango::ChangeEventInfo const &value);
};

template <>
struct StringMaker<Tango::PeriodicEventInfo>
{
    static std::string convert(Tango::PeriodicEventInfo const &value);
};

template <>
struct StringMaker<Tango::ArchiveEventInfo>
{
    static std::string convert(Tango::ArchiveEventInfo const &value);
};

template <>
struct StringMaker<Tango::AttributeEventInfo>
{
    static std::string convert(Tango::AttributeEventInfo const &value);
};

template <>
struct StringMaker<Tango::AttributeInfoEx>
{
    static std::string convert(Tango::AttributeInfoEx const &value);
};

template <>
struct StringMaker<Tango::FwdEventData>
{
    static std::string convert(Tango::FwdEventData const &value);
};

template <>
struct StringMaker<Tango::AttrConfEventData>
{
    static std::string convert(Tango::AttrConfEventData const &value);
};

template <>
struct StringMaker<Tango::AttrConfEventDataList>
{
    static std::string convert(Tango::AttrConfEventDataList const &value)
    {
        using VectorType = Tango::AttrConfEventDataList;
        using ElementType = VectorType::value_type;

        return TangoTest::detail::string_maker_sequence<VectorType, ElementType, true>(value);
    }
};

template <>
struct StringMaker<Tango::FwdAttrConfEventData>
{
    static std::string convert(Tango::FwdAttrConfEventData const &value);
};

template <>
struct StringMaker<Tango::PipeEventData>
{
    static std::string convert(Tango::PipeEventData const &value);
};

template <>
struct StringMaker<Tango::PipeEventDataList>
{
    static std::string convert(Tango::PipeEventDataList const &value)
    {
        using VectorType = Tango::PipeEventDataList;
        using ElementType = VectorType::value_type;

        return TangoTest::detail::string_maker_sequence<VectorType, ElementType, true>(value);
    }
};

template <>
struct StringMaker<CORBA::Any>
{
    static std::string convert(CORBA::Any const &any);
};

template <>
struct StringMaker<Tango::DevError>
{
    static std::string convert(Tango::DevError const &err);
};

template <>
struct StringMaker<Tango::NamedDevFailedList>
{
    static std::string convert(Tango::NamedDevFailedList const &err);
};

template <>
struct StringMaker<Tango::NamedDevFailed>
{
    static std::string convert(Tango::NamedDevFailed const &err);
};

/// Generic output routine for CORBA sequences
template <typename T>
struct StringMaker<T, std::enable_if_t<Tango::detail::is_corba_seq_v<T>>>
{
    static std::string convert(T const &seq)
    {
        using ElementType = Tango::detail::corba_ut_from_seq_t<T>;

        return TangoTest::detail::string_maker_sequence<T, ElementType, false>(seq);
    }
};

// No generic output routine for CORBA var classes
// as this is available via implicit conversion

template <>
struct StringMaker<TangoTest::ExitStatus>
{
    static std::string convert(TangoTest::ExitStatus const &status);
};

template <>
struct StringMaker<Tango::AttributeAlarm>
{
    static std::string convert(Tango::AttributeAlarm const &value);
};

template <>
struct StringMaker<Tango::EventProperties>
{
    static std::string convert(Tango::EventProperties const &value);
};

template <>
struct StringMaker<Tango::ChangeEventProp>
{
    static std::string convert(Tango::ChangeEventProp const &value);
};

template <>
struct StringMaker<Tango::PeriodicEventProp>
{
    static std::string convert(Tango::PeriodicEventProp const &value);
};

template <>
struct StringMaker<Tango::ArchiveEventProp>
{
    static std::string convert(Tango::ArchiveEventProp const &value);
};

template <>
struct StringMaker<Tango::AttributeDim>
{
    static std::string convert(Tango::AttributeDim const &value);
};

template <>
struct StringMaker<Tango::AttributeValue_5>
{
    static std::string convert(Tango::AttributeValue_5 const &attr_conf);
};

template <>
struct StringMaker<Tango::AttributeConfig_5>
{
    static std::string convert(Tango::AttributeConfig_5 const &attr_conf);
};

} // namespace Catch

#endif // CATCH_UTILS_STRINGMAKERS_H
