#ifndef CORE_TEST_H
#define CORE_TEST_H

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

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

        template<typename T>
        bool checkEqual(const char* file, int line, const T& wanted,
                        const T& actual) {
            FileName fileName;
            if(fileName.append(file)) {
                CORE_LOG_WARNING("cannot append file name: #", file);
                return false;
            }
            Result* result = results.search(fileName);
            if(result == nullptr) {
                result = results.tryEmplace(fileName);
                if(result == nullptr) {
                    CORE_LOG_WARNING("cannot add test result for #", file);
                    return false;
                }
            }
            result->tests++;
            if(wanted == actual) {
                result->successTests++;
                return true;
            }
            CORE_LOG_ERROR("#:# - expected '#' got '#'", fileName, line, wanted,
                           actual)
            return false;
        }

        template<typename A, typename B>
        bool checkString(const char* file, int line, const A& wanted,
                         const B& actual) {
            ArrayString<2048> a;
            if(a.append(wanted)) {
                CORE_LOG_WARNING("too small buffer");
                return false;
            }
            ArrayString<2048> b;
            if(b.append(actual)) {
                CORE_LOG_WARNING("too small buffer");
                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) &&
                         result;
            }
            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_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