#include "q_math.h"

#include <math.h>
#include <stdint.h>

#if defined(WINDOWS_BEHAVIOR) && defined(LINUX_BEHAVIOR)
#error "Select only one of WINDOWS_BEHAVIOR or LINUX_BEHAVIOR"
#elif !defined(WINDOWS_BEHAVIOR) && !defined(LINUX_BEHAVIOR)
#error "Select WINDOWS_BEHAVIOR or LINUX_BEHAVIOR"
#endif

#if defined(LINUX_BEHAVIOR)
#include "coduo_x87emu.h"
#endif

/*
 * The retained original bodies all test normal[0..2] in ascending order, set
 * bits 0..2 only for an ordered value below +0.0f, and finally store one byte
 * at cplane_t.signbits (+0x11):
 *
 *   CoDUOMP.exe                 0x004346a0
 *   uo_cgame_mp_x86.dll        0x3004c800
 *   uo_ui_mp_x86.dll           0x40004810
 *   uo_game_mp_x86.dll         0x20019850
 *   coduo_lnxded               0x0806ab2c
 *   game.mp.uo.i386.so         0x0003e9a8
 *
 * The Windows functions use signaling FCOMP against a stored +0.0f.  The
 * Linux functions use quiet FUCOMPP with +0.0f at ST(0).  This distinction is
 * observable through x87 exception state for NaNs even though both platforms
 * leave the corresponding sign bit clear, so retain whole-function variants.
 */
#if defined(WINDOWS_BEHAVIOR)
void SetPlaneSignbits(cplane_t *plane)
{
#if (defined(__i386__) || defined(__x86_64__)) && \
    (defined(__GNUC__) || defined(__clang__))
    static const float zero = 0.0f;
#endif
    uint8_t signbits = 0;

    for (int32_t axis = 0; axis < 3; ++axis) {
#if (defined(__i386__) || defined(__x86_64__)) && \
    (defined(__GNUC__) || defined(__clang__))
        uint16_t status;

        __asm__ __volatile__("flds %1\n\t"
                             "fcomps %2\n\t"
                             "fnstsw %%ax"
                             : "=a"(status)
                             : "m"(plane->normal[axis]), "m"(zero)
                             : "st", "memory");
        if ((status & UINT16_C(0x0500)) == UINT16_C(0x0100)) {
#else
        if (plane->normal[axis] < 0.0f) {
#endif
            signbits |= (uint8_t)(UINT8_C(1) << axis);
        }
    }

    plane->signbits = signbits;
}
#else
void SetPlaneSignbits(cplane_t *plane)
{
    uint8_t signbits = 0;

    for (int32_t axis = 0; axis < 3; ++axis) {
#if EMULATE_X87
        if (x87f_lt(x87f_load_f32(plane->normal[axis]),
                    x87f_load_f32(0.0f))) {
#elif (defined(__i386__) || defined(__x86_64__)) && \
      (defined(__GNUC__) || defined(__clang__))
        uint16_t status;

        __asm__ __volatile__("flds %1\n\t"
                             "fldz\n\t"
                             "fucompp\n\t"
                             "fnstsw %%ax"
                             : "=a"(status)
                             : "m"(plane->normal[axis])
                             : "st", "st(1)", "memory");
        if ((status & UINT16_C(0x4100)) == 0) {
#else
        if (isless(plane->normal[axis], 0.0f)) {
#endif
            signbits |= (uint8_t)(UINT8_C(1) << axis);
        }
    }

    plane->signbits = signbits;
}
#endif
