// Copyright (c) ZeroC, Inc.

#ifndef ICE_VALUE_H
#define ICE_VALUE_H

#include "Config.h"
#include "SlicedDataF.h"
#include "ValueF.h"

#include <ostream>
#include <string>
#include <type_traits>

namespace Ice
{
    class OutputStream;
    class InputStream;

    /// The base class for instances of Slice-defined classes.
    /// @headerfile Ice/Ice.h
    class ICE_API Value
    {
    public:
        /// Default constructor.
        Value() noexcept = default;

        // There is no copy constructor, move constructor, copy-assignment operator or move-assignment operator to
        // prevent accidental slicing.
        Value(Value&&) = delete;

        virtual ~Value();

        Value& operator=(const Value&) = delete;
        Value& operator=(Value&&) = delete;

        /// The Ice runtime calls this function before marshaling a class instance.
        /// Subclasses can override this function in order to update or validate their fields before marshaling.
        virtual void ice_preMarshal() {}

        /// The Ice runtime calls this function after unmarshaling a class instance.
        /// Subclasses can override this function in order to update or validate their fields after unmarshaling.
        virtual void ice_postUnmarshal() {}

        /// Gets the Slice type ID of the most-derived class supported by this object.
        /// @return The Slice type ID.
        [[nodiscard]] virtual const char* ice_id() const noexcept;

        /// Gets the Slice type ID of this type.
        /// @return The return value is always "::Ice::Object".
        static const char* ice_staticId() noexcept;

        /// Creates a shallow polymorphic copy of this instance.
        /// @return The cloned value.
        [[nodiscard]] ValuePtr ice_clone() const { return _iceCloneImpl(); }

        /// Gets the sliced data associated with this instance.
        /// @return The sliced data if the value has a preserved-slice base class and has been sliced during
        /// unmarshaling of the value, null otherwise.
        [[nodiscard]] SlicedDataPtr ice_getSlicedData() const;

        /// Outputs a description of this instance to the stream. This description includes the type name and the name
        /// and value of all the fields of this instance.
        /// Use ["cpp:custom-print"] to tell the Slice compiler to generate an override implemented by the application.
        /// @param os The output stream.
        virtual void ice_print(std::ostream& os) const;

        /// Outputs the name and value of each field of this instance, including inherited fields, to the stream.
        /// Classes generated by the Slice compiler override this function.
        /// @param os The output stream.
        virtual void ice_printFields(std::ostream& os) const;

        /// @cond INTERNAL

        virtual void _iceWrite(Ice::OutputStream*) const;
        virtual void _iceRead(Ice::InputStream*);

    protected:
        Value(const Value&) = default; // for clone

        // Helper class that allows derived classes to clone "this" even though the copy constructor is protected.
        template<class T> struct CloneEnabler : public T
        {
            CloneEnabler(const T& other) : T(other) {}
            static std::shared_ptr<T> clone(const T& other) { return std::make_shared<CloneEnabler>(other); }
        };

        [[nodiscard]] virtual ValuePtr _iceCloneImpl() const;

        virtual void _iceWriteImpl(Ice::OutputStream*) const {}
        virtual void _iceReadImpl(Ice::InputStream*) {}
        /// @endcond

    private:
        SlicedDataPtr _slicedData;
    };

    /// Outputs the description of a class instance to a stream. This function calls Value::ice_print on @p value.
    /// @param os The output stream.
    /// @param value The class instance.
    /// @return The output stream.
    inline std::ostream& operator<<(std::ostream& os, const Value& value)
    {
        value.ice_print(os);
        return os;
    }

    /// Outputs the description of a class instance held in a shared_ptr.
    /// @tparam T The class type.
    /// @param os The output stream.
    /// @param value The class instance held in a shared pointer. May be null.
    /// @return The output stream.
    template<class T, std::enable_if_t<std::is_base_of_v<Value, T>, bool> = true>
    inline std::ostream& operator<<(std::ostream& os, const std::shared_ptr<T>& value)
    {
        if (value)
        {
            os << *value;
        }
        else
        {
            os << "nullptr";
        }
        return os;
    }
}

#endif
