#pragma once

#include "types.h"
#include "compiler.h"
#include <util/generic/singleton.h>

#define Y_CPU_ID_ENUMERATE(F) \
    F(SSE)                    \
    F(SSE2)                   \
    F(SSE3)                   \
    F(SSSE3)                  \
    F(SSE41)                  \
    F(SSE42)                  \
    F(F16C)                   \
    F(POPCNT)                 \
    F(BMI1)                   \
    F(BMI2)                   \
    F(PCLMUL)                 \
    F(AES)                    \
    F(AVX)                    \
    F(FMA)                    \
    F(AVX2)                   \
    F(AVX512F)                \
    F(AVX512DQ)               \
    F(AVX512IFMA)             \
    F(AVX512PF)               \
    F(AVX512ER)               \
    F(AVX512CD)               \
    F(AVX512BW)               \
    F(AVX512VL)               \
    F(AVX512VBMI)             \
    F(PREFETCHWT1)            \
    F(SHA)                    \
    F(ADX)                    \
    F(RDRAND)                 \
    F(RDSEED)                 \
    F(PCOMMIT)                \
    F(RDTSCP)                 \
    F(CLFLUSHOPT)             \
    F(CLWB)                   \
    F(XSAVE)                  \
    F(OSXSAVE)

#define Y_CPU_ID_ENUMERATE_OUTLINED_CACHED_DEFINE(F) \
    F(F16C)                   \
    F(BMI1)                   \
    F(BMI2)                   \
    F(PCLMUL)                 \
    F(AES)                    \
    F(AVX)                    \
    F(FMA)                    \
    F(AVX2)                   \
    F(AVX512F)                \
    F(AVX512DQ)               \
    F(AVX512IFMA)             \
    F(AVX512PF)               \
    F(AVX512ER)               \
    F(AVX512CD)               \
    F(AVX512BW)               \
    F(AVX512VL)               \
    F(AVX512VBMI)             \
    F(PREFETCHWT1)            \
    F(SHA)                    \
    F(ADX)                    \
    F(RDRAND)                 \
    F(RDSEED)                 \
    F(PCOMMIT)                \
    F(RDTSCP)                 \
    F(CLFLUSHOPT)             \
    F(CLWB)                   \
    F(XSAVE)                  \
    F(OSXSAVE)

namespace NX86 {
    /**
     * returns false on non-x86 platforms
     */
    bool CpuId(ui32 op, ui32 res[4]) noexcept;
    bool CpuId(ui32 op, ui32 subOp, ui32 res[4]) noexcept;

#define Y_DEF_NAME(X) Y_CONST_FUNCTION bool Have##X() noexcept;
    Y_CPU_ID_ENUMERATE(Y_DEF_NAME)
#undef Y_DEF_NAME

#define Y_DEF_NAME(X) Y_CONST_FUNCTION bool CachedHave##X() noexcept;
    Y_CPU_ID_ENUMERATE_OUTLINED_CACHED_DEFINE(Y_DEF_NAME)
#undef Y_DEF_NAME

struct TFlagsCache {
#define Y_DEF_NAME(X) const bool Have##X##_ = NX86::Have##X();
    Y_CPU_ID_ENUMERATE(Y_DEF_NAME)
#undef Y_DEF_NAME
};

#define Y_LOOKUP_CPU_ID_IMPL(X) return SingletonWithPriority<TFlagsCache, 0>()->Have##X##_;

inline bool CachedHaveSSE() noexcept {
#ifdef _sse_
    return true;
#else
    Y_LOOKUP_CPU_ID_IMPL(SSE)
#endif
}


inline bool CachedHaveSSE2() noexcept {
#ifdef _sse2_
    return true;
#else
    Y_LOOKUP_CPU_ID_IMPL(SSE2)
#endif
}

inline bool CachedHaveSSE3() noexcept {
#ifdef _sse3_
    return true;
#else
    Y_LOOKUP_CPU_ID_IMPL(SSE3)
#endif
}

inline bool CachedHaveSSSE3() noexcept {
#ifdef _ssse3_
    return true;
#else
    Y_LOOKUP_CPU_ID_IMPL(SSSE3)
#endif
}

inline bool CachedHaveSSE41() noexcept {
#ifdef _sse4_1_
    return true;
#else
    Y_LOOKUP_CPU_ID_IMPL(SSE41)
#endif
}

inline bool CachedHaveSSE42() noexcept {
#ifdef _sse4_2_
    return true;
#else
    Y_LOOKUP_CPU_ID_IMPL(SSE42)
#endif
}

inline bool CachedHavePOPCNT() noexcept {
#ifdef _popcnt_
    return true;
#else
    Y_LOOKUP_CPU_ID_IMPL(POPCNT)
#endif
}

#undef Y_LOOKUP_CPU_ID_IMPL


}

const char* CpuBrand(ui32 store[12]) noexcept;
