// SPDX-License-Identifier: MPL-2.0

//! \file env_detect.c
//! \brief Detects the compilation environment for standards, extensions, endianness, etc
//! \copyright
//! Do NOT modify or remove this copyright and license
//!
//! Copyright (c) 2024-2026 Seagate Technology LLC and/or its Affiliates, All Rights Reserved
//!
//! This software is subject to the terms of the Mozilla Public License, v. 2.0.
//! If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.

#include "env_detect.h"
#include "io_utils.h"
#include "memory_safety.h"
#include "string_utils.h"
#include "type_conversion.h"

#include <string.h>

void print_Architecture(eArchitecture arch)
{
    switch (arch)
    {
    case OPENSEA_ARCH_X86:
        print_str("X86");
        break;
    case OPENSEA_ARCH_X86_64:
        print_str("X86_64");
        break;
    case OPENSEA_ARCH_ARM:
        print_str("ARM");
        break;
    case OPENSEA_ARCH_ARM64:
        print_str("ARM64");
        break;
    case OPENSEA_ARCH_POWERPC:
        print_str("PPC");
        break;
    case OPENSEA_ARCH_POWERPC64:
        print_str("PPC64");
        break;
    case OPENSEA_ARCH_IA_64:
        print_str("IA64");
        break;
    case OPENSEA_ARCH_SPARC:
        print_str("SPARC");
        break;
    case OPENSEA_ARCH_ALPHA:
        print_str("Alpha");
        break;
    case OPENSEA_ARCH_SYSTEMZ:
        print_str("SystemZ");
        break;
    case OPENSEA_ARCH_MIPS:
        print_str("MIPS");
        break;
    case OPENSEA_ARCH_UNKNOWN:
    case OPENSEA_ARCH_RESERVED:
        print_str("Unknown Architecture");
        break;
    }
}

void print_Endianness(eEndianness endian, bool shortPrint)
{
    switch (endian)
    {
    case OPENSEA_LITTLE_ENDIAN:
        if (shortPrint)
        {
            print_str("LSB");
        }
        else
        {
            print_str("Little Endian");
        }
        break;
    case OPENSEA_BIG_ENDIAN:
        if (shortPrint)
        {
            print_str("MSB");
        }
        else
        {
            print_str("Big Endian");
        }
        break;
    case OPENSEA_LITTLE_WORD_ENDIAN:
        if (shortPrint)
        {
            print_str("LSW");
        }
        else
        {
            print_str("Little Endian (Word)");
        }
        break;
    case OPENSEA_BIG_WORD_ENDIAN:
        if (shortPrint)
        {
            print_str("MSW");
        }
        else
        {
            print_str("Big Endian (Word)");
        }
        break;
    case OPENSEA_UNKNOWN_ENDIAN:
        if (shortPrint)
        {
            print_str("???");
        }
        else
        {
            print_str("Unknown Endianness");
        }
        break;
    }
}

void print_OS_Type(eOSType osType)
{
    switch (osType)
    {
    case OS_WINDOWS:
        print_str("Windows");
        break;
    case OS_LINUX:
        print_str("Linux");
        break;
    case OS_FREEBSD:
        print_str("FreeBSD");
        break;
    case OS_SOLARIS:
        print_str("Solaris");
        break;
    case OS_MACOSX:
        print_str("Mac OSX");
        break;
    case OS_AIX:
        print_str("AIX");
        break;
    case OS_TRU64:
        print_str("TRU64");
        break;
    case OS_OPENBSD:
        print_str("OpenBSD");
        break;
    case OS_NETBSD:
        print_str("NetBSD");
        break;
    case OS_DRAGONFLYBSD:
        print_str("Dragonfly BSD");
        break;
    case OS_HPUX:
        print_str("HP UX");
        break;
    case OS_ESX:
        print_str("VMWare ESXi");
        break;
    case OS_UEFI:
        print_str("UEFI");
        break;
    case OS_UNKNOWN:
    default:
        print_str("Unknown OS");
        break;
    }
}

void print_OS_Version(ptrOSVersionNumber versionNumber)
{
    // format is something similar to major.minor.rev
    switch (versionNumber->osVersioningIdentifier)
    {
    case OS_WINDOWS:
        printf("%" PRIu16 ".%" PRIu16 ".%" PRIu16 "", versionNumber->versionType.windowsVersion.majorVersion,
               versionNumber->versionType.windowsVersion.minorVersion,
               versionNumber->versionType.windowsVersion.buildNumber);
        break;
    case OS_LINUX:
        printf("%" PRIu16 ".%" PRIu16 ".%" PRIu16 "-%" PRIu16 "", versionNumber->versionType.linuxVersion.kernelVersion,
               versionNumber->versionType.linuxVersion.majorVersion,
               versionNumber->versionType.linuxVersion.minorVersion,
               versionNumber->versionType.linuxVersion.securityAndBugFixesNumber);
        break;
    case OS_FREEBSD:
        printf("%" PRIu16 ".%" PRIu16 "", versionNumber->versionType.freeBSDVersion.majorVersion,
               versionNumber->versionType.freeBSDVersion.minorVersion);
        break;
    case OS_SOLARIS:
        printf("%" PRIu16 ".%" PRIu16 ".%" PRIu16 "", versionNumber->versionType.solarisVersion.sunOSMajorVersion,
               versionNumber->versionType.solarisVersion.sunOSMinorVersion,
               versionNumber->versionType.solarisVersion.sunOSRevision);
        if (versionNumber->versionType.solarisVersion.solarisMajorVersion > 0)
        {
            printf("-%" PRIu16 ".%" PRIu16 ".%" PRIu16 "",
                   versionNumber->versionType.solarisVersion.solarisMajorVersion,
                   versionNumber->versionType.solarisVersion.solarisMinorVersion,
                   versionNumber->versionType.solarisVersion.solarisRevision);
        }
        break;
    case OS_MACOSX:
        printf("%" PRIu16 ".%" PRIu16 ".%" PRIu16 "", versionNumber->versionType.macOSVersion.majorVersion,
               versionNumber->versionType.macOSVersion.minorVersion, versionNumber->versionType.macOSVersion.revision);
        break;
    case OS_AIX:
        printf("%" PRIu16 ".%" PRIu16 "", versionNumber->versionType.aixVersion.majorVersion,
               versionNumber->versionType.aixVersion.minorVersion);
        break;
    case OS_OPENBSD:
        printf("%" PRIu16 ".%" PRIu16 "", versionNumber->versionType.openBSDVersion.majorVersion,
               versionNumber->versionType.openBSDVersion.minorVersion);
        break;
    case OS_NETBSD:
        printf("%" PRIu16 ".%" PRIu16 ".%" PRIu16 "", versionNumber->versionType.netBSDVersion.majorVersion,
               versionNumber->versionType.netBSDVersion.minorVersion,
               versionNumber->versionType.netBSDVersion.revision);
        break;
    case OS_DRAGONFLYBSD:
        printf("%" PRIu16 ".%" PRIu16 "", versionNumber->versionType.dragonflyVersion.majorVersion,
               versionNumber->versionType.dragonflyVersion.minorVersion);
        break;
    case OS_HPUX:
        printf("%" PRIu16 ".%" PRIu16 "", versionNumber->versionType.hpuxVersion.majorVersion,
               versionNumber->versionType.hpuxVersion.minorVersion);
        break;
    case OS_TRU64:
        printf("%" PRIu16 ".%" PRIu16 "", versionNumber->versionType.tru64Version.majorVersion,
               versionNumber->versionType.tru64Version.minorVersion);
        break;
    case OS_ESX:
        printf("%" PRIu16 ".%" PRIu16 ".%" PRIu16 "", versionNumber->versionType.esxiVersion.majorVersion,
               versionNumber->versionType.esxiVersion.minorVersion, versionNumber->versionType.esxiVersion.revision);
        break;
    case OS_UEFI:
        printf("%" PRIu16 ".%" PRIu16 "", versionNumber->versionType.uefiVersion.majorVersion,
               versionNumber->versionType.uefiVersion.minorVersion);
        break;
    case OS_UNKNOWN:
    default:
        print_str("Unknown OS Version");
        break;
    }
}

eArchitecture get_Compiled_Architecture(void)
{
// NOTE: Removed compiler checks since some reuse the same definitions for the
// same CPU type in order to simplify this code more - TJE
#if defined __alpha__ || defined _M_ALPHA
    return OPENSEA_ARCH_ALPHA;
#elif defined __amd64__ || defined __x86_64__ || defined _M_X64 || defined _M_AMD64 || defined(MDE_CPU_X64)
    // NOTE: MSVC will defined _M_X64 for _M_ARM64EC mode as well (arm
    // compatibility mode)...not currently checking this since this is
    //       not needed. We already natively support ARM and AARCH64, so there
    //       is no need to compile this code in this compatibility mode.-TJE
    return OPENSEA_ARCH_X86_64;
#elif defined __arm__ || defined __thumb__ || defined _M_ARM || defined _M_ARMT || defined(MDE_CPU_ARM)
    return OPENSEA_ARCH_ARM;
#elif defined __aarch64__ || defined(MDE_CPU_AARCH64) || defined(_M_ARM64)
    return OPENSEA_ARCH_ARM64;
#elif defined __i386__ || defined __i486__ || defined __i586__ || defined __i686__ ||                                  \
    (defined __MINGW32__ && defined _X86_) || defined _M_IX86 || defined __i386 || defined __THW_INTEL__ ||            \
    defined(MDE_CPU_IA32)
    return OPENSEA_ARCH_X86;
#elif defined __ia64__ || defined __IA64__ || defined _M_IA64 || defined(MDE_CPU_IPF)
    return OPENSEA_ARCH_IA_64;
#elif defined __powerpc64__ || defined __PPC64__ || defined __ppc64__ || defined _ARCH_PPC64 || defined _ARCH_PPC64
    return OPENSEA_ARCH_POWERPC64;
#elif defined __powerpc__ || defined __POWERPC__ || defined __PPC__ || defined __ppc__ || defined _ARCH_PPC ||         \
    defined   _M_PPC || defined _ARCH_PPC
    return OPENSEA_ARCH_POWERPC;
#elif defined __sparc__ || defined __sparc
    return OPENSEA_ARCH_SPARC;
#elif defined __s390__ || defined __s390x__ || defined __zarch__ || defined __370__ || defined __THW_370__
    return OPENSEA_ARCH_SYSTEMZ;
#elif defined __mips__
    return OPENSEA_ARCH_MIPS;
#else
    return OPENSEA_ARCH_UNKNOWN;
#endif
}

// temporarily disable the unused function warning.
// The reason for this is because in some situations this function is used and
// in others it won't be. rather than making a complicated ifdef around it,
// disabling the warning for now-TJE
#if IS_CLANG_VERSION(1, 0)
// clang specific because behavior can differ even with the GCC diagnostic being
// "compatible" https
// ://clang.llvm.org/docs/UsersManual.html#controlling-diagnostics-via-pragmas
#    pragma clang diagnostic push
#    pragma clang diagnostic ignored "-Wunused-function"
#elif IS_GCC_VERSION(3, 0)
// temporarily disable the warning for unused function
#    pragma GCC diagnostic push
#    pragma GCC diagnostic ignored "-Wunused-function"
#endif

// https://sourceforge.net/p/predef/wiki/Endianness/
static eEndianness calculate_Endianness(void)
{
    static eEndianness endian = OPENSEA_UNKNOWN_ENDIAN; // using static so that it should only need to
                                                        // run this code once...not that it takes a long
                                                        // time, but this may help optimise this.
    if (endian == OPENSEA_UNKNOWN_ENDIAN)
    {
        union
        {
            uint32_t value;
            uint8_t  data[sizeof(uint32_t)];
        } number;

        number.data[0] = 0x00;
        number.data[1] = 0x01;
        number.data[2] = 0x02;
        number.data[3] = 0x03;

        switch (number.value)
        {
        case UINT32_C(0x00010203):
            endian = OPENSEA_BIG_ENDIAN;
            break;
        case UINT32_C(0x03020100):
            endian = OPENSEA_LITTLE_ENDIAN;
            break;
        case UINT32_C(0x02030001):
            endian = OPENSEA_BIG_WORD_ENDIAN;
            break;
        case UINT32_C(0x01000302):
            endian = OPENSEA_LITTLE_WORD_ENDIAN;
            break;
        default:
            endian = OPENSEA_UNKNOWN_ENDIAN;
            break;
        }
    }
    return endian;
}

// reenable the unused function warning
#if IS_CLANG_VERSION(1, 0)
#    pragma clang diagnostic pop
#elif IS_GCC_VERSION(3, 0)
#    pragma GCC diagnostic pop
#endif

// MDE_CPU_EBC should always calculate the endianness for EFI byte code since we
// do not know the exact endianness through other means
eEndianness get_Compiled_Endianness(void)
{
#if defined(ENV_BIG_ENDIAN)
    return OPENSEA_BIG_ENDIAN;
#elif defined(ENV_LITTLE_ENDIAN)
    return OPENSEA_LITTLE_ENDIAN;
#else
    return calculate_Endianness();
#endif
}

eReturnValues get_Compiler_Info(eCompiler* compilerUsed, ptrCompilerVersion compilerVersionInfo)
{
    eReturnValues ret = SUCCESS; // should always return this unless we don't
                                 // find a compiler we support.
    // make sure we set unknown and clear out version info before we continue.
    *compilerUsed = OPENSEA_COMPILER_UNKNOWN;
    safe_memset(compilerVersionInfo, sizeof(compilerVersion), 0, sizeof(compilerVersion));
    // Time to start checking predefined compiler macros
#if defined _MSC_VER
    // Microsoft Visual C/C++ compiler (code is written to use the _MSC_FULL_VER
    // from 2003 and later and we only really support 2013 and higher due to C99
    // usage)-TJE
    *compilerUsed = OPENSEA_COMPILER_MICROSOFT_VISUAL_C_CPP;
#    define MS_VERSION_STRING_LENGTH 10
    DECLARE_ZERO_INIT_ARRAY(char, msVersion, MS_VERSION_STRING_LENGTH);
    snprintf_err_handle(msVersion, MS_VERSION_STRING_LENGTH, "%u", _MSC_FULL_VER);
    DECLARE_ZERO_INIT_ARRAY(char, msMajor, 3);
    DECLARE_ZERO_INIT_ARRAY(char, msMinor, 3);
    DECLARE_ZERO_INIT_ARRAY(char, msPatch, 6);
    snprintf_err_handle(msMajor, 3, "%.2s", &msVersion[0]);
    snprintf_err_handle(msMinor, 3, "%.2s", &msVersion[2]);
    snprintf_err_handle(msPatch, 6, "%.5s", &msVersion[4]);
    compilerVersionInfo->major = C_CAST(uint16_t, strtoul(msMajor, M_NULLPTR, BASE_10_DECIMAL));
    compilerVersionInfo->minor = C_CAST(uint16_t, strtoul(msMinor, M_NULLPTR, BASE_10_DECIMAL));
    compilerVersionInfo->patch = C_CAST(uint16_t, strtoul(msPatch, M_NULLPTR, BASE_10_DECIMAL));
#elif defined __MINGW64__
    *compilerUsed              = OPENSEA_COMPILER_MINGW;
    compilerVersionInfo->major = __MINGW64_VERSION_MAJOR;
    compilerVersionInfo->minor = __MINGW64_VERSION_MINOR;
    compilerVersionInfo->patch = 0;
#elif defined __MINGW32__
    *compilerUsed              = OPENSEA_COMPILER_MINGW;
    compilerVersionInfo->major = __MINGW32_MAJOR_VERSION;
    compilerVersionInfo->minor = __MINGW32_MINOR_VERSION;
    compilerVersionInfo->patch = 0;
#elif defined __clang__
    *compilerUsed              = OPENSEA_COMPILER_CLANG;
    compilerVersionInfo->major = __clang_major__;
    compilerVersionInfo->minor = __clang_minor__;
    compilerVersionInfo->patch = __clang_patchlevel__;
#elif defined __HP_aCC || defined __HP_cc
    // untested
    *compilerUsed = OPENSEA_COMPILER_HP_A_CPP;
#    define HP_ACC_VERSION_STRING_LENGTH 7
    DECLARE_ZERO_INIT_ARRAY(char, hpVersion, HP_ACC_VERSION_STRING_LENGTH);
#    if defined __HP_cc
    snprintf_err_handle(hpVersion, HP_ACC_VERSION_STRING_LENGTH, "%u", __HP_cc);
#    else
    snprintf_err_handle(hpVersion, HP_ACC_VERSION_STRING_LENGTH, "%u", __HP_aCC);
#    endif
    DECLARE_ZERO_INIT_ARRAY(char, hpMajor, 3);
    DECLARE_ZERO_INIT_ARRAY(char, hpMinor, 3);
    DECLARE_ZERO_INIT_ARRAY(char, hpPatch, 3);
    snprintf_err_handle(hpMajor, 3, "%.2s", &hpVersion[0]);
    snprintf_err_handle(hpMinor, 3, "%.2s", &hpVersion[2]);
    snprintf_err_handle(hpPatch, 3, "%.2s", &hpVersion[4]);
    compilerVersionInfo->major = C_CAST(uint16_t, strtoul(hpMajor, M_NULLPTR, BASE_10_DECIMAL));
    compilerVersionInfo->minor = C_CAST(uint16_t, strtoul(hpMinor, M_NULLPTR, BASE_10_DECIMAL));
    compilerVersionInfo->patch = C_CAST(uint16_t, strtoul(hpPatch, M_NULLPTR, BASE_10_DECIMAL));
#elif defined __IBMC__ || defined __IBMCPP__
    // untested
    // detect if it's xl or lx for system z
#    if defined __COMPILER_VER__
    // system z
    compilerVersionInfo->major = M_Nibble6(__COMPILER_VER__);
    compilerVersionInfo->minor = M_Byte2(__COMPILER_VER__);
    compilerVersionInfo->patch = M_Word0(__COMPILER_VER__);
#    else
    // standard xl
    compilerVersionInfo->major = M_Byte1(__xlC__);
    compilerVersionInfo->minor = M_Byte0(__xlC__);
    compilerVersionInfo->patch = M_Byte1(__xlC_ver__);
#    endif
#elif defined __INTEL_COMPILER
    // untested
    *compilerUsed = OPENSEA_COMPILER_INTEL_C_CPP;
#    define INTEL_VERSION_STRING_MAX_LENGTH 4;
    DECLARE_ZERO_INIT_ARRAY(char, intelVersion, 4);
    snprintf_err_handle(intelVersion, INTEL_VERSION_STRING_MAX_LENGTH, "%u", __INTEL_COMPILER);
    DECLARE_ZERO_INIT_ARRAY(char, intelMajor, 2);
    DECLARE_ZERO_INIT_ARRAY(char, intelMinor, 2);
    DECLARE_ZERO_INIT_ARRAY(char, intelPatch, 2);
    snprintf_err_handle(intelMajor, 2, "%.1s", &intelVersion[0]);
    snprintf_err_handle(intelMinor, 2, "%.1s", &intelVersion[1]);
    snprintf_err_handle(intelPatch, 2, "%.1s", &intelVersion[2]);
    compilerVersionInfo->major = C_CAST(uint16_t, strtoul(intelMajor, M_NULLPTR, BASE_0_AUTO));
    compilerVersionInfo->minor = C_CAST(uint16_t, strtoul(intelMinor, M_NULLPTR, BASE_0_AUTO));
    compilerVersionInfo->patch = C_CAST(uint16_t, strtoul(intelPatch, M_NULLPTR, BASE_0_AUTO));
#elif defined __SUNPRO_C || defined __SUNPRO_CC
    // untested
    // code below is written for versions 5.10 and later. (latest release as of
    // writing this code is version 5.12) - TJE
    *compilerUsed              = OPENSEA_COMPILER_SUNPRO_C_CPP;
    compilerVersionInfo->major = M_Nibble3(__SUNPRO_C);
    compilerVersionInfo->minor =
        (M_Nibble2(__SUNPRO_C) << 4) | M_Nibble1(__SUNPRO_C); // I think this is wrong. Version 5.10 is represented
                                                              // as 0x5100. Need to put extra code in here to fix
                                                              // this when we actually support this compiler.
    compilerVersionInfo->patch = M_Nibble0(__SUNPRO_C);
#elif defined __GNUC__ // CYGWIN?
    // GCC C/C++ compiler
    *compilerUsed              = OPENSEA_COMPILER_GCC;
    compilerVersionInfo->major = __GNUC__;
    compilerVersionInfo->minor = __GNUC_MINOR__;
    compilerVersionInfo->patch = __GNUC_PATCHLEVEL__; // introduced in GCC 3.0, but even that is super
                                                      // old so this shouldn't cause any problems.-TJE
#else
    ret = NOT_SUPPORTED;
#endif
    return ret;
}

void print_Compiler(eCompiler compilerUsed)
{
    switch (compilerUsed)
    {
    case OPENSEA_COMPILER_MICROSOFT_VISUAL_C_CPP:
        print_str("Microsoft Visual C/C++");
        break;
    case OPENSEA_COMPILER_GCC:
        print_str("GCC");
        break;
    case OPENSEA_COMPILER_CLANG:
        print_str("Clang");
        break;
    case OPENSEA_COMPILER_MINGW:
        print_str("MinGW");
        break;
    case OPENSEA_COMPILER_INTEL_C_CPP:
        print_str("Intel C/C++");
        break;
    case OPENSEA_COMPILER_SUNPRO_C_CPP:
        print_str("Oracle Sunpro C/C++");
        break;
    case OPENSEA_COMPILER_IBM_XL_C_CPP:
        print_str("IBM XL C/C++");
        break;
    case OPENSEA_COMPILER_IBM_SYSTEMZ_C_CPP:
        print_str("IBM XL C/C++ for SystemZ");
        break;
    case OPENSEA_COMPILER_HP_A_CPP:
        print_str("HP aCC");
        break;
    case OPENSEA_COMPILER_UNKNOWN:
        // default:
        print_str("Unknown Compiler");
        break;
    }
}

void print_Compiler_Version_Info(const ptrCompilerVersion compilerVersionInfo)
{
    printf("%" PRIu16 ".%" PRIu16 ".%" PRIu16 "", compilerVersionInfo->major, compilerVersionInfo->minor,
           compilerVersionInfo->patch);
}
