/////////////////////////////////////////////////////////////////////////////////////////
//
// Please refer to "COPYRIGHT.md" for the relevant copyright statement of this software.
//
/////////////////////////////////////////////////////////////////////////////////////////
//
#ifndef IMATH_HALF_H_
#define IMATH_HALF_H_

#if defined(__linux__)
#  ifdef GTIL_EXPORTS
#    define GTIL_API __attribute__((visibility("default")))
#  else
#    define GTIL_API
#  endif // GTIL_EXPORTS
#else
#  ifdef GTIL_EXPORTS
#    define GTIL_API __declspec(dllexport)
#  else
#    define GTIL_API __declspec(dllimport)
#  endif // GTIL_EXPORTS
#endif
#define GTIL_GCC_DEFAULT

#include "ImathExport.h"
#include "ImathNamespace.h"
#include "ImathPlatform.h"

#ifdef _WIN32
#  include <intrin.h>
#elif defined(__x86_64__)
#  include <x86intrin.h>
#endif

#include <stdint.h>
#include <stdio.h>

#ifdef IMATH_HALF_ENABLE_FP_EXCEPTIONS
#  include <fenv.h>
#endif

#if (defined _WIN32 || defined _WIN64) && defined _MSC_VER
#  define HALF_DENORM_MIN 5.96046448e-08f
#  define HALF_NRM_MIN 6.10351562e-05f
#  define HALF_MIN 6.10351562e-05f
#  define HALF_MAX 65504.0f
#  define HALF_EPSILON 0.00097656f
#else
#  define HALF_DENORM_MIN 5.96046448e-08
#  define HALF_NRM_MIN 6.10351562e-05
#  define HALF_MIN 6.10351562e-05f
#  define HALF_MAX 65504.0
#  define HALF_EPSILON 0.00097656
#endif

#define HALF_MANT_DIG 11
#define HALF_DIG 3
#define HALF_DECIMAL_DIG 5
#define HALF_RADIX 2
#define HALF_DENORM_MIN_EXP -13
#define HALF_MAX_EXP 16
#define HALF_DENORM_MIN_10_EXP -4
#define HALF_MAX_10_EXP 4

typedef union imath_half_uif
{
  uint32_t i;
  float f;
} imath_half_uif_t;

typedef uint16_t imath_half_bits_t;

#if !defined(__cplusplus) && !defined(__CUDACC__)
typedef imath_half_bits_t half;
#endif

#if !defined(IMATH_HALF_NO_LOOKUP_TABLE)
#  if defined(__cplusplus)
extern "C"
#  else
extern
#  endif
  GTIL_API const imath_half_uif_t* imath_half_to_float_table;
#endif

static inline float imath_half_to_float(imath_half_bits_t h)
{
#if defined(__F16C__)
#  ifdef _MSC_VER
  return _mm_cvtss_f32(_mm_cvtph_ps(_mm_set1_epi16(h)));
#  else
  return _cvtsh_ss(h);
#  endif
#elif defined(IMATH_HALF_USE_LOOKUP_TABLE) && !defined(IMATH_HALF_NO_LOOKUP_TABLE)
  return imath_half_to_float_table[h].f;
#else
  imath_half_uif_t v;
  uint32_t hexpmant = ((uint32_t)(h) << 17) >> 4;
  v.i = ((uint32_t)(h >> 15)) << 31;

  if (IMATH_LIKELY((hexpmant >= 0x00800000)))
  {
    v.i |= hexpmant;
    if (IMATH_LIKELY((hexpmant < 0x0f800000)))
      v.i += 0x38000000;
    else
      v.i |= 0x7f800000;
  }
  else if (hexpmant != 0)
  {
    uint32_t lc;
#  if defined(_MSC_VER) && (_M_IX86 || _M_X64)
    lc = __lzcnt(hexpmant);
#  elif defined(__GNUC__) || defined(__clang__)
    lc = (uint32_t)__builtin_clz(hexpmant);
#  else
    lc = 0;
    while (0 == ((hexpmant << lc) & 0x80000000))
      ++lc;
#  endif
    lc -= 8;
    v.i |= 0x38800000;
    v.i |= (hexpmant << lc);
    v.i -= (lc << 23);
  }
  return v.f;
#endif
}

static inline imath_half_bits_t imath_float_to_half(float f)
{
#if defined(__F16C__)
#  ifdef _MSC_VER
  return _mm_extract_epi16(_mm_cvtps_ph(_mm_set_ss(f), (_MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC)), 0);
#  else
  return _cvtss_sh(f, (_MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC));
#  endif
#else
  imath_half_uif_t v;
  imath_half_bits_t ret;
  uint32_t e, m, ui, r, shift;

  v.f = f;

  ui = (v.i & ~0x80000000);
  ret = ((v.i >> 16) & 0x8000);

  if (ui >= 0x38800000)
  {
    if (IMATH_UNLIKELY(ui >= 0x7f800000))
    {
      ret |= 0x7c00;
      if (ui == 0x7f800000)
        return ret;
      m = (ui & 0x7fffff) >> 13;
      return ret | (uint16_t)m | (uint16_t)(m == 0);
    }

    if (IMATH_UNLIKELY(ui > 0x477fefff))
    {
#  ifdef IMATH_HALF_ENABLE_FP_EXCEPTIONS
      feraiseexcept(FE_OVERFLOW);
#  endif
      return ret | 0x7c00;
    }

    ui -= 0x38000000;
    ui = ((ui + 0x00000fff + ((ui >> 13) & 1)) >> 13);
    return ret | (uint16_t)ui;
  }

  if (ui < 0x33000001)
  {
#  ifdef IMATH_HALF_ENABLE_FP_EXCEPTIONS
    if (ui == 0)
      return ret;
    feraiseexcept(FE_UNDERFLOW);
#  endif
    return ret;
  }

  e = (ui >> 23);
  shift = 0x7e - e;
  m = 0x800000 | (ui & 0x7fffff);
  r = m << (32 - shift);
  ret |= (m >> shift);
  if (r > 0x80000000 || (r == 0x80000000 && (ret & 0x1) != 0))
    ++ret;
  return ret;
#endif
}

#ifdef __cplusplus

#  include <iostream>

IMATH_INTERNAL_NAMESPACE_HEADER_ENTER

class GTIL_API half
{
public:
  enum IMATH_EXPORT_ENUM FromBitsTag
  {
    FromBits
  };

  half() IMATH_NOEXCEPT = default;

  half(float f) IMATH_NOEXCEPT;

  constexpr half(FromBitsTag, uint16_t bits) IMATH_NOEXCEPT;

  constexpr half(const half&) IMATH_NOEXCEPT = default;

  constexpr half(half&&) IMATH_NOEXCEPT = default;

  ~half() IMATH_NOEXCEPT = default;

  operator float() const IMATH_NOEXCEPT;

  constexpr half operator-() const IMATH_NOEXCEPT;

  half& operator=(const half& h) IMATH_NOEXCEPT = default;

  half& operator=(half&& h) IMATH_NOEXCEPT = default;

  half& operator=(float f) IMATH_NOEXCEPT;

  half& operator+=(half h) IMATH_NOEXCEPT;

  half& operator+=(float f) IMATH_NOEXCEPT;

  half& operator-=(half h) IMATH_NOEXCEPT;

  half& operator-=(float f) IMATH_NOEXCEPT;

  half& operator*=(half h) IMATH_NOEXCEPT;

  half& operator*=(float f) IMATH_NOEXCEPT;

  half& operator/=(half h) IMATH_NOEXCEPT;

  half& operator/=(float f) IMATH_NOEXCEPT;

  IMATH_CONSTEXPR14 half round(unsigned int n) const IMATH_NOEXCEPT;

  constexpr bool isFinite() const IMATH_NOEXCEPT;

  constexpr bool isNormalized() const IMATH_NOEXCEPT;

  constexpr bool isDenormalized() const IMATH_NOEXCEPT;

  constexpr bool isZero() const IMATH_NOEXCEPT;

  constexpr bool isNan() const IMATH_NOEXCEPT;

  constexpr bool isInfinity() const IMATH_NOEXCEPT;

  constexpr bool isNegative() const IMATH_NOEXCEPT;

  static constexpr half posInf() IMATH_NOEXCEPT;

  static constexpr half negInf() IMATH_NOEXCEPT;

  static constexpr half qNan() IMATH_NOEXCEPT;

  static constexpr half sNan() IMATH_NOEXCEPT;

  constexpr uint16_t bits() const IMATH_NOEXCEPT;

  IMATH_CONSTEXPR14 void setBits(uint16_t bits) IMATH_NOEXCEPT;

public:
  static_assert(sizeof(float) == sizeof(uint32_t), "Assumption about the size of floats correct");
  using uif = imath_half_uif;

private:
  constexpr uint16_t mantissa() const IMATH_NOEXCEPT;
  constexpr uint16_t exponent() const IMATH_NOEXCEPT;

  uint16_t _h;
};

inline half::half(float f) IMATH_NOEXCEPT : _h(imath_float_to_half(f))
{
}

inline constexpr half::half(FromBitsTag, uint16_t bits) IMATH_NOEXCEPT : _h(bits)
{
}

inline half::operator float() const IMATH_NOEXCEPT
{
  return imath_half_to_float(_h);
}

inline IMATH_CONSTEXPR14 half half::round(unsigned int n) const IMATH_NOEXCEPT
{
  if (n >= 10)
    return *this;

  uint16_t s = _h & 0x8000;
  uint16_t e = _h & 0x7fff;

  e >>= 9 - n;
  e += e & 1;
  e <<= 9 - n;

  if (e >= 0x7c00)
  {
    e = _h;
    e >>= 10 - n;
    e <<= 10 - n;
  }

  half h(FromBits, s | e);

  return h;
}

inline constexpr half half::operator-() const IMATH_NOEXCEPT
{
  return half(FromBits, bits() ^ 0x8000);
}

inline half& half::operator=(float f) IMATH_NOEXCEPT
{
  *this = half(f);
  return *this;
}

inline half& half::operator+=(half h) IMATH_NOEXCEPT
{
  *this = half(float(*this) + float(h));
  return *this;
}

inline half& half::operator+=(float f) IMATH_NOEXCEPT
{
  *this = half(float(*this) + f);
  return *this;
}

inline half& half::operator-=(half h) IMATH_NOEXCEPT
{
  *this = half(float(*this) - float(h));
  return *this;
}

inline half& half::operator-=(float f) IMATH_NOEXCEPT
{
  *this = half(float(*this) - f);
  return *this;
}

inline half& half::operator*=(half h) IMATH_NOEXCEPT
{
  *this = half(float(*this) * float(h));
  return *this;
}

inline half& half::operator*=(float f) IMATH_NOEXCEPT
{
  *this = half(float(*this) * f);
  return *this;
}

inline half& half::operator/=(half h) IMATH_NOEXCEPT
{
  *this = half(float(*this) / float(h));
  return *this;
}

inline half& half::operator/=(float f) IMATH_NOEXCEPT
{
  *this = half(float(*this) / f);
  return *this;
}

inline constexpr uint16_t half::mantissa() const IMATH_NOEXCEPT
{
  return _h & 0x3ff;
}

inline constexpr uint16_t half::exponent() const IMATH_NOEXCEPT
{
  return (_h >> 10) & 0x001f;
}

inline constexpr bool half::isFinite() const IMATH_NOEXCEPT
{
  return exponent() < 31;
}

inline constexpr bool half::isNormalized() const IMATH_NOEXCEPT
{
  return exponent() > 0 && exponent() < 31;
}

inline constexpr bool half::isDenormalized() const IMATH_NOEXCEPT
{
  return exponent() == 0 && mantissa() != 0;
}

inline constexpr bool half::isZero() const IMATH_NOEXCEPT
{
  return (_h & 0x7fff) == 0;
}

inline constexpr bool half::isNan() const IMATH_NOEXCEPT
{
  return exponent() == 31 && mantissa() != 0;
}

inline constexpr bool half::isInfinity() const IMATH_NOEXCEPT
{
  return exponent() == 31 && mantissa() == 0;
}

inline constexpr bool half::isNegative() const IMATH_NOEXCEPT
{
  return (_h & 0x8000) != 0;
}

inline constexpr half half::posInf() IMATH_NOEXCEPT
{
  return half(FromBits, 0x7c00);
}

inline constexpr half half::negInf() IMATH_NOEXCEPT
{
  return half(FromBits, 0xfc00);
}

inline constexpr half half::qNan() IMATH_NOEXCEPT
{
  return half(FromBits, 0x7fff);
}

inline constexpr half half::sNan() IMATH_NOEXCEPT
{
  return half(FromBits, 0x7dff);
}

inline constexpr uint16_t half::bits() const IMATH_NOEXCEPT
{
  return _h;
}

inline IMATH_CONSTEXPR14 void half::setBits(uint16_t bits) IMATH_NOEXCEPT
{
  _h = bits;
}

IMATH_INTERNAL_NAMESPACE_HEADER_EXIT

GTIL_API std::ostream& operator<<(std::ostream& os, IMATH_INTERNAL_NAMESPACE::half h);

GTIL_API std::istream& operator>>(std::istream& is, IMATH_INTERNAL_NAMESPACE::half& h);

GTIL_API void printBits(std::ostream& os, IMATH_INTERNAL_NAMESPACE::half h);
GTIL_API void printBits(std::ostream& os, float f);
GTIL_API void printBits(char c[19], IMATH_INTERNAL_NAMESPACE::half h);
GTIL_API void printBits(char c[35], float f);

#  ifndef __CUDACC__
using half = IMATH_INTERNAL_NAMESPACE::half;
#  else
#    include <cuda_fp16.h>
#  endif

#endif

#endif