#ifndef CORE_TEST_HPP
#define CORE_TEST_HPP

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

namespace Core::Test {
    namespace Internal {
        struct Result final {
            int tests = 0;
            int successTests = 0;
        };
        using FileName = String32<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) {
            Error e = Error::NONE;
            FileName fileName;
            if(checkError(e, fileName.append(file))) {
                warn(file, line, e);
                return false;
            }
            Result* result = results.search(fileName);
            if(result == nullptr &&
               checkError(e, results.tryEmplace(result, fileName))) {
                warn(file, line, e);
                return false;
            }
            result->tests++;
            if(c) {
                result->successTests++;
                return true;
            }
            CORE_LOG_ERROR("#:# - 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) {
            Error e = Error::NONE;
            String32<2048> a;
            if(checkError(e, a.append(wanted))) {
                warn(file, line, e);
                return false;
            }
            String32<2048> b;
            if(checkError(e, b.append(actual))) {
                warn(file, line, e);
                return false;
            }
            return checkEqual(file, line, a, b);
        }

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

        template<int 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(int 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::Error::NONE, actual)
#define CORE_TEST_NULL(actual) CORE_TEST_EQUAL(true, actual == nullptr)
#define CORE_TEST_NOT_NULL(actual) CORE_TEST_EQUAL(true, 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