/*************************************************************************
 *
 * Copyright 2016 Realm Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 **************************************************************************/

#ifndef REALM_NULL_HPP
#define REALM_NULL_HPP

#include <cmath>
#include <cstring>

#include <realm/util/features.h>
#include <realm/util/optional.hpp>
#include <realm/utilities.hpp>
#include <realm/exceptions.hpp>

namespace realm {

/*
Represents null in Query, find(), get(), set(), etc.

Float/Double: Realm can both store user-given NaNs and null. Any user-given signaling NaN is converted to
0x7fa00000 (if float) or 0x7ff4000000000000 (if double). Any user-given quiet NaN is converted to
0x7fc00000 (if float) or 0x7ff8000000000000 (if double). So Realm does not preserve the optional bits in
user-given NaNs.

However, since both clang and gcc on x64 and ARM, and also Java on x64, return these bit patterns when
requesting NaNs, these will actually seem to roundtrip bit-exact for the end-user in most cases.

If set_null() is called, a null is stored in form of the bit pattern 0xffffffff (if float) or
0xffffffffffffffff (if double). These are quiet NaNs.

Executing a query that involves a float/double column that contains NaNs gives an undefined result. If
it contains signaling NaNs, it may throw an exception.

Notes on IEEE:

A NaN float is any bit pattern `s 11111111 S xxxxxxxxxxxxxxxxxxxxxx` where `s` and `x` are arbitrary, but at
least 1 `x` must be 1. If `S` is 1, it's a quiet NaN, else it's a signaling NaN.

A NaN doubule is the same as above, but for `s eeeeeeeeeee S xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`

The `S` bit is at position 22 (float) or 51 (double).
*/

struct null {
    constexpr null() = default;
    template <class T>
    operator util::Optional<T>()
    {
        return util::none;
    }

    /// Returns whether `v` bitwise equals the null bit-pattern
    template <class T>
    static bool is_null_float(T v)
    {
        T i = null::get_null_float<T>();
        return std::memcmp(&i, &v, sizeof(T)) == 0;
    }

    /// Returns the quiet NaNs that represent null for floats/doubles in Realm in stored payload.
    template <class T>
    static T get_null_float()
    {
        typename std::conditional<std::is_same<T, float>::value, uint32_t, uint64_t>::type i;
        int64_t double_nan = 0x7ff80000000000aa;
        i = std::is_same<T, float>::value ? 0x7fc000aa : static_cast<decltype(i)>(double_nan);
        T d = type_punning<T, decltype(i)>(i);
        REALM_ASSERT_DEBUG(std::isnan(d));
        REALM_ASSERT_DEBUG(!is_signaling(d));
        return d;
    }

    /// Takes a NaN as argument and returns whether or not it's signaling
    template <class T>
    static bool is_signaling(T v)
    {
        REALM_ASSERT(std::isnan(static_cast<double>(v)));
        typename std::conditional<std::is_same<T, float>::value, uint32_t, uint64_t>::type i;
        size_t signal_bit = std::is_same<T, float>::value ? 22 : 51; // If this bit is set, it's quiet
        i = type_punning<decltype(i), T>(v);
        return !(i & (1ull << signal_bit));
    }

    /// Converts any signaling or quiet NaN to their their respective bit patterns that are used on x64 gcc+clang,
    /// ARM clang and x64 Java.
    template <class T>
    static T to_realm(T v)
    {
        if (std::isnan(static_cast<double>(v))) {
            typename std::conditional<std::is_same<T, float>::value, uint32_t, uint64_t>::type i;
            if (std::is_same<T, float>::value) {
                i = is_signaling(v) ? 0x7fa00000 : 0x7fc00000;
            }
            else {
                i = static_cast<decltype(i)>(is_signaling(v) ? 0x7ff4000000000000 : 0x7ff8000000000000);
            }
            return type_punning<T, decltype(i)>(i);
        }
        else {
            return v;
        }
    }
};

template <typename T>
inline bool is_null(const T&)
{
    return false;
}

template <class OS>
OS& operator<<(OS& os, const null&)
{
    os << "(null)";
    return os;
}

} // namespace realm

#endif // REALM_NULL_HPP
