//MIT License
//
//Copyright (c) 2017 Mindaugas Vinkelis
//
//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files (the "Software"), to deal
//in the Software without restriction, including without limitation the rights
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//copies of the Software, and to permit persons to whom the Software is
//furnished to do so, subject to the following conditions:
//
//The above copyright notice and this permission notice shall be included in all
//copies or substantial portions of the Software.
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//SOFTWARE.

#ifndef BITSERY_DETAILS_SERIALIZATION_COMMON_H
#define BITSERY_DETAILS_SERIALIZATION_COMMON_H

#include <type_traits>
#include <utility>
#include <tuple>
#include "adapter_common.h"
#include "../traits/core/traits.h"


namespace bitsery {

    //this allows to call private serialize method, and construct instance (if no default constructor is provided) for your type
    //just make friend it in your class
    class Access {
    public:
        template<typename S, typename T>
        static auto serialize(S &s, T &obj) -> decltype(obj.serialize(s)) {
            obj.serialize(s);
        }

        template <typename T>
        static T create() {
            //if you get an error here, please create default constructor
            return T{};
        }
        template <typename T>
        static T* create(void* ptr) {
            return new(ptr) T{};
        }

    };


    // convenient functors that can be passed as lambda to serializer/deserializer instead of writing lambda
    // e.g. instead of writing this:
    // s.container(c, 100, [](S& s, float& v) { s.ext4b(v, CompactValue{});});
    // you can write like this
    // s.container(c, 100, FtorExtValue2b<CompactValue>{});
    template<size_t N, typename Ext>
    struct FtorExtValue : public Ext {
        template <typename S, typename T>
        void operator()(S& s, T& v) const {
            s.template ext<N>(v, static_cast<const Ext&>(*this));
        }
    };

    template <typename Ext>
    struct FtorExtValue1b: FtorExtValue<1, Ext> {};
    template <typename Ext>
    struct FtorExtValue2b: FtorExtValue<2, Ext> {};
    template <typename Ext>
    struct FtorExtValue4b: FtorExtValue<4, Ext> {};
    template <typename Ext>
    struct FtorExtValue8b: FtorExtValue<8, Ext> {};
    template <typename Ext>
    struct FtorExtValue16b: FtorExtValue<16, Ext> {};

    template<typename Ext>
    struct FtorExtObject : public Ext {
        template <typename S, typename T>
        void operator()(S& s, T& v) const {
            s.ext(v, static_cast<const Ext&>(*this));
        }
    };


    //when call to serialize function is ambiguous (member and non-member serialize function exists for a type)
    //specialize this class by inheriting from either UseNonMemberFnc or UseMemberFnc
    //e.g.
    //template <> struct SelectSerializeFnc<MyDerivedClass>:UseMemberFnc {};
    template<typename T>
    struct SelectSerializeFnc : std::integral_constant<int, 0> {
    };

    //types you need to inherit from when specializing SelectSerializeFnc class
    struct UseNonMemberFnc : std::integral_constant<int, 1> {
    };
    struct UseMemberFnc : std::integral_constant<int, 2> {
    };

    namespace details {

        //helper types for error handling
        template<typename T>
        struct IsContainerTraitsDefined : public IsDefined<typename traits::ContainerTraits<T>::TValue> {
        };

        template<typename T>
        struct IsTextTraitsDefined : public IsDefined<typename traits::TextTraits<T>::TValue> {
        };

        template<typename Ext, typename T>
        struct IsExtensionTraitsDefined : public IsDefined<typename traits::ExtensionTraits<Ext, T>::TValue> {
        };

#ifdef _MSC_VER
        //helper types for HasSerializeFunction
        template <typename S, typename T>
        using TrySerializeFunction = decltype(serialize(std::declval<S &>(), std::declval<T &>()));

        template <typename S, typename T>
        struct HasSerializeFunctionHelper {
            template <typename Q, typename R, typename = TrySerializeFunction<Q, R>>
            static std::true_type tester(Q&&, R&&);
            static std::false_type tester(...);
            using type = decltype(tester(std::declval<S>(), std::declval<T>()));
        };
        template <typename S, typename T>
        struct HasSerializeFunction :HasSerializeFunctionHelper<S, T>::type {};

        //helper types for HasSerializeMethod
        template <typename S, typename T>
        using TrySerializeMethod = decltype(Access::serialize(std::declval<S &>(), std::declval<T &>()));

        template <typename S, typename T>
        struct HasSerializeMethodHelper {
            template <typename Q, typename R, typename = TrySerializeMethod<Q, R>>
            static std::true_type tester(Q&&, R&&);
            static std::false_type tester(...);
            using type = decltype(tester(std::declval<S>(), std::declval<T>()));
        };
        template <typename S, typename T>
        struct HasSerializeMethod :HasSerializeMethodHelper<S, T>::type {};

        //helper types for IsBriefSyntaxIncluded
        template <typename S, typename T>
        using TryProcessBriefSyntax = decltype(processBriefSyntax(std::declval<S &>(), std::declval<T &&>()));

        template <typename S, typename T>
        struct IsBriefSyntaxIncludedHelper {
            template <typename Q, typename R, typename = TryProcessBriefSyntax<Q, R>>
            static std::true_type tester(Q&&, R&&);
            static std::false_type tester(...);
            using type = decltype(tester(std::declval<S>(), std::declval<T>()));
        };

        template <typename S, typename T>
        struct IsBriefSyntaxIncluded :IsBriefSyntaxIncludedHelper<S, T>::type {};
#else
        //helper metafunction, that is added to c++17
        template<typename... Ts>
        struct make_void {
            typedef void type;
        };
        template<typename... Ts>
        using void_t = typename make_void<Ts...>::type;

        template<typename, typename, typename = void>
        struct HasSerializeFunction : std::false_type {
        };

        template<typename S, typename T>
        struct HasSerializeFunction<S, T,
                void_t<decltype(serialize(std::declval<S &>(), std::declval<T &>()))>
        > : std::true_type {
        };


        template<typename, typename, typename = void>
        struct HasSerializeMethod : std::false_type {
        };

        template<typename S, typename T>
        struct HasSerializeMethod<S, T,
                void_t<decltype(Access::serialize(std::declval<S &>(), std::declval<T &>()))>
        > : std::true_type {
        };

        //this solution doesn't work with visual studio, but is more elegant
        template<typename, typename, typename = void>
        struct IsBriefSyntaxIncluded : std::false_type {
        };

        template<typename S, typename T>
        struct IsBriefSyntaxIncluded<S, T,
                void_t<decltype(processBriefSyntax(std::declval<S &>(), std::declval<T &&>()))>
        > : std::true_type {
        };
#endif


        //used for extensions when extension TValue = void
        struct DummyType {
        };

/*
 * this includes all integral types, floats and enums(except bool)
 */
        template<typename T>
        struct IsFundamentalType : std::integral_constant<bool,
                std::is_enum<T>::value
                || std::is_floating_point<T>::value
                || std::is_integral<T>::value> {
        };

        template<typename T, typename Integral = void>
        struct IntegralFromFundamental {
            using TValue = T;
        };

        template<typename T>
        struct IntegralFromFundamental<T, typename std::enable_if<std::is_enum<T>::value>::type> {
            using TValue = typename std::underlying_type<T>::type;
        };

        template<typename T>
        struct IntegralFromFundamental<T, typename std::enable_if<std::is_floating_point<T>::value>::type> {
            using TValue = typename std::conditional<std::is_same<T, float>::value, uint32_t, uint64_t>::type;
        };

        template<typename T>
        struct UnsignedFromFundamental {
            using type = typename std::make_unsigned<typename IntegralFromFundamental<T>::TValue>::type;
        };

        template<typename T>
        using SameSizeUnsigned = typename UnsignedFromFundamental<T>::type;


/*
 * functions for object serialization
 */

        template<typename S, typename T>
        struct SerializeFunction {

            static void invoke(S &s, T &v) {
                static_assert(HasSerializeFunction<S, T>::value || HasSerializeMethod<S, T>::value,
                              "\nPlease define 'serialize' function for your type (inside or outside of class):\n"
                                      "  template<typename S>\n"
                                      "  void serialize(S& s)\n"
                                      "  {\n"
                                      "    ...\n"
                                      "  }\n");
                using TDecayed = typename std::decay<T>::type;
                selectSerializeFnc(s, v, SelectSerializeFnc<TDecayed>{});
            }

            static constexpr bool isDefined() {
                return HasSerializeFunction<S, T>::value || HasSerializeMethod<S, T>::value;
            }

        private:
            static void selectSerializeFnc(S &s, T &v, std::integral_constant<int, 0>) {
                static_assert(!(HasSerializeFunction<S, T>::value && HasSerializeMethod<S, T>::value),
                              "\nPlease define only one 'serialize' function (member OR free).\n"
                                      "If serialization function is inherited from base class, then explicitly select correct function for your type e.g.:\n"
                                      "  template <>\n"
                                      "  struct SelectSerializeFnc<DerivedClass>:UseMemberFnc {};\n");
                selectSerializeFnc(s, v, std::integral_constant<int,
                        HasSerializeFunction<S, T>::value ? 1 : 2>{});
            }

            static void selectSerializeFnc(S &s, T &v, std::integral_constant<int, 1>) {
                serialize(s, v);
            }

            static void selectSerializeFnc(S &s, T &v, std::integral_constant<int, 2>) {
                Access::serialize(s, v);
            }
        };

/*
 * functions for object serialization
 */

        template<typename S, typename T, typename Enabled = void>
        struct BriefSyntaxFunction {

            static void invoke(S &s, T &&obj) {
                static_assert(IsBriefSyntaxIncluded<S, T>::value,
                              "\nPlease include '<bitsery/brief_syntax.h>' to use operator():\n");

                processBriefSyntax(s, std::forward<T>(obj));
            }
        };

        /*
         * helper function for getting context from serializer/deserializer
         */

        template<int Index, typename... Conds>
        struct FindIndex : std::integral_constant<int, Index> {};

        template<int Index, typename Cond, typename... Conds>
        struct FindIndex<Index, Cond, Conds...> :
            std::conditional<Cond::value, std::integral_constant<int, Index>, FindIndex<Index+1, Conds...>>::type
        {};

        template <typename T, typename Tuple>
        struct GetConvertibleTypeIndexFromTuple;

        template <typename T, typename... Us>
        struct GetConvertibleTypeIndexFromTuple<T, std::tuple<Us...>> : FindIndex<0, std::is_convertible<Us&, T&>...> {};


        template <typename T, typename Tuple>
        struct IsExistsConvertibleTupleType;

        template <typename T, typename... Us>
        struct IsExistsConvertibleTupleType<T, std::tuple<Us...>> :
            std::integral_constant<bool, GetConvertibleTypeIndexFromTuple<T, std::tuple<Us...>>::value != sizeof...(Us)> {};


        /*
         * get context from internal or external, and check if it's convertible or not
         */


        template<bool AssertExists, typename TCast, typename TContext>
        TCast* getDirectlyIfExists(TContext& ctx, std::true_type) {
            return &static_cast<TCast&>(ctx);
        }

        template<bool AssertExists, typename TCast, typename TContext>
        TCast* getDirectlyIfExists(TContext& , std::false_type) {
            // TCast cannot be convertible from provided context
            static_assert(!AssertExists,
                          "Invalid context cast. Context type doesn't exists.\nSome functionality requires (de)seserializer to have specific context.");
            return nullptr;
        }


        template<bool AssertExists, typename TCast, typename ... TArgs>
        TCast* getFromTupleIfExists(std::tuple<TArgs...>& ctx, std::true_type) {
            using TupleIndex = GetConvertibleTypeIndexFromTuple<TCast, std::tuple<TArgs...>>;
            return &static_cast<TCast&>(std::get<TupleIndex::value>(ctx));
        }

        template<bool AssertExists, typename TCast, typename ... TArgs>
        TCast* getFromTupleIfExists(std::tuple<TArgs...>& , std::false_type) {
            // TCast cannot be convertible from provided context
            static_assert(!AssertExists,
                "Invalid context cast. Context type doesn't exists.\nSome functionality requires (de)seserializer to have specific context.");
            return nullptr;
        }

        //non tuple context
        template<bool AssertExists, typename TCast, typename TContext>
        TCast* getContext(TContext& ctx) {
            return getDirectlyIfExists<AssertExists, TCast>(ctx, std::is_convertible<TContext&, TCast&>{});
        }

        //tuple context
        template<bool AssertExists, typename TCast, typename ... TArgs>
        TCast* getContext(std::tuple<TArgs...>& ctx) {
            return getFromTupleIfExists<AssertExists, TCast>(ctx, IsExistsConvertibleTupleType<TCast, std::tuple<TArgs...>>{});
        }

        template <typename Adapter, typename Context>
        class AdapterAndContextRef {
        public:
            static constexpr bool HasContext = true;
            using Config = typename Adapter::TConfig;

            // constructing adapter in place is important,
            // because enableBitPacking might create instance with bit write/read enabled adapter wrapper,
            // which has non trivial destructor
            template <typename ... TArgs>
            explicit AdapterAndContextRef(Context& ctx, TArgs&& ... args)
                : _adapter{std::forward<TArgs>(args)...},
                  _context{ctx}
            {
            }

            /*
             * get serialization context.
             * this is optional, but might be required for some specific serialization flows.
             */

            template <typename T>
            T& context() {
                return *getContext<true, T>(_context);
            }

            template <typename T>
            T* contextOrNull() {
                return getContext<false, T>(_context);
            }

            Adapter& adapter() & {
                return _adapter;
            }

            Adapter adapter() && {
                return std::move(_adapter);
            }

        protected:
            Adapter _adapter;
            Context& _context;
        };

        template <typename Adapter>
        class AdapterAndContextRef<Adapter, void> {
        public:
            static constexpr bool HasContext = false;
            using Config = typename Adapter::TConfig;

            template <typename ... TArgs>
            explicit AdapterAndContextRef(TArgs&& ... args)
                : _adapter{std::forward<TArgs>(args)...}
            {
            }

            template <typename T>
            T& context() {
                static_assert(std::is_void<T>::value, "Context is not defined (is void).");
            }

            template <typename T>
            T* contextOrNull() {
                return nullptr;
            }

            Adapter& adapter() & {
                return _adapter;
            }

            Adapter adapter() && {
                return std::move(_adapter);
            }

        protected:
            Adapter _adapter;
        };

/**
 * other helper meta-functions
 */

        template<typename T, template<typename...> class Template>
        struct IsSpecializationOf : std::false_type {
        };

        template<template<typename...> class Template, typename... Args>
        struct IsSpecializationOf<Template<Args...>, Template> : std::true_type {
        };

    }
}

#endif //BITSERY_DETAILS_SERIALIZATION_COMMON_H

