#ifndef CORE_TEST_HPP
#define CORE_TEST_HPP

#include "core/data/HashMap.hpp"
#include "core/math/Vector.hpp"
#include "core/utils/HashedString.hpp"
#include "core/utils/Logger.hpp"

namespace Core::Test {
    namespace Internal {
        struct Result final {
            int tests = 0;
            int successTests = 0;
        };
        using FileName = HashedString<256>;
        extern HashMap<FileName, Result> results;

        void warn(const char* file, int line, Error e);

        template<typename T>
        bool check(const char* file, int line, const T& wanted, const T& actual,
                   bool c) {
            file = Logger::getFileName(file);
            Error e = ErrorCode::NONE;
            FileName fileName(file);
            Result* result = results.search(fileName);
            if(result == nullptr && !results.tryEmplace(result, fileName)) {
                warn(file, line, e);
                return false;
            }
            result->tests++;
            if(c) {
                result->successTests++;
                return true;
            }
            Core::Logger::log(Core::Logger::COLOR_RED,
                              "#:# - expected '#' got '#'", fileName, line,
                              wanted, actual);
            return false;
        }

        template<typename T>
        bool checkEqual(const char* file, int line, const T& wanted,
                        const T& actual) {
            return check(file, line, wanted, actual, wanted == actual);
        }

        template<typename A, typename B>
        bool checkString(const char* file, int line, const A& wanted,
                         const B& actual) {
            ArrayString<2048> a;
            a.append(wanted);
            ArrayString<2048> b;
            b.append(actual);
            return checkEqual(file, line, a, b);
        }

        bool checkFloat(const char* file, int line, float wanted, float actual,
                        float error);

        template<size_t N, typename T>
        bool checkVector(const char* file, int line,
                         const Core::Vector<N, T>& wanted,
                         const Core::Vector<N, T>& actual, float error) {
            bool result = true;
            for(size_t i = 0; i < N; i++) {
                result &= checkFloat(file, line, static_cast<float>(wanted[i]),
                                     static_cast<float>(actual[i]), error);
            }
            return result;
        }
    }
    void finalize();
}

#define CORE_TEST_EQUAL(wanted, actual)                                        \
    Core::Test::Internal::checkEqual<Core::RemoveReference<decltype(actual)>>( \
        __FILE__, __LINE__, wanted, actual)
#define CORE_TEST_STRING(wanted, actual)                                       \
    Core::Test::Internal::checkString(__FILE__, __LINE__, wanted, actual)
#define CORE_TEST_FALSE(actual) CORE_TEST_EQUAL(false, actual)
#define CORE_TEST_TRUE(actual) CORE_TEST_EQUAL(true, actual)
#define CORE_TEST_ERROR(actual) CORE_TEST_EQUAL(Core::ErrorCode::NONE, actual)
#define CORE_TEST_NULL(actual) CORE_TEST_EQUAL(true, actual == nullptr)
// double check to make the null check clear for the compiler
#define CORE_TEST_NOT_NULL(actual)                                             \
    (CORE_TEST_EQUAL(true, (actual) != nullptr) && (actual) != nullptr)
#define CORE_TEST_FLOAT(wanted, actual, error)                                 \
    Core::Test::Internal::checkFloat(__FILE__, __LINE__, wanted, actual, error)
#define CORE_TEST_VECTOR(wanted, actual)                                       \
    Core::Test::Internal::checkVector(__FILE__, __LINE__, wanted, actual,      \
                                      0.0001f)

#endif