#pragma once

#include <Core/Common/CoreTypes.h>
#include <Juliet.h>

#include <algorithm>
#include <source_location>

namespace Juliet
{

#if JULIET_DEBUG
#define JULIET_ASSERT_INTERNAL(expression, message)                                                                    \
    __pragma(warning(push)) __pragma(warning(disable : 4127)) __pragma(warning(disable : 4548)) do                     \
    {                                                                                                                  \
        if (!(expression))                                                                                             \
        {                                                                                                              \
            Juliet::JulietAssert(#expression, message);                                                                \
        }                                                                                                              \
    }                                                                                                                  \
    while (0)                                                                                                          \
    __pragma(warning(pop))

#define AssertHR(hr_expression, message)                                                                               \
    do                                                                                                                 \
    {                                                                                                                  \
        long hr_val = (hr_expression);                                                                                 \
        if (hr_val < 0)                                                                                                \
        {                                                                                                              \
            Juliet::JulietAssert(#hr_expression, message, std::source_location::current(), hr_val);                    \
        }                                                                                                              \
    }                                                                                                                  \
    while (0)

#define GET_ASSERT_MACRO(_1, _2, NAME, ...) NAME
#define Assert(...) GET_ASSERT_MACRO(__VA_ARGS__, JULIET_ASSERT_INTERNAL, JULIET_ASSERT_NO_MSG)(__VA_ARGS__)
#define JULIET_ASSERT_NO_MSG(expression) JULIET_ASSERT_INTERNAL(expression, "No additional information provided.")

#define Unimplemented()                                                                                                \
    do                                                                                                                 \
    {                                                                                                                  \
        Juliet::JulietAssert("UNIMPLEMENTED", "This code path is not yet functional.");                                \
    }                                                                                                                  \
    while (0)

#else
#define Assert(...) ((void)0)
#define AssertHR(hr_expression, message) ((void)(hr_expression))
#define Unimplemented() ((void)0)
#endif

    JULIET_API extern void JulietAssert(const char* expression, const char* message,
                                        std::source_location location = std::source_location::current(), long handleResult = 0);

#define ZeroStruct(structInstance) ZeroSize(sizeof(structInstance), &(structInstance))
#define ZeroArray(array) ZeroSize(sizeof((array)), (array))
#define ZeroDynArray(Count, Pointer) ZeroSize((Count) * sizeof((Pointer)[0]), Pointer)
    inline void ZeroSize(size_t size, void* ptr)
    {
        auto Byte = (uint8*)ptr;
        while (size--)
        {
            *Byte++ = 0;
        }
    }

    template <class Function>
    class DeferredFunction
    {
      public:
        explicit DeferredFunction(const Function& otherFct) noexcept
          : Callback(otherFct)
        {
        }
        explicit DeferredFunction(Function&& otherFct) noexcept
          : Callback(std::move(otherFct))
        {
        }

        ~DeferredFunction() noexcept { Callback(); }

        DeferredFunction(const DeferredFunction&)  = delete;
        DeferredFunction(const DeferredFunction&&) = delete;
        void operator=(const DeferredFunction&)    = delete;
        void operator=(DeferredFunction&&)         = delete;

      private:
        Function Callback;
    };

    template <class Function>
    auto Defer(Function&& fct) noexcept
    {
        return DeferredFunction<std::decay_t<Function>>{ std::forward<Function>(fct) };
    }

    inline bool IsValid(ByteBuffer buffer)
    {
        return buffer.Size > 0 && buffer.Data;
    }

    extern JULIET_API void Free(ByteBuffer& buffer);
} // namespace Juliet
