#include "pocketpy/interpreter/vm.h"
#include "pocketpy/common/sstream.h"
#include "pocketpy/pocketpy.h"

#include <math.h>

static bool try_castfloat(py_Ref self, double* out) {
    switch(self->type) {
        case tp_int: *out = (double)self->_i64; return true;
        case tp_float: *out = self->_f64; return true;
        default: return false;
    }
}

#define DEF_NUM_BINARY_OP(name, op, rint, rfloat)                                                  \
    static bool int##name(int argc, py_Ref argv) {                                                 \
        PY_CHECK_ARGC(2);                                                                          \
        if(py_isint(&argv[1])) {                                                                   \
            py_i64 lhs = py_toint(&argv[0]);                                                       \
            py_i64 rhs = py_toint(&argv[1]);                                                       \
            rint(py_retval(), lhs op rhs);                                                         \
        } else if(py_isfloat(&argv[1])) {                                                          \
            py_i64 lhs = py_toint(&argv[0]);                                                       \
            py_f64 rhs = py_tofloat(&argv[1]);                                                     \
            rfloat(py_retval(), lhs op rhs);                                                       \
        } else {                                                                                   \
            py_newnotimplemented(py_retval());                                                     \
        }                                                                                          \
        return true;                                                                               \
    }                                                                                              \
    static bool float##name(int argc, py_Ref argv) {                                               \
        PY_CHECK_ARGC(2);                                                                          \
        py_f64 lhs = py_tofloat(&argv[0]);                                                         \
        py_f64 rhs;                                                                                \
        if(try_castfloat(&argv[1], &rhs)) {                                                        \
            rfloat(py_retval(), lhs op rhs);                                                       \
        } else {                                                                                   \
            py_newnotimplemented(py_retval());                                                     \
        }                                                                                          \
        return true;                                                                               \
    }

DEF_NUM_BINARY_OP(__add__, +, py_newint, py_newfloat)
DEF_NUM_BINARY_OP(__sub__, -, py_newint, py_newfloat)
DEF_NUM_BINARY_OP(__mul__, *, py_newint, py_newfloat)

DEF_NUM_BINARY_OP(__eq__, ==, py_newbool, py_newbool)
DEF_NUM_BINARY_OP(__ne__, !=, py_newbool, py_newbool)
DEF_NUM_BINARY_OP(__lt__, <, py_newbool, py_newbool)
DEF_NUM_BINARY_OP(__le__, <=, py_newbool, py_newbool)
DEF_NUM_BINARY_OP(__gt__, >, py_newbool, py_newbool)
DEF_NUM_BINARY_OP(__ge__, >=, py_newbool, py_newbool)

static bool int__neg__(int argc, py_Ref argv) {
    PY_CHECK_ARGC(1);
    py_i64 val = py_toint(&argv[0]);
    py_newint(py_retval(), -val);
    return true;
}

static bool float__neg__(int argc, py_Ref argv) {
    PY_CHECK_ARGC(1);
    py_f64 val = py_tofloat(&argv[0]);
    py_newfloat(py_retval(), -val);
    return true;
}

static bool int__truediv__(int argc, py_Ref argv) {
    PY_CHECK_ARGC(2);
    py_i64 lhs = py_toint(&argv[0]);
    py_f64 rhs;
    if(try_castfloat(&argv[1], &rhs)) {
        py_newfloat(py_retval(), lhs / rhs);
    } else {
        py_newnotimplemented(py_retval());
    }
    return true;
}

static bool float__truediv__(int argc, py_Ref argv) {
    PY_CHECK_ARGC(2);
    py_f64 lhs = py_tofloat(&argv[0]);
    py_f64 rhs;
    if(try_castfloat(&argv[1], &rhs)) {
        py_newfloat(py_retval(), lhs / rhs);
    } else {
        py_newnotimplemented(py_retval());
    }
    return true;
}

static bool number__pow__(int argc, py_Ref argv) {
    PY_CHECK_ARGC(2);
    if(py_isint(&argv[0]) && py_isint(&argv[1])) {
        py_i64 lhs = py_toint(&argv[0]);
        py_i64 rhs = py_toint(&argv[1]);
        if(rhs < 0) {
            if(lhs == 0) {
                return ZeroDivisionError("0.0 cannot be raised to a negative power");
            } else {
                py_newfloat(py_retval(), pow(lhs, rhs));
            }
        } else {
            // rhs >= 0
            py_i64 ret = 1;
            while(true) {
                if(rhs & 1) ret *= lhs;
                rhs >>= 1;
                if(!rhs) break;
                lhs *= lhs;  // place this here to avoid overflow
            }
            py_newint(py_retval(), ret);
        }
    } else {
        py_f64 lhs, rhs;
        if(!py_castfloat(&argv[0], &lhs)) return false;
        if(try_castfloat(&argv[1], &rhs)) {
            py_newfloat(py_retval(), pow(lhs, rhs));
        } else {
            py_newnotimplemented(py_retval());
        }
    }
    return true;
}

static py_i64 i64_abs(py_i64 x) { return x < 0 ? -x : x; }

static py_i64 cpy11__fast_floor_div(py_i64 a, py_i64 b) {
    assert(b != 0);
    if(a == 0) return 0;
    if((a < 0) == (b < 0)) {
        return i64_abs(a) / i64_abs(b);
    } else {
        return -1 - (i64_abs(a) - 1) / i64_abs(b);
    }
}

static py_i64 cpy11__fast_mod(py_i64 a, py_i64 b) {
    assert(b != 0);
    if(a == 0) return 0;
    py_i64 res;
    if((a < 0) == (b < 0)) {
        res = i64_abs(a) % i64_abs(b);
    } else {
        res = i64_abs(b) - 1 - (i64_abs(a) - 1) % i64_abs(b);
    }
    return b < 0 ? -res : res;
}

static bool int__floordiv__(int argc, py_Ref argv) {
    PY_CHECK_ARGC(2);
    py_i64 lhs = py_toint(&argv[0]);
    if(py_isint(&argv[1])) {
        py_i64 rhs = py_toint(&argv[1]);
        if(rhs == 0) return ZeroDivisionError("integer division by zero");
        py_newint(py_retval(), cpy11__fast_floor_div(lhs, rhs));
    } else {
        py_newnotimplemented(py_retval());
    }
    return true;
}

static bool int__mod__(int argc, py_Ref argv) {
    PY_CHECK_ARGC(2);
    py_i64 lhs = py_toint(&argv[0]);
    if(py_isint(&argv[1])) {
        py_i64 rhs = py_toint(&argv[1]);
        if(rhs == 0) return ZeroDivisionError("integer modulo by zero");
        py_newint(py_retval(), cpy11__fast_mod(lhs, rhs));
    } else {
        py_newnotimplemented(py_retval());
    }
    return true;
}

static bool int__divmod__(int argc, py_Ref argv) {
    PY_CHECK_ARGC(2);
    PY_CHECK_ARG_TYPE(1, tp_int);
    py_i64 lhs = py_toint(&argv[0]);
    py_i64 rhs = py_toint(&argv[1]);
    if(rhs == 0) return ZeroDivisionError("integer division or modulo by zero");
    py_Ref p = py_newtuple(py_retval(), 2);
    py_newint(&p[0], cpy11__fast_floor_div(lhs, rhs));
    py_newint(&p[1], cpy11__fast_mod(lhs, rhs));
    return true;
}

static bool int__invert__(int argc, py_Ref argv) {
    PY_CHECK_ARGC(1);
    py_i64 val = py_toint(&argv[0]);
    py_newint(py_retval(), ~val);
    return true;
}

static bool int_bit_length(int argc, py_Ref argv) {
    PY_CHECK_ARGC(1);
    py_i64 x = py_toint(py_arg(0));
    if(x < 0) x = -x;
    int bits = 0;
    while(x) {
        x >>= 1;
        bits++;
    }
    py_newint(py_retval(), bits);
    return true;
}

#define DEF_INT_BITWISE_OP(name, op)                                                               \
    static bool int##name(int argc, py_Ref argv) {                                                 \
        PY_CHECK_ARGC(2);                                                                          \
        py_i64 lhs = py_toint(&argv[0]);                                                           \
        if(py_isint(&argv[1])) {                                                                   \
            py_i64 rhs = py_toint(&argv[1]);                                                       \
            py_newint(py_retval(), lhs op rhs);                                                    \
        } else {                                                                                   \
            py_newnotimplemented(py_retval());                                                     \
        }                                                                                          \
        return true;                                                                               \
    }

DEF_INT_BITWISE_OP(__and__, &)
DEF_INT_BITWISE_OP(__or__, |)
DEF_INT_BITWISE_OP(__xor__, ^)
DEF_INT_BITWISE_OP(__lshift__, <<)
DEF_INT_BITWISE_OP(__rshift__, >>)

static bool int__repr__(int argc, py_Ref argv) {
    PY_CHECK_ARGC(1);
    py_i64 val = py_toint(&argv[0]);
    char buf[32];
    int size = snprintf(buf, sizeof(buf), "%lld", (long long)val);
    py_newstrv(py_retval(), (c11_sv){buf, size});
    return true;
}

static bool float__repr__(int argc, py_Ref argv) {
    PY_CHECK_ARGC(1);
    py_f64 val = py_tofloat(&argv[0]);
    c11_sbuf buf;
    c11_sbuf__ctor(&buf);
    c11_sbuf__write_f64(&buf, val, -1);
    c11_sbuf__py_submit(&buf, py_retval());
    return true;
}

union c11_8bytes {
    py_i64 _i64;
    py_f64 _f64;

    union {
        uint32_t upper;
        uint32_t lower;
    } bits;
};

static py_i64 c11_8bytes__hash(union c11_8bytes u) {
    // https://stackoverflow.com/questions/664014/what-integer-hash-function-are-good-that-accepts-an-integer-hash-key
    const uint32_t C = 2654435761;
    u.bits.upper *= C;
    u.bits.lower *= C;
    return u._i64;
}

static bool int__hash__(int argc, py_Ref argv) {
    PY_CHECK_ARGC(1);
    py_assign(py_retval(), argv);
    return true;
}

static bool float__hash__(int argc, py_Ref argv) {
    PY_CHECK_ARGC(1);
    py_f64 val = py_tofloat(&argv[0]);
    union c11_8bytes u = {._f64 = val};
    py_newint(py_retval(), c11_8bytes__hash(u));
    return true;
}

static bool int__abs__(int argc, py_Ref argv) {
    PY_CHECK_ARGC(1);
    py_i64 val = py_toint(&argv[0]);
    py_newint(py_retval(), val < 0 ? -val : val);
    return true;
}

static bool float__abs__(int argc, py_Ref argv) {
    PY_CHECK_ARGC(1);
    py_f64 val = py_tofloat(&argv[0]);
    py_newfloat(py_retval(), val < 0 ? -val : val);
    return true;
}

static bool int__new__(int argc, py_Ref argv) {
    if(argc == 1 + 0) {
        // int() == 0
        py_newint(py_retval(), 0);
        return true;
    }
    // 1 arg
    if(argc == 1 + 1) {
        switch(argv[1].type) {
            case tp_float: {
                // int(1.1) == 1
                py_newint(py_retval(), (py_i64)py_tofloat(&argv[1]));
                return true;
            }
            case tp_int: {
                // int(1) == 1
                *py_retval() = argv[1];
                return true;
            }
            case tp_bool: {
                // int(True) == 1
                py_newint(py_retval(), (py_i64)py_tobool(&argv[1]));
                return true;
            }
            case tp_str: break;  // leave to the next block
            default: return TypeError("int() argument must be a string, number or boolean");
        }
    }
    // 2+ args -> error
    if(argc > 1 + 2) return TypeError("int() takes at most 2 arguments");
    // 1 or 2 args with str
    int base = 10;
    if(argc == 1 + 2) {
        PY_CHECK_ARG_TYPE(2, tp_int);
        base = py_toint(py_arg(2));
    }

    PY_CHECK_ARG_TYPE(1, tp_str);

    c11_sv sv = py_tosv(py_arg(1));
    bool negative = false;
    if(sv.size && (sv.data[0] == '+' || sv.data[0] == '-')) {
        negative = sv.data[0] == '-';
        sv.data++;
        sv.size--;
    }
    py_i64 val;
    if(c11__parse_uint(sv, &val, base) != IntParsing_SUCCESS) {
        return ValueError("invalid literal for int() with base %d: %q", base, sv);
    }
    py_newint(py_retval(), negative ? -val : val);
    return true;
}

static bool float__new__(int argc, py_Ref argv) {
    if(argc == 1 + 0) {
        // float() == 0.0
        py_newfloat(py_retval(), 0.0);
        return true;
    }
    if(argc > 1 + 1) return TypeError("float() takes at most 1 argument");
    // 1 arg
    switch(argv[1].type) {
        case tp_int: {
            // float(1) == 1.0
            py_newfloat(py_retval(), py_toint(&argv[1]));
            return true;
        }
        case tp_float: {
            // float(1.1) == 1.1
            *py_retval() = argv[1];
            return true;
        }
        case tp_bool: {
            // float(True) == 1.0
            py_newfloat(py_retval(), py_tobool(&argv[1]));
            return true;
        }
        case tp_str: {
            // str to float
            c11_sv sv = py_tosv(py_arg(1));

            if(c11__sveq2(sv, "inf")) {
                py_newfloat(py_retval(), INFINITY);
                return true;
            }
            if(c11__sveq2(sv, "-inf")) {
                py_newfloat(py_retval(), -INFINITY);
                return true;
            }

            char* p_end;
            py_f64 float_out = strtod(sv.data, &p_end);
            if(p_end != sv.data + sv.size) return ValueError("invalid literal for float(): %q", sv);
            py_newfloat(py_retval(), float_out);
            return true;
        }
        default: return TypeError("float() argument must be a string or a real number");
    }
}

// tp_bool
static bool bool__new__(int argc, py_Ref argv) {
    assert(argc > 0);
    if(argc == 1) {
        py_newbool(py_retval(), false);
        return true;
    }
    if(argc == 2) {
        int res = py_bool(py_arg(1));
        if(res == -1) return false;
        py_newbool(py_retval(), res);
        return true;
    }
    return TypeError("bool() takes at most 1 argument");
}

static bool bool__hash__(int argc, py_Ref argv) {
    PY_CHECK_ARGC(1);
    bool res = py_tobool(argv);
    py_newint(py_retval(), res);
    return true;
}

static bool bool__repr__(int argc, py_Ref argv) {
    PY_CHECK_ARGC(1);
    bool res = py_tobool(argv);
    py_newstr(py_retval(), res ? "True" : "False");
    return true;
}

static bool bool__eq__(int argc, py_Ref argv) {
    PY_CHECK_ARGC(2);
    bool lhs = py_tobool(&argv[0]);
    if(argv[1].type == tp_bool) {
        bool rhs = py_tobool(&argv[1]);
        py_newbool(py_retval(), lhs == rhs);
    } else {
        py_newnotimplemented(py_retval());
    }
    return true;
}

static bool bool__ne__(int argc, py_Ref argv) {
    PY_CHECK_ARGC(2);
    bool lhs = py_tobool(&argv[0]);
    if(argv[1].type == tp_bool) {
        bool rhs = py_tobool(&argv[1]);
        py_newbool(py_retval(), lhs != rhs);
    } else {
        py_newnotimplemented(py_retval());
    }
    return true;
}

#define DEF_BOOL_BITWISE(name, op)                                                                 \
    static bool bool##name(int argc, py_Ref argv) {                                                \
        PY_CHECK_ARGC(2);                                                                          \
        bool lhs = py_tobool(&argv[0]);                                                            \
        if(argv[1].type == tp_bool) {                                                              \
            bool rhs = py_tobool(&argv[1]);                                                        \
            py_newbool(py_retval(), lhs op rhs);                                                   \
        } else {                                                                                   \
            py_newnotimplemented(py_retval());                                                     \
        }                                                                                          \
        return true;                                                                               \
    }

DEF_BOOL_BITWISE(__and__, &&)
DEF_BOOL_BITWISE(__or__, ||)
DEF_BOOL_BITWISE(__xor__, !=)

static bool bool__invert__(int argc, py_Ref argv) {
    PY_CHECK_ARGC(1);
    bool val = py_tobool(&argv[0]);
    py_newbool(py_retval(), !val);
    return true;
}

void pk_number__register() {
    /****** tp_int & tp_float ******/
    py_bindmagic(tp_int, __add__, int__add__);
    py_bindmagic(tp_float, __add__, float__add__);
    py_bindmagic(tp_int, __sub__, int__sub__);
    py_bindmagic(tp_float, __sub__, float__sub__);
    py_bindmagic(tp_int, __mul__, int__mul__);
    py_bindmagic(tp_float, __mul__, float__mul__);

    py_bindmagic(tp_int, __eq__, int__eq__);
    py_bindmagic(tp_float, __eq__, float__eq__);
    py_bindmagic(tp_int, __ne__, int__ne__);
    py_bindmagic(tp_float, __ne__, float__ne__);
    py_bindmagic(tp_int, __lt__, int__lt__);
    py_bindmagic(tp_float, __lt__, float__lt__);
    py_bindmagic(tp_int, __le__, int__le__);
    py_bindmagic(tp_float, __le__, float__le__);
    py_bindmagic(tp_int, __gt__, int__gt__);
    py_bindmagic(tp_float, __gt__, float__gt__);
    py_bindmagic(tp_int, __ge__, int__ge__);
    py_bindmagic(tp_float, __ge__, float__ge__);

    // __neg__
    py_bindmagic(tp_int, __neg__, int__neg__);
    py_bindmagic(tp_float, __neg__, float__neg__);

    // __repr__
    py_bindmagic(tp_int, __repr__, int__repr__);
    py_bindmagic(tp_float, __repr__, float__repr__);

    // __hash__
    py_bindmagic(tp_int, __hash__, int__hash__);
    py_bindmagic(tp_float, __hash__, float__hash__);

    // __abs__
    py_bindmagic(tp_int, __abs__, int__abs__);
    py_bindmagic(tp_float, __abs__, float__abs__);

    // __new__
    py_bindmagic(tp_int, __new__, int__new__);
    py_bindmagic(tp_float, __new__, float__new__);

    // __truediv__
    py_bindmagic(tp_int, __truediv__, int__truediv__);
    py_bindmagic(tp_float, __truediv__, float__truediv__);

    // __pow__
    py_bindmagic(tp_int, __pow__, number__pow__);
    py_bindmagic(tp_float, __pow__, number__pow__);

    // __floordiv__ & __mod__ & __divmod__
    py_bindmagic(tp_int, __floordiv__, int__floordiv__);
    py_bindmagic(tp_int, __mod__, int__mod__);
    py_bindmagic(tp_int, __divmod__, int__divmod__);

    // int.__invert__ & int.<BITWISE OP>
    py_bindmagic(tp_int, __invert__, int__invert__);

    py_bindmagic(tp_int, __and__, int__and__);
    py_bindmagic(tp_int, __or__, int__or__);
    py_bindmagic(tp_int, __xor__, int__xor__);
    py_bindmagic(tp_int, __lshift__, int__lshift__);
    py_bindmagic(tp_int, __rshift__, int__rshift__);

    // int.bit_length
    py_bindmethod(tp_int, "bit_length", int_bit_length);

    /* tp_bool */
    py_bindmagic(tp_bool, __new__, bool__new__);
    py_bindmagic(tp_bool, __hash__, bool__hash__);
    py_bindmagic(tp_bool, __repr__, bool__repr__);
    py_bindmagic(tp_bool, __eq__, bool__eq__);
    py_bindmagic(tp_bool, __ne__, bool__ne__);
    py_bindmagic(tp_bool, __and__, bool__and__);
    py_bindmagic(tp_bool, __or__, bool__or__);
    py_bindmagic(tp_bool, __xor__, bool__xor__);
    py_bindmagic(tp_bool, __invert__, bool__invert__);
}

#undef DEF_NUM_BINARY_OP
#undef DEF_INT_BITWISE_OP
#undef DEF_BOOL_BITWISE