#pragma once

#include <cmath>
#include <cstring>
#include <ctime>

#include <stdexcept>
#include <string>
#include <chrono>
#include <string_view>
#include <memory>
#include <iostream>
#include <map>
#include <algorithm>
#include <variant>
#include <type_traits>
#include <typeindex>

#define PK_VERSION				"2.0.0"
#define PK_VERSION_MAJOR            2
#define PK_VERSION_MINOR            0
#define PK_VERSION_PATCH            0

#include "config.h"
#include "export.h"

#include "_generated.h"

#ifdef min
#undef min
#endif

#ifdef max
#undef max
#endif

/*******************************************************************************/

#if PK_ENABLE_THREAD
#define PK_THREAD_LOCAL thread_local
#include <mutex>

struct GIL {
	inline static std::mutex _mutex;
    explicit GIL() { _mutex.lock(); }
    ~GIL() { _mutex.unlock(); }
};
#define PK_GLOBAL_SCOPE_LOCK() GIL _lock;

#else
#define PK_THREAD_LOCAL
#define PK_GLOBAL_SCOPE_LOCK()
#endif

/*******************************************************************************/
namespace pkpy{

namespace std = ::std;

using i64 = int64_t;		// always 64-bit
using f64 = double;			// always 64-bit

static_assert(sizeof(i64) == 8);
static_assert(sizeof(f64) == 8);

struct DummyInstance { };
struct DummyModule { };
struct NoReturn { };
struct Discarded { };

struct Type {
	int16_t index;
	constexpr Type(): index(0) {}
	explicit constexpr Type(int index): index(index) {}
	bool operator==(Type other) const { return this->index == other.index; }
	bool operator!=(Type other) const { return this->index != other.index; }
    constexpr operator int() const { return index; }
};

#define PK_LAMBDA(x) ([](VM* vm, ArgsView args) -> PyVar { return x; })
#define PK_VAR_LAMBDA(x) ([](VM* vm, ArgsView args) -> PyVar { return VAR(x); })
#define PK_ACTION(x) ([](VM* vm, ArgsView args) -> PyVar { x; return vm->None; })

#define PK_REGION(name)	1

#ifdef POCKETPY_H
#define PK_FATAL_ERROR() throw std::runtime_error( "L" + std::to_string(__LINE__) + " FATAL_ERROR()!");
#else
#define PK_FATAL_ERROR() throw std::runtime_error( __FILE__ + std::string(":") + std::to_string(__LINE__) + " FATAL_ERROR()!");
#endif

#define PK_ASSERT(x) if(!(x)) PK_FATAL_ERROR();

#if PK_DEBUG_EXTRA_CHECK
#define PK_DEBUG_ASSERT(x) if(!(x)) PK_FATAL_ERROR();
#else
#define PK_DEBUG_ASSERT(x)
#endif

// is_pod_v<> for c++17 and c++20
template<typename T>
inline constexpr bool is_pod_v = std::is_trivially_copyable_v<T> && std::is_standard_layout_v<T>;

#define PK_ALWAYS_PASS_BY_POINTER(T) \
	T(const T&) = delete; \
	T& operator=(const T&) = delete; \
	T(T&&) = delete; \
	T& operator=(T&&) = delete;

inline const char* kPlatformStrings[] = {
    "win32",        // 0
    "emscripten",   // 1
    "ios",          // 2
    "darwin",       // 3
    "android",      // 4
    "linux",        // 5
    "unknown"       // 6
};

#define PK_SLICE_LOOP(i, start, stop, step) for(int i=start; step>0?i<stop:i>stop; i+=step)

template<typename T>
inline constexpr bool is_integral_v = std::is_same_v<T, char>
        || std::is_same_v<T, short>
        || std::is_same_v<T, int>
        || std::is_same_v<T, long>
        || std::is_same_v<T, long long>
        || std::is_same_v<T, unsigned char>
        || std::is_same_v<T, unsigned short>
        || std::is_same_v<T, unsigned int>
        || std::is_same_v<T, unsigned long>
        || std::is_same_v<T, unsigned long long>
		|| std::is_same_v<T, signed char>;		// for imgui

template<typename T>
inline constexpr bool is_floating_point_v = std::is_same_v<T, float> || std::is_same_v<T, double>;

inline const char* PK_HEX_TABLE = "0123456789abcdef";

struct PyObject;
struct Frame;
class VM;

// by default, only `int` and `float` enable SSO
// users can specialize this template to enable SSO for other types
// SSO types cannot have instance dict
template<typename T>
inline constexpr bool is_sso_v = is_integral_v<T> || is_floating_point_v<T>;

// make a obj_get_t<T> for a given type T, if is_sso_v<T> is true, return T, else return T&
template<typename T>
using obj_get_t = std::conditional_t<is_sso_v<T>, T, T&>;

struct const_sso_var {};

struct PyVar final{
    Type type;
    bool is_ptr;
    uint8_t flags;
    // 12 bytes SSO
    int _0; i64 _1;

    // uninitialized
    PyVar() = default;

    // implict conversion
    PyVar(PyObject* p);

    /* We must initialize all members to allow == operator to work correctly */
    // constexpr initialized
    constexpr PyVar(const const_sso_var&, Type type, int value): type(type), is_ptr(false), flags(0), _0(value), _1(0) {}
    // zero initialized
    constexpr PyVar(std::nullptr_t): type(0), is_ptr(false), flags(0), _0(0), _1(0) {}
    // PyObject* initialized (is_sso = false)
    PyVar(Type type, PyObject* p): type(type), is_ptr(true), flags(0), _0(0), _1(reinterpret_cast<i64>(p)) {}
    // SSO initialized (is_sso = true)
    template<typename T>
    PyVar(Type type, T value): type(type), is_ptr(false), flags(0), _0(0), _1(0) {
        static_assert(sizeof(T) <= 12, "SSO size exceeded");
        as<T>() = value;
    }

    template<typename T>
    T& as(){
        static_assert(!std::is_reference_v<T>);
        if constexpr(sizeof(T) <= 8){
            return reinterpret_cast<T&>(_1);
        }else{
            return reinterpret_cast<T&>(_0);
        }
    }

    explicit operator bool() const { return (bool)type; }

    void set_null() { _qword(0) = 0; _qword(1) = 0; }

    i64 _qword(int i) const { return ((const i64*)this)[i]; }
    i64& _qword(int i) { return ((i64*)this)[i]; }

    bool operator==(const PyVar& other) const {
        return _qword(0) == other._qword(0) && _qword(1) == other._qword(1);
    }

    bool operator!=(const PyVar& other) const {
        return _qword(0) != other._qword(0) || _qword(1) != other._qword(1);
    }

    bool operator==(std::nullptr_t) const { return !(bool)type; }
    bool operator!=(std::nullptr_t) const { return (bool)type; }

    PyObject* get() const {
        PK_DEBUG_ASSERT(is_ptr)
        return reinterpret_cast<PyObject*>(_1);
    }

    PyObject* operator->() const {
        PK_DEBUG_ASSERT(is_ptr)
        return reinterpret_cast<PyObject*>(_1);
    }

    i64 hash() const { return _0 + _1; }

    template<typename T>
    obj_get_t<T> obj_get();
};

static_assert(sizeof(PyVar) == 16 && is_pod_v<PyVar>);

} // namespace pkpy


// specialize std::less for PyVar
namespace std {
    template<>
    struct less<pkpy::PyVar> {
        bool operator()(const pkpy::PyVar& lhs, const pkpy::PyVar& rhs) const {
            return memcmp(&lhs, &rhs, sizeof(pkpy::PyVar)) < 0;
        }
    };
}