/*
 * Copyright (C) 2017 Canonical Ltd.
 * Copyright (C) 2022 SUSE Software Solutions Germany GmbH
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Authored by: Pete Woods <pete.woods@canonical.com>
 *              Michi Henning <michi.henning@canonical.com>
 *              James Henstridge <james.henstridge@canonical.com>
 *              Benjamin Zeller <bzeller@suse.com>
 */

#ifndef ZYPP_GLIB_UTIL_GLIBMEMORY_H
#define ZYPP_GLIB_UTIL_GLIBMEMORY_H

#include <memory>
#include <glib.h>


#include <zypp-glib/utils/ResourcePtr>
#include <zypp-glib/utils/RetainPtr>
#include <zypp-glib/utils/OpaqueTypePtr>
namespace zypp::glib {

    template< typename T> struct GLibTypeTraits;

    template <typename T, typename Trait = GLibTypeTraits<T> >
    struct StdDeleterForGlibType {
        void operator() ( T* ptr ) const {
            Trait::delete_fn(ptr);
        }
    };

    namespace internal
    {
        /**
         * \brief Adapter class to assign to smart pointers from functions that take a reference.
         *
         * Adapter class that allows passing a shared_ptr or unique_ptr where glib
         * expects a parameter of type ElementType** (such as GError**), by providing
         * a default conversion operator to ElementType**. This allows the glib method
         * to assign to the ptr_ member. From the destructor, we assign to the
         * provided smart pointer.
         */
        template<typename SP>
        class GlibAssigner
        {
        public:
            typedef typename SP::element_type ElementType;
            using TraitsType = typename SP::traits_type;

            GlibAssigner(SP& smart_ptr) noexcept :
                    smart_ptr_(smart_ptr)
            {
            }

            GlibAssigner(const GlibAssigner& other) = delete;

            GlibAssigner(GlibAssigner&& other) noexcept:
                    ptr_(other.ptr_), smart_ptr_(other.smart_ptr_)
            {
                other.ptr_ = nullptr;
            }

            ~GlibAssigner() noexcept
            {
                smart_ptr_ = SP(ptr_);
            }

            GlibAssigner& operator=(const GlibAssigner& other) = delete;

            operator ElementType**() noexcept
            {
                return &ptr_;
            }

        private:
            ElementType* ptr_ = nullptr;

            SP& smart_ptr_;
        };

        struct GSourceUnsubscriber
        {
            void operator()(guint tag) noexcept
            {
                if (tag != 0)
                {
                    g_source_remove(tag);
                }
            }
        };

        template <typename T>
        using has_glib_type_trait = typename GLibTypeTraits<T>::type_name; // type name is required to be in all the glib traits

        template <class T>
        using has_retain_traits = typename T::RetainTraits;

        template <class T>
        using has_delete_trait = decltype(T::delete_fn(std::declval< typename T::type_name *>()));

        template <class T, class fn>
        using is_simple_copy_trait_fn  =  decltype( std::declval<fn>()(std::declval<T *>()) );

        template <class T, class fn>
        using is_extended_copy_trait_fn  = decltype(std::declval<fn>()(std::declval<T *>(), std::declval< gpointer >()) );

        template <class T>
        using has_simple_copy_trait = is_simple_copy_trait_fn< typename T::type_name, decltype(T::copy_fn) >;

        template <class T>
        using has_extended_copy_trait = is_extended_copy_trait_fn< typename T::type_name, decltype(T::copy_fn) >;
    }

/**
 \brief Helper method to take ownership of glib types assigned from a reference.

 Example:
 \code{.cpp}
 GErrorUPtr error;
 if (!g_key_file_get_boolean(gkf.get(), "group", "key", assign_glib(error)))
 {
     std::cerr << error->message << std::endl;
     throw some_exception();
 }
 \endcode

 Another example:
 \code{.cpp}
 gcharUPtr name;
 g_object_get(obj, "name", assign_glib(name), nullptr);
 \endcode
 */
template<typename SP>
inline internal::GlibAssigner<SP> assign_glib(SP& smart_ptr) noexcept
{
    return internal::GlibAssigner<SP>(smart_ptr);
}

using GSourceManager = ResourcePtr<guint, internal::GSourceUnsubscriber>;

/**
 \brief Simple wrapper to manage the lifecycle of sources.

 When 'timer' goes out of scope or is dealloc'ed, the source will be removed:
 \code{.cpp}
 auto timer = g_source_manager(g_timeout_add(5000, on_timeout, nullptr));
 \endcode
 */
inline GSourceManager g_source_manager(guint id)
{
    return GSourceManager(id, internal::GSourceUnsubscriber());
}

}  // namespace zypp::glib

/**
 * @brief Generates the minimum \ref zypp::internal::GLibTypeTraits for the given \a TypeName.
 * These Type traits are used in C++ code to detect features of the glib types at compile time.
 */
#define ZYPP_GLIB_DEFINE_GLIB_TRAITS_WITH_CODE(TypeName, ...) \
namespace zypp::glib \
{ \
    template<> struct GLibTypeTraits<::TypeName> \
    { \
       using type_name = ::TypeName; \
       __VA_ARGS__\
    }; \
}

/**
 * @brief Generates the minimum \ref zypp::internal::GLibTypeTraits for the given ambigous \a TypeName.
 * Some types that are exported from glib are just simply typedefs for void. In those cases overloading
 * the \ref GLibTypeTraits template does not work. And they are not detected automatically.
 * Instead we create a Traits type having the TypeName as a postfix: GLibTypeTraitsTypeName for example.
 * This trait can then be passed explicitely to templates requiring a GLibTypeTraits
 */
#define ZYPP_GLIB_DEFINE_AMBIGOUS_TYPE_GLIB_TRAITS_WITH_CODE(TypeName, ...) \
namespace zypp::glib \
{ \
    struct GLibTypeTraits##TypeName \
    { \
       using type_name = TypeName; \
       __VA_ARGS__\
    }; \
}

/**
 * @brief Adds a retain trait to the enclosing \ref GLibTypeTraits.
 * Use this as a argument to a \ref GLibTypeTraits definition in case the type supports reference counting.
 * \code {.cpp}
 *
 * ZYPP_GLIB_DEFINE_GLIB_TRAITS_WITH_CODE(TypeName,
 *   ZYPP_GLIB_ADD_GLIB_RETAINTRAITS_FULL( TypeName_ref, TypeName_unref )
 * )
 *
 * \endcode
 */
#define ZYPP_GLIB_ADD_GLIB_RETAINTRAITS_FULL( ref, unref ) \
static constexpr auto ref_counting = true; \
struct RetainTrait { \
    static void increment( type_name *ptr ){ \
        ::ref(ptr); \
    } \
    static void decrement( type_name *ptr ) { \
        ::unref(ptr); \
    } \
};

/**
 * @brief Convenience macro to add a retain trait to a type
 * This macro can be used whenever the type has reference counting and
 * uses functions with _ref and _unref postfixes. E.g. g_object_ref and g_object_unref
 *
 * \code {.cpp}
 * // will add retain traits using g_object_ref and g_object_unref functions
 * ZYPP_GLIB_DEFINE_GLIB_TRAITS_WITH_CODE(GObject,
 *   ZYPP_GLIB_ADD_GLIB_RETAINTRAITS( g_object )
 * )
 * \endcode
 */
#define ZYPP_GLIB_ADD_GLIB_RETAINTRAITS( ref_prefix ) ZYPP_GLIB_ADD_GLIB_RETAINTRAITS_FULL( ref_prefix##_ref, ref_prefix##_unref )

/**
 * @brief Adds a copy helper trait to a \ref GLibTypeTraits type
 * The given \a copyfn must either return a new instance or increase the refcount and return a pointer to the refcounted object,
 * this is the case for all GObject types, because GObject's can not be copied
 */
#define ZYPP_GLIB_ADD_GLIB_COPYTRAIT(copyfn) \
    static type_name *copy_fn ( type_name *ptr ){ \
        return ::copyfn(ptr); \
    }

/**
 * @brief Adds a delete helper trait to a \ref GLibTypeTraits type
 * The given function must either delete the given object or decrease the refcount in case of reference counted types.
 */
#define ZYPP_GLIB_ADD_GLIB_DELETETRAIT(delfn) \
    static void delete_fn ( type_name *ptr ){ \
        ::delfn(ptr); \
    }

/**
 * @brief Convencience macro to define the standard traits and smart pointer types for ref counted types
 */
#define ZYPP_GLIB_DEFINE_GLIB_REF_TYPE_FULL(TypeName, ref, unref, ...) \
ZYPP_GLIB_DEFINE_GLIB_TRAITS_WITH_CODE(TypeName, \
    ZYPP_GLIB_ADD_GLIB_RETAINTRAITS_FULL( ref, unref ) \
    ZYPP_GLIB_ADD_GLIB_COPYTRAIT   ( ref ) \
    ZYPP_GLIB_ADD_GLIB_DELETETRAIT ( unref ) \
    __VA_ARGS__ \
) \
namespace zypp::glib { \
    using TypeName##Ref       = RetainPtr<TypeName, GLibTypeTraits<TypeName>::RetainTrait>; \
    using TypeName##UniqueRef = std::unique_ptr<TypeName, StdDeleterForGlibType<TypeName> >; \
}

/**
 * @brief Convencience macro to define the standard traits and smart pointer types for ref counted types using ref and unref functions
 * with the same prefix, e.g. g_object_ref and g_object_unref
 */
#define ZYPP_GLIB_DEFINE_GLIB_REF_TYPE(TypeName, ref_prefix ) \
    ZYPP_GLIB_DEFINE_GLIB_REF_TYPE_FULL( TypeName, ref_prefix##_ref, ref_prefix##_unref )

/**
 * @brief Convencience macro to define the standard traits and smart pointer types for a simply glib type, that is a type only
 * having a delete helper trait.
 */
#define ZYPP_GLIB_DEFINE_GLIB_SIMPLE_TYPE(TypeName, func, ...) \
ZYPP_GLIB_DEFINE_GLIB_TRAITS_WITH_CODE(TypeName, \
    ZYPP_GLIB_ADD_GLIB_DELETETRAIT ( func ) \
    __VA_ARGS__ \
) \
namespace zypp::glib { \
    static_assert( !std::is_same_v<TypeName, void>, "Void derived types can not be used with ZYPP_GLIB_DEFINE_GLIB_SIMPLE_TYPE" );\
    using TypeName##Ref       = OpaqueTypePtr<TypeName, StdDeleterForGlibType<TypeName> >; \
    using TypeName##UniqueRef = std::unique_ptr<TypeName, StdDeleterForGlibType<TypeName> >; \
}

/**
 * @brief Convencience macro to define the standard traits and smart pointer types for a simple ambiguous glib type, that is a type only
 * having a delete helper trait.
 */
#define ZYPP_GLIB_DEFINE_GLIB_AMBIGUOUS_TYPE(TypeName, func, ... ) \
ZYPP_GLIB_DEFINE_AMBIGOUS_TYPE_GLIB_TRAITS_WITH_CODE(TypeName, \
    ZYPP_GLIB_ADD_GLIB_DELETETRAIT ( func ) \
    __VA_ARGS__ \
) \
namespace zypp::glib { \
    using TypeName##Ref = OpaqueTypePtr<TypeName, StdDeleterForGlibType<TypeName, GLibTypeTraits##TypeName> >; \
    using TypeName##UniqueRef = std::unique_ptr<TypeName, StdDeleterForGlibType<TypeName, GLibTypeTraits##TypeName> >; \
}


ZYPP_GLIB_DEFINE_GLIB_REF_TYPE(GAsyncQueue, g_async_queue)
ZYPP_GLIB_DEFINE_GLIB_SIMPLE_TYPE(GBookmarkFile, g_bookmark_file_free)
ZYPP_GLIB_DEFINE_GLIB_REF_TYPE(GBytes, g_bytes)
ZYPP_GLIB_DEFINE_GLIB_SIMPLE_TYPE(GChecksum, g_checksum_free)
ZYPP_GLIB_DEFINE_GLIB_REF_TYPE(GDateTime, g_date_time)
ZYPP_GLIB_DEFINE_GLIB_SIMPLE_TYPE(GDate, g_date_free)
ZYPP_GLIB_DEFINE_GLIB_SIMPLE_TYPE(GDir, g_dir_close)
ZYPP_GLIB_DEFINE_GLIB_SIMPLE_TYPE(GError, g_error_free)
ZYPP_GLIB_DEFINE_GLIB_REF_TYPE(GHashTable, g_hash_table)
ZYPP_GLIB_DEFINE_GLIB_REF_TYPE(GHmac, g_hmac)
ZYPP_GLIB_DEFINE_GLIB_REF_TYPE(GIOChannel, g_io_channel)
ZYPP_GLIB_DEFINE_GLIB_REF_TYPE(GKeyFile, g_key_file)
ZYPP_GLIB_DEFINE_GLIB_SIMPLE_TYPE(GList, g_list_free)
ZYPP_GLIB_DEFINE_GLIB_REF_TYPE(GArray, g_array)
ZYPP_GLIB_DEFINE_GLIB_REF_TYPE(GPtrArray, g_ptr_array)
ZYPP_GLIB_DEFINE_GLIB_REF_TYPE(GByteArray, g_byte_array)
ZYPP_GLIB_DEFINE_GLIB_REF_TYPE(GMainContext, g_main_context)
ZYPP_GLIB_DEFINE_GLIB_AMBIGUOUS_TYPE(GMainContextPusher, g_main_context_pusher_free)
ZYPP_GLIB_DEFINE_GLIB_REF_TYPE(GMainLoop, g_main_loop)
ZYPP_GLIB_DEFINE_GLIB_REF_TYPE(GSource, g_source)
ZYPP_GLIB_DEFINE_GLIB_REF_TYPE(GMappedFile, g_mapped_file)
ZYPP_GLIB_DEFINE_GLIB_REF_TYPE(GMarkupParseContext, g_markup_parse_context)
ZYPP_GLIB_DEFINE_GLIB_SIMPLE_TYPE(GNode, g_node_destroy)
ZYPP_GLIB_DEFINE_GLIB_SIMPLE_TYPE(GOptionContext, g_option_context_free)
ZYPP_GLIB_DEFINE_GLIB_REF_TYPE(GOptionGroup, g_option_group)
ZYPP_GLIB_DEFINE_GLIB_SIMPLE_TYPE(GPatternSpec, g_pattern_spec_free)
ZYPP_GLIB_DEFINE_GLIB_SIMPLE_TYPE(GQueue, g_queue_free)
ZYPP_GLIB_DEFINE_GLIB_SIMPLE_TYPE(GRand, g_rand_free)
ZYPP_GLIB_DEFINE_GLIB_REF_TYPE(GRegex, g_regex)
ZYPP_GLIB_DEFINE_GLIB_REF_TYPE(GMatchInfo, g_match_info)
ZYPP_GLIB_DEFINE_GLIB_SIMPLE_TYPE(GScanner, g_scanner_destroy)
ZYPP_GLIB_DEFINE_GLIB_SIMPLE_TYPE(GSequence, g_sequence_free)
ZYPP_GLIB_DEFINE_GLIB_SIMPLE_TYPE(GSList, g_slist_free)
ZYPP_GLIB_DEFINE_GLIB_SIMPLE_TYPE(GString, g_autoptr_cleanup_gstring_free)
ZYPP_GLIB_DEFINE_GLIB_SIMPLE_TYPE(GStringChunk, g_string_chunk_free)
ZYPP_GLIB_DEFINE_GLIB_REF_TYPE(GStrvBuilder, g_strv_builder)
ZYPP_GLIB_DEFINE_GLIB_REF_TYPE(GThread, g_thread)
ZYPP_GLIB_DEFINE_GLIB_AMBIGUOUS_TYPE(GMutexLocker, g_mutex_locker_free)
ZYPP_GLIB_DEFINE_GLIB_AMBIGUOUS_TYPE(GRecMutexLocker, g_rec_mutex_locker_free)
ZYPP_GLIB_DEFINE_GLIB_AMBIGUOUS_TYPE(GRWLockWriterLocker, g_rw_lock_writer_locker_free)
ZYPP_GLIB_DEFINE_GLIB_AMBIGUOUS_TYPE(GRWLockReaderLocker, g_rw_lock_reader_locker_free)
ZYPP_GLIB_DEFINE_GLIB_SIMPLE_TYPE(GTimer, g_timer_destroy)
ZYPP_GLIB_DEFINE_GLIB_REF_TYPE(GTimeZone, g_time_zone)
ZYPP_GLIB_DEFINE_GLIB_REF_TYPE(GTree, g_tree)
ZYPP_GLIB_DEFINE_GLIB_REF_TYPE(GVariant, g_variant)
ZYPP_GLIB_DEFINE_GLIB_REF_TYPE(GVariantBuilder, g_variant_builder)
ZYPP_GLIB_DEFINE_GLIB_SIMPLE_TYPE(GVariantIter, g_variant_iter_free)
ZYPP_GLIB_DEFINE_GLIB_REF_TYPE( GVariantDict, g_variant_dict )
ZYPP_GLIB_DEFINE_GLIB_SIMPLE_TYPE(GVariantType, g_variant_type_free)
ZYPP_GLIB_DEFINE_GLIB_AMBIGUOUS_TYPE(GRefString, g_ref_string_release)
ZYPP_GLIB_DEFINE_GLIB_REF_TYPE(GUri, g_uri)

/**
 * Manually add extra definitions for gchar* and gchar**
 */
ZYPP_GLIB_DEFINE_GLIB_AMBIGUOUS_TYPE(gchar, g_free)

typedef gchar* gcharv;
ZYPP_GLIB_DEFINE_GLIB_AMBIGUOUS_TYPE(gcharv, g_strfreev)




#endif
