/*************************************************************************
 *
 * 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_UTILITIES_HPP
#define REALM_UTILITIES_HPP

#include <cstdint>
#include <cstdlib>
#include <cstdlib> // size_t
#include <cstdio>
#include <algorithm>
#include <functional>
#include <time.h>

#ifdef _WIN32

#include <WinSock2.h>
#include <intrin.h>
#include <BaseTsd.h>

#if !defined(_SSIZE_T_) && !defined(_SSIZE_T_DEFINED)
typedef SSIZE_T ssize_t;
#define _SSIZE_T_
#define _SSIZE_T_DEFINED
#endif

#endif // _WIN32

#include <realm/util/features.h>
#include <realm/util/assert.hpp>
#include <realm/util/functional.hpp>
#include <realm/util/safe_int_ops.hpp>

// GCC defines __i386__ and __x86_64__
#if (defined(__X86__) || defined(__i386__) || defined(i386) || defined(_M_IX86) || defined(__386__) ||               \
     defined(__x86_64__) || defined(_M_X64))
#define REALM_X86_OR_X64
#define REALM_X86_OR_X64_TRUE true
#else
#define REALM_X86_OR_X64_TRUE false
#endif

// GCC defines __arm__
#ifdef __arm__
#define REALM_ARCH_ARM
#endif

#if defined _LP64 || defined __LP64__ || defined __64BIT__ || defined _ADDR64 || defined _WIN64 ||                   \
    defined __arch64__ || (defined(__WORDSIZE) && __WORDSIZE == 64) || (defined __sparc && defined __sparcv9) ||     \
    defined __x86_64 || defined __amd64 || defined __x86_64__ || defined _M_X64 || defined _M_IA64 ||                \
    defined __ia64 || defined __IA64__
#define REALM_PTR_64
#endif


#if defined(REALM_PTR_64) && defined(REALM_X86_OR_X64) && !REALM_WATCHOS
#define REALM_COMPILER_SSE // Compiler supports SSE 4.2 through __builtin_ accessors or back-end assembler
#define REALM_COMPILER_AVX
#endif

namespace realm {

using StringCompareCallback = util::UniqueFunction<bool(const char* string1, const char* string2)>;

extern signed char sse_support;
extern signed char avx_support;

template <int version>
REALM_FORCEINLINE bool sseavx()
{
    /*
    Return whether or not SSE 3.0 (if version = 30) or 4.2 (for version = 42) is supported. Return value
    is based on the CPUID instruction.

    sse_support = -1: No SSE support
    sse_support = 0: SSE3
    sse_support = 1: SSE42

    avx_support = -1: No AVX support
    avx_support = 0: AVX1 supported
    sse_support = 1: AVX2 supported (not yet implemented for detection in our cpuid_init(), todo)

    This lets us test very rapidly at runtime because we just need 1 compare instruction (with 0) to test both for
    SSE 3 and 4.2 by caller (compiler optimizes if calls are concecutive), and can decide branch with ja/jl/je because
    sse_support is signed type. Also, 0 requires no immediate operand. Same for AVX.

    We runtime-initialize sse_support in a constructor of a static variable which is not guaranteed to be called
    prior to cpu_sse(). So we compile-time initialize sse_support to -2 as fallback.
    */
    static_assert(version == 1 || version == 2 || version == 30 || version == 42,
                  "Only version == 1 (AVX), 2 (AVX2), 30 (SSE 3) and 42 (SSE 4.2) are supported for detection");
#ifdef REALM_COMPILER_SSE
    if (version == 30)
        return (sse_support >= 0);
    else if (version == 42)
        return (sse_support > 0); // faster than == 1 (0 requres no immediate operand)
    else if (version == 1)        // avx
        return (avx_support >= 0);
    else if (version == 2) // avx2
        return (avx_support > 0);
    else
        return false;
#else
    return false;
#endif
}

void cpuid_init();
void millisleep(unsigned long milliseconds);

#ifdef _WIN32
int gettimeofday(struct timeval* tp, struct timezone* tzp);
#endif

int64_t platform_timegm(tm time);

#ifdef REALM_SLAB_ALLOC_TUNE
void process_mem_usage(double& vm_usage, double& resident_set);
#endif
// popcount
int fast_popcount32(int32_t x);
int fast_popcount64(int64_t x);
uint64_t fastrand(uint64_t max = 0xffffffffffffffffULL, bool is_seed = false);

// Class to be used when a private generator is wanted.
// Object of this class should not be shared between threads.
class FastRand {
public:
    FastRand(uint64_t seed = 1)
        : m_state(seed)
    {
    }
    uint64_t operator()(uint64_t max = uint64_t(-1));

private:
    uint64_t m_state;
};

// log2 - returns -1 if x==0, otherwise log2(x)
inline int log2(size_t x)
{
    if (x == 0)
        return -1;
#if defined(__GNUC__)
#ifdef REALM_PTR_64
    return 63 - __builtin_clzll(x); // returns int
#else
    return 31 - __builtin_clz(x); // returns int
#endif
#elif defined(_WIN32)
    unsigned long index = 0;
#ifdef REALM_PTR_64
    unsigned char c = _BitScanReverse64(&index, x); // outputs unsigned long
#else
    unsigned char c = _BitScanReverse(&index, x); // outputs unsigned long
#endif
    return static_cast<int>(index);
#else // not __GNUC__ and not _WIN32
    int r = 0;
    while (x >>= 1) {
        r++;
    }
    return r;
#endif
}

// count trailing zeros (from least-significant bit)
inline int ctz(size_t x)
{
    if (x == 0)
        return sizeof(x) * 8;

#if defined(__GNUC__)
#ifdef REALM_PTR_64
    return __builtin_ctzll(x); // returns int
#else
    return __builtin_ctz(x);      // returns int
#endif
#elif defined(_WIN32)
    unsigned long index = 0;
#ifdef REALM_PTR_64
    unsigned char c = _BitScanForward64(&index, x); // outputs unsigned long
#else
    unsigned char c = _BitScanForward(&index, x); // outputs unsigned long
#endif
    return static_cast<int>(index);
#else // not __GNUC__ and not _WIN32
    int r = 0;
    while (r < sizeof(size_t) * 8 && (x & 1) == 0) {
        x >>= 1;
        r++;
    }
    return r;
#endif
}

// Implementation:

// Safe cast from 64 to 32 bits on 32 bit architecture. Differs from to_ref() by not testing alignment and
// REF-bitflag.
inline size_t to_size_t(int_fast64_t v) noexcept
{
    REALM_ASSERT_DEBUG(!util::int_cast_has_overflow<size_t>(v));
    return size_t(v);
}


template <typename ReturnType, typename OriginalType>
ReturnType type_punning(OriginalType variable) noexcept
{
    union Both {
        OriginalType in;
        ReturnType out;
    };
    Both both;
    both.out = ReturnType(); // Clear all bits in case ReturnType is larger than OriginalType
    both.in = variable;
    return both.out;
}

// Also see the comments in Array::index_string()
enum FindRes {
    // Indicate that no results were found in the search
    FindRes_not_found,
    // Indicates a single result is found
    FindRes_single,
    // Indicates more than one result is found and they are stored in a column
    FindRes_column
};

enum IndexMethod {
    index_FindFirst,
    index_FindAll_nocopy,
    index_FindAll_prefix,
    index_Count,
};

// Combined result of the index_FindAll_nocopy operation. The column returned
// can contain results that are not matches but all matches are within the
// returned start_ndx and end_ndx.
struct InternalFindResult {
    // Reference to a IntegerColumn containing result rows, or a single row
    // value if the result is FindRes_single.
    int64_t payload;
    // Offset into the result column to start at.
    size_t start_ndx;
    // Offset index in the result column to end at.
    size_t end_ndx;
};


// realm::is_any<T, U1, U2, U3, ...> ==
// std::is_same<T, U1>::value || std::is_same<T, U2>::value || std::is_same<T, U3>::value ...
template <typename... T>
struct is_any : std::false_type {};

template <typename T, typename... Ts>
struct is_any<T, T, Ts...> : std::true_type {};

template <typename T, typename U, typename... Ts>
struct is_any<T, U, Ts...> : is_any<T, Ts...> {};

template <typename... Ts>
inline constexpr bool is_any_v = is_any<Ts...>::value;


// Use realm::safe_equal() instead of std::equal() if one of the parameters can be a null pointer.
template <class InputIterator1, class InputIterator2>
bool safe_equal(InputIterator1 first1, InputIterator1 last1, InputIterator2 first2)
{
#if defined(_MSC_VER)
    // VS has a special check in debug mode against passing a null pointer std::equal(); it will give a warning
    // at runtime if this is observed.
    // It's uncertain if this is allowed by the C++ standard. For details, see
    // http://stackoverflow.com/questions/19120779/is-char-p-0-stdequalp-p-p-well-defined-according-to-the-c-standard.
    // So we use a safe C++14 method instead that takes two range pairs.
    size_t len = last1 - first1;
    return std::equal(first1, last1, first2, first2 + len);
#else
    return std::equal(first1, last1, first2);
#endif
}

// Use realm::safe_copy_n() instead of std::copy_n() if one of the parameters can be a null pointer. See the
// explanation of safe_equal() above; same things apply.
template <class InputIt, class Size, class OutputIt>
OutputIt safe_copy_n(InputIt first, Size count, OutputIt result)
{
#if defined(_MSC_VER)
    // This loop and the method prototype is copy pasted
    // from "Possible implementation" on http://en.cppreference.com/w/cpp/algorithm/copy_n
    if (count > 0) {
        *result++ = *first;
        for (Size i = 1; i < count; ++i) {
            *result++ = *++first;
        }
    }
    return result;
#else
    return std::copy_n(first, count, result);
#endif
}

// Converts ascii c-locale uppercase characters to lower case,
// leaves other char values unchanged.
inline char toLowerAscii(char c)
{
    if (isascii(c) && isupper(c)) {
#if REALM_ANDROID
        return tolower(c); // _tolower is not supported on all ABI levels
#else
        return _tolower(c);
#endif
    }
    return c;
}

inline void* round_up(void* p, uintptr_t align)
{
    uintptr_t r = uintptr_t(p) % align == 0 ? 0 : align - uintptr_t(p) % align;
    return static_cast<char*>(p) + r;
}

inline void* round_down(void* p, uintptr_t align)
{
    uintptr_t r = uintptr_t(p);
    return reinterpret_cast<void*>(r & ~(align - 1));
}

constexpr size_t round_up(size_t p, size_t align)
{
    size_t r = p % align == 0 ? 0 : align - p % align;
    return p + r;
}

constexpr size_t round_down(size_t p, size_t align)
{
    size_t r = p;
    return r & (~(align - 1));
}

// return pointer to found character or to terminating NUL
static inline const char* find_chr(const char* p, char c)
{
    while (*p && *p != c) {
        ++p;
    }
    return p;
}

#ifdef _WIN32
typedef HANDLE FileDesc;
#else
typedef int FileDesc;
#endif


enum class IteratorControl { AdvanceToNext, Stop };

} // namespace realm

#endif // REALM_UTILITIES_HPP
