//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_SERIALIZER_H
#define BITSERY_SERIALIZER_H

#include "details/serialization_common.h"
#include "details/adapter_common.h"
#include <cassert>
#include <limits>

namespace bitsery {

    namespace details {
        template<typename TAdapter>
        class OutputAdapterBitPackingWrapper {
        public:

            static constexpr bool BitPackingEnabled = true;
            using TConfig = typename TAdapter::TConfig;
            using TValue = typename TAdapter::TValue;

            OutputAdapterBitPackingWrapper(TAdapter& adapter)
                : _wrapped{adapter}
            {
            }


            ~OutputAdapterBitPackingWrapper() {
                align();
            }

            template<size_t SIZE, typename T>
            void writeBytes(const T &v) {
                static_assert(std::is_integral<T>(), "");
                static_assert(sizeof(T) == SIZE, "");

                if (!_scratchBits) {
                    this->_wrapped.template writeBytes<SIZE,T>(v);
                } else {
                    using UT = typename std::make_unsigned<T>::type;
                    writeBitsInternal(reinterpret_cast<const UT &>(v), details::BitsSize<T>::value);
                }
            }

            template<size_t SIZE, typename T>
            void writeBuffer(const T *buf, size_t count) {
                static_assert(std::is_integral<T>(), "");
                static_assert(sizeof(T) == SIZE, "");
                if (!_scratchBits) {
                    this->_wrapped.template writeBuffer<SIZE,T>(buf, count);
                } else {
                    using UT = typename std::make_unsigned<T>::type;
                    //todo improve implementation
                    const auto end = buf + count;
                    for (auto it = buf; it != end; ++it)
                        writeBitsInternal(reinterpret_cast<const UT &>(*it), details::BitsSize<T>::value);
                }
            }

            template<typename T>
            void writeBits(const T &v, size_t bitsCount) {
                static_assert(std::is_integral<T>() && std::is_unsigned<T>(), "");
                assert(0 < bitsCount && bitsCount <= details::BitsSize<T>::value);
                assert(v <= (bitsCount < 64
                             ? (1ULL << bitsCount) - 1
                             : (1ULL << (bitsCount-1)) + ((1ULL << (bitsCount-1)) -1)));
                writeBitsInternal(v, bitsCount);
            }

            void align() {
                writeBitsInternal(UnsignedType{}, (details::BitsSize<UnsignedType>::value - _scratchBits) % 8);
            }

            void currentWritePos(size_t pos) {
                align();
                this->_wrapped.currentWritePos(pos);
            }

            size_t currentWritePos() const {
                return this->_wrapped.currentWritePos();
            }

            void flush() {
                align();
                this->_wrapped.flush();
            }

            size_t writtenBytesCount() const {
                return this->_wrapped.writtenBytesCount();
            }

        private:
            TAdapter& _wrapped;

            using UnsignedType = typename std::make_unsigned<typename TAdapter::TValue>::type;
            using ScratchType = typename details::ScratchType<UnsignedType>::type;
            static_assert(details::IsDefined<ScratchType>::value, "Underlying adapter value type is not supported");


            template<typename T>
            void writeBitsInternal(const T &v, size_t size) {
                constexpr size_t valueSize = details::BitsSize<UnsignedType>::value;
                T value = v;
                size_t bitsLeft = size;
                while (bitsLeft > 0) {
                    auto bits = (std::min)(bitsLeft, valueSize);
                    _scratch |= static_cast<ScratchType>( value ) << _scratchBits;
                    _scratchBits += bits;
                    if (_scratchBits >= valueSize) {
                        auto tmp = static_cast<UnsignedType>(_scratch & _MASK);
                        this->_wrapped.template writeBytes<sizeof(UnsignedType), UnsignedType >(tmp);
                        _scratch >>= valueSize;
                        _scratchBits -= valueSize;

                        value = static_cast<T>(value >> valueSize);
                    }
                    bitsLeft -= bits;
                }
            }

            //overload for TValue, for better performance
            void writeBitsInternal(const UnsignedType &v, size_t size) {
                if (size > 0) {
                    _scratch |= static_cast<ScratchType>( v ) << _scratchBits;
                    _scratchBits += size;
                    if (_scratchBits >= details::BitsSize<UnsignedType>::value) {
                        auto tmp = static_cast<UnsignedType>(_scratch & _MASK);
                        this->_wrapped.template writeBytes<sizeof(UnsignedType), UnsignedType>(tmp);
                        _scratch >>= details::BitsSize<UnsignedType>::value;
                        _scratchBits -= details::BitsSize<UnsignedType>::value;
                    }
                }
            }

            const UnsignedType _MASK = (std::numeric_limits<UnsignedType>::max)();
            ScratchType _scratch{};
            size_t _scratchBits{};
        };

    }

    template<typename TOutputAdapter, typename TContext = void>
    class Serializer: public details::AdapterAndContextRef<TOutputAdapter, TContext> {
    public:
        //helper type, that always returns bit-packing enabled type, useful inside serialize function when enabling bitpacking
        using BPEnabledType = Serializer<typename std::conditional<TOutputAdapter::BitPackingEnabled,
                TOutputAdapter,
                details::OutputAdapterBitPackingWrapper<TOutputAdapter>>::type, TContext>;
        using TConfig = typename TOutputAdapter::TConfig;

        using details::AdapterAndContextRef<TOutputAdapter, TContext>::AdapterAndContextRef;

        /*
         * object function
         */
        template<typename T>
        void object(const T &obj) {
            details::SerializeFunction<Serializer, T>::invoke(*this, const_cast<T& >(obj));
        }

        template<typename T, typename Fnc>
        void object(const T &obj, Fnc &&fnc) {
            fnc(*this, const_cast<T& >(obj));
        }

        /*
         * functionality, that enables simpler serialization syntax, by including additional header
         */

        template <typename... TArgs>
        Serializer &operator()(TArgs &&... args) {
            archive(std::forward<TArgs>(args)...);
            return *this;
        }

        /*
         * value overloads
         */

        template<size_t VSIZE, typename T>
        void value(const T &v) {
            static_assert(details::IsFundamentalType<T>::value, "Value must be integral, float or enum type.");
            using TValue = typename details::IntegralFromFundamental<T>::TValue;
            this->_adapter.template writeBytes<VSIZE>(reinterpret_cast<const TValue &>(v));
        }

        /*
         * enable bit-packing
         */
        template <typename Fnc>
        void enableBitPacking(Fnc&& fnc) {
            procEnableBitPacking(std::forward<Fnc>(fnc),
                std::integral_constant<bool, TOutputAdapter::BitPackingEnabled>{},
                std::integral_constant<bool, Serializer::HasContext>{});
        }

        /*
         * extension functions
         */

        template<typename T, typename Ext, typename Fnc>
        void ext(const T &obj, const Ext &extension, Fnc &&fnc) {
            static_assert(details::IsExtensionTraitsDefined<Ext, T>::value, "Please define ExtensionTraits");
            static_assert(traits::ExtensionTraits<Ext,T>::SupportLambdaOverload,
                          "extension doesn't support overload with lambda");
            extension.serialize(*this, obj, std::forward<Fnc>(fnc));
        }

        template<size_t VSIZE, typename T, typename Ext>
        void ext(const T &obj, const Ext &extension) {
            static_assert(details::IsExtensionTraitsDefined<Ext, T>::value, "Please define ExtensionTraits");
            static_assert(traits::ExtensionTraits<Ext,T>::SupportValueOverload,
                          "extension doesn't support overload with `value<N>`");
            using ExtVType = typename traits::ExtensionTraits<Ext, T>::TValue;
            using VType = typename std::conditional<std::is_void<ExtVType>::value, details::DummyType, ExtVType>::type;
            extension.serialize(*this, obj, [](Serializer& s, VType &v) { s.value<VSIZE>(v); });
        }

        template<typename T, typename Ext>
        void ext(const T &obj, const Ext &extension) {
            static_assert(details::IsExtensionTraitsDefined<Ext, T>::value, "Please define ExtensionTraits");
            static_assert(traits::ExtensionTraits<Ext,T>::SupportObjectOverload,
                          "extension doesn't support overload with `object`");
            using ExtVType = typename traits::ExtensionTraits<Ext, T>::TValue;
            using VType = typename std::conditional<std::is_void<ExtVType>::value, details::DummyType, ExtVType>::type;
            extension.serialize(*this, obj, [](Serializer& s, VType &v) { s.object(v); });
        }

        /*
         * boolValue
         */

        void boolValue(bool v) {
            procBoolValue(v, std::integral_constant<bool, TOutputAdapter::BitPackingEnabled>{});
        }

        /*
         * text overloads
         */

        template<size_t VSIZE, typename T>
        void text(const T &str, size_t maxSize) {
            static_assert(details::IsTextTraitsDefined<T>::value,
                          "Please define TextTraits or include from <bitsery/traits/...>");
            static_assert(traits::ContainerTraits<T>::isResizable,
                          "use text(const T&) overload without `maxSize` for static container");
            procText<VSIZE>(str, maxSize);
        }

        template<size_t VSIZE, typename T>
        void text(const T &str) {
            static_assert(details::IsTextTraitsDefined<T>::value,
                          "Please define TextTraits or include from <bitsery/traits/...>");
            static_assert(!traits::ContainerTraits<T>::isResizable,
                          "use text(const T&, size_t) overload with `maxSize` for dynamic containers");
            procText<VSIZE>(str, traits::ContainerTraits<T>::size(str));
        }

        /*
         * container overloads
         */

        //dynamic size containers

        template<typename T, typename Fnc>
        void container(const T &obj, size_t maxSize, Fnc &&fnc) {
            static_assert(details::IsContainerTraitsDefined<T>::value,
                          "Please define ContainerTraits or include from <bitsery/traits/...>");
            static_assert(traits::ContainerTraits<T>::isResizable,
                          "use container(const T&, Fnc) overload without `maxSize` for static containers");
            auto size = traits::ContainerTraits<T>::size(obj);
            (void)maxSize; // unused in release
            assert(size <= maxSize);
            details::writeSize(this->_adapter, size);
            procContainer(std::begin(obj), std::end(obj), std::forward<Fnc>(fnc));
        }

        template<size_t VSIZE, typename T>
        void container(const T &obj, size_t maxSize) {
            static_assert(details::IsContainerTraitsDefined<T>::value,
                          "Please define ContainerTraits or include from <bitsery/traits/...>");
            static_assert(traits::ContainerTraits<T>::isResizable,
                          "use container(const T&) overload without `maxSize` for static containers");
            static_assert(VSIZE > 0, "");
            auto size = traits::ContainerTraits<T>::size(obj);
            (void)maxSize; // unused in release
            assert(size <= maxSize);
            details::writeSize(this->_adapter, size);

            procContainer<VSIZE>(std::begin(obj), std::end(obj), std::integral_constant<bool, traits::ContainerTraits<T>::isContiguous>{});
        }

        template<typename T>
        void container(const T &obj, size_t maxSize) {
            static_assert(details::IsContainerTraitsDefined<T>::value,
                          "Please define ContainerTraits or include from <bitsery/traits/...>");
            static_assert(traits::ContainerTraits<T>::isResizable,
                          "use container(const T&) overload without `maxSize` for static containers");
            auto size = traits::ContainerTraits<T>::size(obj);
            (void)maxSize; // unused in release
            assert(size <= maxSize);
            details::writeSize(this->_adapter, size);
            procContainer(std::begin(obj), std::end(obj));
        }

        //fixed size containers

        template<typename T, typename Fnc, typename std::enable_if<!std::is_integral<Fnc>::value>::type * = nullptr>
        void container(const T &obj, Fnc &&fnc) {
            static_assert(details::IsContainerTraitsDefined<T>::value,
                          "Please define ContainerTraits or include from <bitsery/traits/...>");
            static_assert(!traits::ContainerTraits<T>::isResizable,
                          "use container(const T&, size_t, Fnc) overload with `maxSize` for dynamic containers");
            procContainer(std::begin(obj), std::end(obj), std::forward<Fnc>(fnc));
        }

        template<size_t VSIZE, typename T>
        void container(const T &obj) {
            static_assert(details::IsContainerTraitsDefined<T>::value,
                          "Please define ContainerTraits or include from <bitsery/traits/...>");
            static_assert(!traits::ContainerTraits<T>::isResizable,
                          "use container(const T&, size_t) overload with `maxSize` for dynamic containers");
            static_assert(VSIZE > 0, "");
            procContainer<VSIZE>(std::begin(obj), std::end(obj), std::integral_constant<bool, traits::ContainerTraits<T>::isContiguous>{});
        }

        template<typename T>
        void container(const T &obj) {
            static_assert(details::IsContainerTraitsDefined<T>::value,
                          "Please define ContainerTraits or include from <bitsery/traits/...>");
            static_assert(!traits::ContainerTraits<T>::isResizable,
                          "use container(const T&, size_t) overload with `maxSize` for dynamic containers");
            procContainer(std::begin(obj), std::end(obj));
        }

        //overloads for functions with explicit type size

        template<typename T>
        void value1b(T &&v) { value<1>(std::forward<T>(v)); }

        template<typename T>
        void value2b(T &&v) { value<2>(std::forward<T>(v)); }

        template<typename T>
        void value4b(T &&v) { value<4>(std::forward<T>(v)); }

        template<typename T>
        void value8b(T &&v) { value<8>(std::forward<T>(v)); }

        template<typename T>
        void value16b(T &&v) { value<16>(std::forward<T>(v)); }

        template<typename T, typename Ext>
        void ext1b(const T &v, Ext &&extension) { ext<1, T, Ext>(v, std::forward<Ext>(extension)); }

        template<typename T, typename Ext>
        void ext2b(const T &v, Ext &&extension) { ext<2, T, Ext>(v, std::forward<Ext>(extension)); }

        template<typename T, typename Ext>
        void ext4b(const T &v, Ext &&extension) { ext<4, T, Ext>(v, std::forward<Ext>(extension)); }

        template<typename T, typename Ext>
        void ext8b(const T &v, Ext &&extension) { ext<8, T, Ext>(v, std::forward<Ext>(extension)); }

        template<typename T, typename Ext>
        void ext16b(const T &v, Ext &&extension) { ext<16, T, Ext>(v, std::forward<Ext>(extension)); }

        template<typename T>
        void text1b(const T &str, size_t maxSize) { text<1>(str, maxSize); }

        template<typename T>
        void text2b(const T &str, size_t maxSize) { text<2>(str, maxSize); }

        template<typename T>
        void text4b(const T &str, size_t maxSize) { text<4>(str, maxSize); }

        template<typename T>
        void text1b(const T &str) { text<1>(str); }

        template<typename T>
        void text2b(const T &str) { text<2>(str); }

        template<typename T>
        void text4b(const T &str) { text<4>(str); }

        template<typename T>
        void container1b(T &&obj, size_t maxSize) { container<1>(std::forward<T>(obj), maxSize); }

        template<typename T>
        void container2b(T &&obj, size_t maxSize) { container<2>(std::forward<T>(obj), maxSize); }

        template<typename T>
        void container4b(T &&obj, size_t maxSize) { container<4>(std::forward<T>(obj), maxSize); }

        template<typename T>
        void container8b(T &&obj, size_t maxSize) { container<8>(std::forward<T>(obj), maxSize); }

        template<typename T>
        void container16b(T &&obj, size_t maxSize) { container<16>(std::forward<T>(obj), maxSize); }

        template<typename T>
        void container1b(T &&obj) { container<1>(std::forward<T>(obj)); }

        template<typename T>
        void container2b(T &&obj) { container<2>(std::forward<T>(obj)); }

        template<typename T>
        void container4b(T &&obj) { container<4>(std::forward<T>(obj)); }

        template<typename T>
        void container8b(T &&obj) { container<8>(std::forward<T>(obj)); }

        template<typename T>
        void container16b(T &&obj) { container<16>(std::forward<T>(obj)); }


    private:

        //process value types
        //false_type means that we must process all elements individually
        template<size_t VSIZE, typename It>
        void procContainer(It first, It last, std::false_type) {
            for (; first != last; ++first)
                value<VSIZE>(*first);
        }

        //process value types
        //true_type means, that we can copy whole buffer
        template<size_t VSIZE, typename It>
        void procContainer(It first, It last, std::true_type) {
            using TValue = typename std::decay<decltype(*first)>::type;
            using TIntegral = typename details::IntegralFromFundamental<TValue>::TValue;
			if (first != last)
				this->_adapter.template writeBuffer<VSIZE>(reinterpret_cast<const TIntegral*>(&(*first)),
                                                    static_cast<size_t>(std::distance(first, last)));
        }

        //process by calling functions
        template<typename It, typename Fnc>
        void procContainer(It first, It last, Fnc fnc) {
            using TValue = typename std::decay<decltype(*first)>::type;
            for (; first != last; ++first) {
                fnc(*this, const_cast<TValue&>(*first));
            }
        }

        //process text,
        template<size_t VSIZE, typename T>
        void procText(const T& str, size_t maxSize) {
            const size_t length = traits::TextTraits<T>::length(str);
            (void)maxSize; // unused in release
            assert((length + (traits::TextTraits<T>::addNUL ? 1u : 0u)) <= maxSize);
            details::writeSize(this->_adapter, length);
            auto begin = std::begin(str);
            using diff_t = typename std::iterator_traits<decltype(begin)>::difference_type;
            procContainer<VSIZE>(begin, std::next(begin, static_cast<diff_t>(length)), std::integral_constant<bool, traits::ContainerTraits<T>::isContiguous>{});
        }

        //process object types
        template<typename It>
        void procContainer(It first, It last) {
            for (; first != last; ++first)
                object(*first);
        }

        //proc bool writing bit or byte, depending on if BitPackingEnabled or not
        void procBoolValue(bool v, std::true_type) {
            this->_adapter.writeBits(static_cast<unsigned char>(v ? 1 : 0), 1);
        }

        void procBoolValue(bool v, std::false_type) {
            this->_adapter.template writeBytes<1>(static_cast<unsigned char>(v ? 1 : 0));
        }

        //enable bit-packing or do nothing if it is already enabled
        template <typename Fnc, typename HasContext>
        void procEnableBitPacking(const Fnc& fnc, std::true_type, HasContext) {
            fnc(*this);
        }

        template <typename Fnc>
        void procEnableBitPacking(const Fnc& fnc, std::false_type, std::true_type) {
            //create serializer using bitpacking wrapper
            BPEnabledType ser{this->_context, this->_adapter};
            fnc(ser);
        }

        template <typename Fnc>
        void procEnableBitPacking(const Fnc& fnc, std::false_type, std::false_type) {
            //create serializer using bitpacking wrapper
            BPEnabledType ser{this->_adapter};
            fnc(ser);
        }

        //these are dummy functions for extensions that have TValue = void
        void object(const details::DummyType&) {

        }

        template <size_t VSIZE>
        void value(const details::DummyType&) {

        }

        template<typename T, typename ... TArgs>
        void archive(T &&head, TArgs &&... tail) {
            //serialize object
            details::BriefSyntaxFunction<Serializer, T>::invoke(*this, std::forward<T>(head));
            //expand other elements
            archive(std::forward<TArgs>(tail)...);
        }
        //dummy function, that stops archive variadic arguments expansion
        void archive() {
        }

    };

    //helper function that set ups all the basic steps and after serialziation returns serialized bytes count
    template <typename OutputAdapter, typename T>
    size_t quickSerialization(OutputAdapter adapter, const T& value) {
        Serializer<OutputAdapter> ser{std::move(adapter)};
        ser.object(value);
        ser.adapter().flush();
        return ser.adapter().writtenBytesCount();
    }

    template <typename Context, typename OutputAdapter, typename T>
    size_t quickSerialization(Context& ctx, OutputAdapter adapter, const T& value) {
        Serializer<OutputAdapter, Context> ser{ctx, std::move(adapter)};
        ser.object(value);
        ser.adapter().flush();
        return ser.adapter().writtenBytesCount();
    }

}
#endif //BITSERY_SERIALIZER_H
