/*
Copyright (C) 2004, The Perl Foundation.
$Id: bigint.pmc 16296 2006-12-28 19:58:45Z paultcochrane $

=head1 NAME

src/pmc/bigint.pmc - BigInt PMC class

=head1 DESCRIPTION

C<BigInt> provides arbitrary precision integer mathematic functions.

=head2 Functions

=over 4

=item C<static void bigint_set_long(Interp*, PMC*, long value)>

=item C<static void bigint_set_double(Interp*, PMC*, double value)>

=cut

*/

#include "parrot/parrot.h"

/*
 * TODO split that out into a separate file
 */

#ifdef PARROT_HAS_GMP
#  include <gmp.h>
typedef struct {
    mpz_t b;
} BIGINT;

#  define BN(x) ((BIGINT*)PMC_struct_val(x))->b

static void
bigint_init(Interp *interp, PMC *self) {
    PMC_struct_val(self) = malloc(sizeof (BIGINT));
    mpz_init(BN(self));
}
static void
bigint_clear(Interp *interp, PMC *self) {
    mpz_clear(BN(self));
}

static void
bigint_set(Interp *interp, PMC *dest, PMC *src) {
    mpz_set(BN(dest), BN(src));
}

static void
bigint_set_long(Interp *interp, PMC *self, long value) {
    mpz_set_si(BN(self), value);
}

static void
bigint_set_double(Interp *interp, PMC *self, double value) {
    mpz_set_d(BN(self), value);
}

static void
bigint_set_str(Interp *interp, PMC *self, char* value, int base) {
    mpz_set_str(BN(self), value, base);
}

static BIGNUM*
bigint_get_self(Interp *interp, PMC *self) {
    return PMC_struct_val(self);
}

static void
bigint_set_self(Interp *interp, PMC *self, BIGNUM *value) {
    mpz_set(BN(self), value);
}

static long
bigint_get_long(Interp *interp, PMC *self) {
    if (mpz_fits_slong_p(BN(self)))
        return mpz_get_si(BN(self));
    real_exception(interp, NULL, 1, "bigint_get_long: number too big");
    return 0;
}

static int
bigint_get_bool(Interp *interp, PMC *self) {
    if (mpz_sgn(BN(self)) != 0) {
        return 1;
    }
    else {
        return 0;
    }
}
static char *
bigint_get_string(Interp *interp, PMC *self, int base) {
    size_t n = mpz_sizeinbase(BN(self), base) + 2;
    char *s = mem_sys_allocate(n);
    return mpz_get_str(s, base, BN(self));
}

static double
bigint_get_double(Interp *interp, PMC *self) {
    return mpz_get_d( BN(self));
}

static void
bigint_add_bigint(Interp *interp, PMC* self, PMC *value, PMC *dest)
{
    mpz_add(BN(dest), BN(self), BN(value));
}

static void
bigint_add_bigint_int(Interp *interp, PMC* self, INTVAL value,
        PMC *dest)
{
    int n = value < 0;
    if (n) {
        mpz_sub_ui(BN(dest), BN(self), -value);
    }
    else
        mpz_add_ui(BN(dest), BN(self), value);
}
static void
bigint_sub_bigint(Interp *interp, PMC* self, PMC *value, PMC *dest)
{
    mpz_sub(BN(dest), BN(self), BN(value));
}

static void
bigint_sub_bigint_int(Interp *interp, PMC* self, INTVAL value,
        PMC *dest)
{
    int n = value < 0;
    if (n)
        mpz_add_ui(BN(dest), BN(self), -value);
    else
        mpz_sub_ui(BN(dest), BN(self), value);
}
static void
bigint_mul_bigint(Interp *interp, PMC* self, PMC *value, PMC *dest)
{
    mpz_mul(BN(dest), BN(self), BN(value));
}

static void
bigint_mul_bigint_int(Interp *interp, PMC* self, INTVAL value,
        PMC *dest)
{
    mpz_mul_si(BN(dest), BN(self), value);
}

static void
bigint_pow_bigint_int(Interp *interp, PMC* self, INTVAL value,
        PMC *dest)
{
    mpz_pow_ui(BN(dest), BN(self), value);
}

static void
int_check_divide_zero(Interp *interp, INTVAL value)
{
    if (value == 0) {
        real_exception(interp, NULL, E_ZeroDivisionError,
                       "Divide by zero");
    }
}

static void
bigint_check_divide_zero(Interp *interp, PMC *value)
{
    /* Throw an exception if we are dividing by zero. */
    if (mpz_cmp_si(BN(value), 0) == 0) {
        real_exception(interp, NULL, E_ZeroDivisionError,
                       "Divide by zero");
    }
}

static void
bigint_div_bigint(Interp *interp, PMC* self, PMC *value, PMC *dest)
{
    bigint_check_divide_zero(interp, value);
    /* this is mpz_fdiv_q */
    mpz_div(BN(dest), BN(self), BN(value));
}
static void
bigint_div_bigint_int(Interp *interp, PMC* self, INTVAL value, PMC *dest)
{
    int n;
    int_check_divide_zero(interp, value);
    /* this is mpz_fdiv_q */
    n = value < 0;
    if (n) {
        mpz_div_ui(BN(dest), BN(self), -value);
        mpz_neg(BN(dest), BN(dest));
    }
    else
        mpz_div_ui(BN(dest), BN(self), value);
}
static void
bigint_fdiv_bigint(Interp *interp, PMC* self, PMC *value, PMC *dest)
{
    bigint_check_divide_zero(interp, value);
    mpz_fdiv_q(BN(dest), BN(self), BN(value));
}
static void
bigint_fdiv_bigint_int(Interp *interp, PMC* self, INTVAL value, PMC *dest)
{
    int n;
    int_check_divide_zero(interp, value);
    n = value < 0;
    if (n) {
        mpz_fdiv_q_ui(BN(dest), BN(self), -value);
        mpz_neg(BN(dest), BN(dest));
    }
    else
        mpz_fdiv_q_ui(BN(dest), BN(self), value);
}

static void
bigint_mod_bigint(Interp *interp, PMC* self, PMC *value, PMC *dest)
{
    bigint_check_divide_zero(interp, value);
    mpz_mod(BN(dest), BN(self), BN(value));
}

static void
bigint_mod_bigint_int(Interp *interp, PMC* self, INTVAL value, PMC *dest)
{
    int n;
    int_check_divide_zero(interp, value);
    n = value < 0;
    if (n) {
        mpz_mod_ui(BN(dest), BN(self), -value);
    }
    else
        mpz_mod_ui(BN(dest), BN(self), value);
}
static INTVAL
bigint_cmp(Interp *interp, PMC* self, PMC *value)
{
    return mpz_cmp(BN(self), BN(value));
}

static INTVAL
bigint_cmp_int(Interp *interp, PMC* self, INTVAL value)
{
    return mpz_cmp_si(BN(self), value);
}

static void
bigint_abs(Interp *interp, PMC* self, PMC *dest)
{
    VTABLE_morph(interp, dest, enum_class_BigInt);
    mpz_abs(BN(dest), BN(self));
}

static void
bigint_neg(Interp *interp, PMC* self, PMC *dest)
{
    VTABLE_morph(interp, dest, enum_class_BigInt);
    mpz_neg(BN(dest), BN(self));
}

static void
bigint_bitwise_shl_bigint_int(Interp *interp, PMC* self,
                              INTVAL value, PMC *dest)
{
    mpz_mul_2exp(BN(dest), BN(self), value);
}
static void
bigint_bitwise_shr_bigint_int(Interp *interp, PMC* self,
                              INTVAL value, PMC *dest)
{
    mpz_tdiv_q_2exp(BN(dest), BN(self), value);
}

#else /* ifdef PARROT_HAS_GMP */

static void
bigint_init(Interp *interp, PMC *self) {
    real_exception(interp, NULL, E_LibraryNotLoadedError,
        "no bigint lib loaded");
}
static void
bigint_clear(Interp *interp, PMC *self) {
    real_exception(interp, NULL, E_LibraryNotLoadedError,
        "no bigint lib loaded");
}
static void
bigint_set_long(Interp *interp, PMC *self, long value) {
    real_exception(interp, NULL, E_LibraryNotLoadedError,
        "no bigint lib loaded");
}
static void
bigint_set(Interp *interp, PMC *dest, PMC *src) {
    real_exception(interp, NULL, E_LibraryNotLoadedError,
        "no bigint lib loaded");
}
static void
bigint_set_double(Interp *interp, PMC *self, double value) {
    real_exception(interp, NULL, E_LibraryNotLoadedError,
        "no bigint lib loaded");
}
static void
bigint_set_str(Interp *interp, PMC *self, char* value, int base) {
    real_exception(interp, NULL, E_LibraryNotLoadedError,
        "no bigint lib loaded");
}
static void
bigint_set_self(Interp *interp, PMC *self, BIGNUM *value) {
    real_exception(interp, NULL, E_LibraryNotLoadedError,
        "no bigint lib loaded");
}
static BIGNUM*
bigint_get_self(Interp *interp, PMC *self) {
    real_exception(interp, NULL, E_LibraryNotLoadedError,
        "no bigint lib loaded");
    return NULL;
}

static char *
bigint_get_string(Interp *interp, PMC *self, int base) {
    real_exception(interp, NULL, E_LibraryNotLoadedError,
        "no bigint lib loaded");
    return NULL;
}

static long
bigint_get_long(Interp *interp, PMC *self) {
    real_exception(interp, NULL, E_LibraryNotLoadedError,
        "no bigint lib loaded");
    return 0L;
}
static long
bigint_get_bool(Interp *interp, PMC *self) {
    real_exception(interp, NULL, E_LibraryNotLoadedError,
        "no bigint lib loaded");
    return 0L;
}
static double
bigint_get_double(Interp *interp, PMC *self) {
    real_exception(interp, NULL, E_LibraryNotLoadedError,
        "no bigint lib loaded");
    return 0.0;
}
static void
bigint_add_bigint(Interp *interp, PMC* self, PMC *value, PMC *dest)
{
    real_exception(interp, NULL, E_LibraryNotLoadedError,
        "no bigint lib loaded");
}
static void
bigint_add_bigint_int(Interp *interp, PMC* self, INTVAL value, PMC *dest)
{
    real_exception(interp, NULL, E_LibraryNotLoadedError,
        "no bigint lib loaded");
}
static void
bigint_sub_bigint(Interp *interp, PMC* self, PMC *value, PMC *dest)
{
    real_exception(interp, NULL, E_LibraryNotLoadedError,
        "no bigint lib loaded");
}
static void
bigint_sub_bigint_int(Interp *interp, PMC* self, INTVAL value, PMC *dest)
{
    real_exception(interp, NULL, E_LibraryNotLoadedError,
        "no bigint lib loaded");
}
static void
bigint_mul_bigint(Interp *interp, PMC* self, PMC *value, PMC *dest)
{
    real_exception(interp, NULL, E_LibraryNotLoadedError,
        "no bigint lib loaded");
}
static void
bigint_mul_bigint_int(Interp *interp, PMC* self, INTVAL value,
        PMC *dest)
{
    real_exception(interp, NULL, E_LibraryNotLoadedError,
        "no bigint lib loaded");
}
static void
bigint_pow_bigint_int(Interp *interp, PMC* self, INTVAL value,
        PMC *dest)
{
    real_exception(interp, NULL, E_LibraryNotLoadedError,
        "no bigint lib loaded");
}
static void
bigint_div_bigint(Interp *interp, PMC* self, PMC *value, PMC *dest)
{
    real_exception(interp, NULL, E_LibraryNotLoadedError,
        "no bigint lib loaded");
}
static void
bigint_div_bigint_int(Interp *interp, PMC* self, INTVAL value, PMC *dest)
{
    real_exception(interp, NULL, E_LibraryNotLoadedError,
        "no bigint lib loaded");
}
static void
bigint_fdiv_bigint(Interp *interp, PMC* self, PMC *value, PMC *dest)
{
    real_exception(interp, NULL, E_LibraryNotLoadedError,
        "no bigint lib loaded");
}
static void
bigint_fdiv_bigint_int(Interp *interp, PMC* self, INTVAL value, PMC *dest)
{
    real_exception(interp, NULL, E_LibraryNotLoadedError,
        "no bigint lib loaded");
}
static void
bigint_mod_bigint(Interp *interp, PMC* self, PMC *value, PMC *dest)
{
    real_exception(interp, NULL, E_LibraryNotLoadedError,
        "no bigint lib loaded");
}
static void
bigint_mod_bigint_int(Interp *interp, PMC* self, INTVAL value, PMC *dest)
{
    real_exception(interp, NULL, E_LibraryNotLoadedError,
        "no bigint lib loaded");
}
static INTVAL
bigint_cmp(Interp *interp, PMC* self, PMC *value)
{
    real_exception(interp, NULL, E_LibraryNotLoadedError,
        "no bigint lib loaded");
    return 0;
}
static INTVAL
bigint_cmp_int(Interp *interp, PMC* self, INTVAL value)
{
    real_exception(interp, NULL, E_LibraryNotLoadedError,
        "no bigint lib loaded");
    return 0;
}
static void
bigint_abs(Interp *interp, PMC* self, PMC *dest)
{
    real_exception(interp, NULL, E_LibraryNotLoadedError,
        "no bigint lib loaded");
}
static void
bigint_neg(Interp *interp, PMC* self, PMC *dest)
{
    real_exception(interp, NULL, E_LibraryNotLoadedError,
        "no bigint lib loaded");
}
static void
bigint_bitwise_shl_bigint(Interp *interp, PMC* self, PMC *value, PMC *dest)
{
    real_exception(interp, NULL, E_LibraryNotLoadedError,
        "no bigint lib loaded");
}
static void
bigint_bitwise_shl_bigint_int(Interp *interp, PMC* self,
                              INTVAL value, PMC *dest)
{
    real_exception(interp, NULL, E_LibraryNotLoadedError,
        "no bigint lib loaded");
}

static void
bigint_bitwise_shr_bigint(Interp *interp, PMC* self, PMC *value, PMC *dest)
{
    real_exception(interp, NULL, E_LibraryNotLoadedError,
        "no bigint lib loaded");
}
static void
bigint_bitwise_shr_bigint_int(Interp *interp, PMC* self,
                              INTVAL value, PMC *dest)
{
    real_exception(interp, NULL, E_LibraryNotLoadedError,
        "no bigint lib loaded");
}
#endif /* ifdef PARROT_HAS_GMP */

pmclass BigInt {

/*

=back

=head2 Methods

=over 4

=item C<METHOD STRING* version()>

Return GMP version string "x.y.z".

=cut

*/

    METHOD STRING* version() {
#ifdef PARROT_HAS_GMP
        return string_from_cstring(INTERP, gmp_version, 0);
#else
        return const_string(INTERP, "0.0.0");
#endif
    }

/*

=item C<PMC* instantiate(PMC* sig)>

Object constructor. SELF is a BigInt Class object. Return a new
BigInt object.

=cut

*/
    PMC* instantiate(PMC* sig) {
        int argcP = REG_INT(3);
        int base;
        PMC *res;
        STRING *num;

        return PMCNULL;  /* TODO */

        res = pmc_new(INTERP, enum_class_BigInt);
        if (!argcP) {
            return res;
        }
        base = 10;
        if (argcP == 2)
            base = VTABLE_get_integer(INTERP, REG_PMC(6));
        num = VTABLE_get_string(INTERP, REG_PMC(5));
        VTABLE_set_string_keyed_int(INTERP, res, base, num);
        return res;
    }

    void morph(INTVAL new_type) {
        pmc_reuse(INTERP, SELF, new_type, 0);
    }

    void init() {
        bigint_init(INTERP, SELF);
        PObj_active_destroy_SET(SELF);
    }

    PMC* clone() {
        PMC *res;
        res = pmc_new(INTERP, SELF->vtable->base_type);
        bigint_set(INTERP, res, SELF);
        return res;
    }

    void destroy() {
        bigint_clear(INTERP, SELF);
        mem_sys_free(PMC_struct_val(SELF));
    }

/*

=item C<void set_integer_native(INTVAL value)>

=cut

*/

    void set_integer_native(INTVAL value) {
        bigint_set_long(INTERP, SELF, (long)value);
    }

/*

=item C<void set_number_native(FLOATVAL value)>

Sets the value of the bigint to C<value>.

=cut

*/

    void set_number_native(FLOATVAL value) {
        bigint_set_double(INTERP, SELF, (double)value);
    }

/*

=item C<void set_string_native(STRING* value)>

Sets the value of the integer to the result of converting C<*value> to a
number.

=item C<void set_string_keyed_int(INTVAL base, STRING* value)>

Same assume number base C<base>.

=cut

*/

    void set_string_native(STRING* value) {
        char *s = string_to_cstring(INTERP, value);
        bigint_set_str(INTERP, SELF, s, 10);
        string_cstring_free(s);
    }

    void set_string_keyed_int(INTVAL base, STRING* value) {
        char *s = string_to_cstring(INTERP, value);
        bigint_set_str(INTERP, SELF, s, base);
        string_cstring_free(s);
    }
/*

=item C<void set_pmc(PMC *value)>

Sets the value of the integer to the integer value of C<*value>.

=cut

*/

    void set_pmc(PMC *value) {
        bigint_set_self(INTERP, SELF, bigint_get_self(INTERP, value));
    }

/*

=item C<FLOATVAL get_number()>

Returns the value of the integer as a floating point number.

=cut

*/

    FLOATVAL get_number() {
        return bigint_get_double(INTERP, SELF);
    }

/*

=item C<INTVAL get_integer()>

Returns the value of the integer.

=cut

*/

    INTVAL get_integer() {
        return bigint_get_long(INTERP, SELF);
    }

/*

=item C<PMC* get_bignum()>

Returns SELF

=cut

*/

    PMC* get_bignum() {
        return SELF;
    }

/*

=item C<INTVAL get_bool()>

Returns the boolean value of the integer.

=cut

*/

    INTVAL get_bool() {
        return bigint_get_bool(INTERP, SELF);
    }

/*

=item C<STRING* get_string()>

Returns the string representation of the integer.

=item C<STRING* get_string_keyed_int(INTVAL base)>

Returns the string representation of the integer in base C<base>.

=item C<STRING* get_repr()>

Returns the string representation of the integer with the letter 'L'
appended.

=cut

*/

    STRING* get_string() {
        char *s = bigint_get_string(INTERP, SELF, 10);
        STRING *ps = string_from_cstring(INTERP, s, 0);
        mem_sys_free(s);
        return ps;
    }

    STRING* get_string_keyed_int(INTVAL base) {
        char *s = bigint_get_string(INTERP, SELF, base);
        STRING *ps = string_from_cstring(INTERP, s, 0);
        mem_sys_free(s);
        return ps;
    }
    STRING* get_repr() {
        STRING *s = SELF.get_string();
        return string_append(INTERP, s, const_string(INTERP, "L"));
    }
/*

=item C<void increment()>

Increments the integer.

=cut

*/

    void increment() {
        bigint_add_bigint_int(INTERP, SELF, 1, SELF);
    }

/*

=item C<void decrement()>

Decrements the integer.

=cut

*/

    void decrement() {
        bigint_sub_bigint_int(INTERP, SELF, 1, SELF);
    }

    PMC* add(PMC* value, PMC* dest) {
MMD_BigInt: {
                if (dest)
                    VTABLE_morph(interp, dest, SELF->vtable->base_type);
                else
                    dest = pmc_new(INTERP, SELF->vtable->base_type);
                bigint_add_bigint(INTERP, SELF, value, dest);
                return dest;
            }
MMD_Integer: {
                if (dest)
                    VTABLE_morph(interp, dest, SELF->vtable->base_type);
                else
                    dest = pmc_new(INTERP, SELF->vtable->base_type);
                bigint_add_bigint_int(INTERP, SELF, PMC_int_val(value), dest);
                return dest;
            }
MMD_DEFAULT: {
            real_exception(INTERP, NULL, E_NotImplementedError, "unimp add");
            return dest;
        }
    }

    PMC* add_int(INTVAL value, PMC* dest) {
        if (dest)
            VTABLE_morph(interp, dest, SELF->vtable->base_type);
        else
            dest = pmc_new(INTERP, SELF->vtable->base_type);
        bigint_add_bigint_int(INTERP, SELF, value, dest);
        return dest;
    }

    void i_add(PMC* value) {
MMD_BigInt: {
                bigint_add_bigint(INTERP, SELF, value, SELF);
            }
MMD_Integer: {
                bigint_add_bigint_int(INTERP, SELF, PMC_int_val(value), SELF);
             }
MMD_DEFAULT: {
            real_exception(INTERP, NULL, E_NotImplementedError, "unimp add");
        }
    }

    void i_add_int(INTVAL value) {
        bigint_add_bigint_int(INTERP, SELF, value, SELF);
    }

    void i_add_float(FLOATVAL value) {
        real_exception(INTERP, NULL, E_NotImplementedError, "unimp add");
    }


    PMC* subtract(PMC* value, PMC* dest) {
MMD_BigInt: {
                if (dest)
                    VTABLE_morph(interp, dest, SELF->vtable->base_type);
                else
                    dest = pmc_new(INTERP, SELF->vtable->base_type);
                bigint_sub_bigint(INTERP, SELF, value, dest);
                return dest;
            }
MMD_Integer: {
                if (dest)
                    VTABLE_morph(interp, dest, SELF->vtable->base_type);
                else
                    dest = pmc_new(INTERP, SELF->vtable->base_type);
                bigint_sub_bigint_int(INTERP, SELF, PMC_int_val(value), dest);
                return dest;
            }
MMD_DEFAULT: {
            real_exception(INTERP, NULL, E_NotImplementedError, "unimp sub");
            return dest;
        }
    }

    PMC* subtract_int(INTVAL value, PMC* dest) {
        if (dest)
            VTABLE_morph(interp, dest, SELF->vtable->base_type);
        else
            dest = pmc_new(INTERP, SELF->vtable->base_type);
        bigint_sub_bigint_int(INTERP, SELF, value, dest);
        return dest;
    }

    void i_subtract(PMC* value) {
MMD_BigInt: {
                bigint_sub_bigint(INTERP, SELF, value, SELF);
            }
MMD_Integer: {
                bigint_sub_bigint_int(INTERP, SELF, PMC_int_val(value), SELF);
             }
MMD_DEFAULT: {
            real_exception(INTERP, NULL, E_NotImplementedError, "unimp sub");
        }
    }

    void i_subtract_int(INTVAL value) {
        bigint_sub_bigint_int(INTERP, SELF, value, SELF);
    }

    void i_subtract_float(FLOATVAL value) {
        real_exception(INTERP, NULL, E_NotImplementedError, "unimp sub");
    }


    PMC* multiply(PMC* value, PMC* dest) {
MMD_BigInt: {
            if (dest)
                VTABLE_morph(interp, dest, SELF->vtable->base_type);
            else
                dest = pmc_new(INTERP, SELF->vtable->base_type);
            bigint_mul_bigint(INTERP, SELF, value, dest);
            return dest;
        }
MMD_Integer: {
            if (dest)
                VTABLE_morph(interp, dest, SELF->vtable->base_type);
            else
                dest = pmc_new(INTERP, SELF->vtable->base_type);
            bigint_mul_bigint_int(INTERP, SELF, PMC_int_val(value), dest);
            return dest;
         }
MMD_DEFAULT: {
             real_exception(INTERP, NULL, E_NotImplementedError, "unimp mul");
            return dest;
         }
    }

    PMC* multiply_int(INTVAL value, PMC* dest) {
        if (dest)
            VTABLE_morph(interp, dest, SELF->vtable->base_type);
        else
                dest = pmc_new(INTERP, SELF->vtable->base_type);
        bigint_mul_bigint_int(INTERP, SELF, value, dest);
        return dest;
    }

    void i_multiply(PMC* value) {
MMD_BigInt: {
                bigint_mul_bigint(INTERP, SELF, value, SELF);
            }
MMD_Integer: {
                bigint_mul_bigint_int(INTERP, SELF, PMC_int_val(value), SELF);
             }
MMD_DEFAULT: {
            real_exception(INTERP, NULL, E_NotImplementedError, "unimp mul");
        }
    }

    void i_multiply_int(INTVAL value) {
        bigint_mul_bigint_int(INTERP, SELF, value, SELF);
    }

    void i_multiply_float(FLOATVAL value) {
        real_exception(INTERP, NULL, E_NotImplementedError, "unimp mul");
    }

    PMC* pow_int(INTVAL value, PMC* dest) {
        if (dest)
            VTABLE_morph(interp, dest, SELF->vtable->base_type);
        else
            dest = pmc_new(INTERP, SELF->vtable->base_type);
        bigint_pow_bigint_int(INTERP, SELF, value, dest);
        return dest;
    }

    PMC* pow(PMC* value, PMC* dest) {
        /* XXX only Integer RHS currently */
        INTVAL r = VTABLE_get_integer(INTERP, value);
        return SELF.pow_int(r, dest);
    }

    PMC* divide(PMC* value, PMC* dest) {
MMD_BigInt: {
            if (dest)
                VTABLE_morph(interp, dest, SELF->vtable->base_type);
            else
                dest = pmc_new(INTERP, SELF->vtable->base_type);
            bigint_div_bigint(INTERP, SELF, value, dest);
#if 0
            /* to downgrade or not that's the question */
            if (mpz_fits_slong_p(BN(dest))) {
                long iresult = mpz_get_si(BN(dest));
                VTABLE_morph(interp, dest, enum_class_Integer);
                VTABLE_set_integer_native(interp, dest, iresult);
            }
#endif
            return dest;
        }
MMD_Integer: {
            if (dest)
                VTABLE_morph(interp, dest, SELF->vtable->base_type);
            else
                dest = pmc_new(INTERP, SELF->vtable->base_type);
            bigint_div_bigint_int(INTERP, SELF, PMC_int_val(value), dest);
            return dest;
        }
MMD_DEFAULT: {
            real_exception(INTERP, NULL, E_NotImplementedError, "unimp div");
            return dest;
         }
    }

    PMC* divide_int(INTVAL value, PMC* dest) {
        if (dest)
            VTABLE_morph(interp, dest, SELF->vtable->base_type);
        else
            dest = pmc_new(INTERP, SELF->vtable->base_type);
        bigint_div_bigint_int(INTERP, SELF, value, dest);
        return dest;
    }

    void i_divide(PMC* value) {
MMD_BigInt: {
            bigint_div_bigint(INTERP, SELF, value, SELF);
        }
MMD_Integer: {
            bigint_div_bigint_int(INTERP, SELF, PMC_int_val(value), SELF);
        }
MMD_DEFAULT: {
             real_exception(INTERP, NULL, E_NotImplementedError, "unimp fdiv");
        }
    }

    void i_divide_int(INTVAL value) {
        bigint_div_bigint_int(INTERP, SELF, value, SELF);
    }

    PMC* floor_divide(PMC* value, PMC* dest) {
MMD_BigInt: {
            if (dest)
                VTABLE_morph(interp, dest, SELF->vtable->base_type);
            else
                dest = pmc_new(INTERP, SELF->vtable->base_type);
            bigint_fdiv_bigint(INTERP, SELF, value, dest);
            return dest;
        }
MMD_Integer: {
            if (dest)
                VTABLE_morph(interp, dest, SELF->vtable->base_type);
            else
                dest = pmc_new(INTERP, SELF->vtable->base_type);
            bigint_fdiv_bigint_int(INTERP, SELF, PMC_int_val(value), dest);
            return dest;
        }
MMD_DEFAULT: {
            real_exception(INTERP, NULL, E_NotImplementedError, "unimp fdiv");
            return dest;
        }
    }

    PMC* floor_divide_int(INTVAL value, PMC* dest) {
        if (dest)
            VTABLE_morph(interp, dest, SELF->vtable->base_type);
        else
            dest = pmc_new(INTERP, SELF->vtable->base_type);
        bigint_fdiv_bigint_int(INTERP, SELF, value, dest);
        return dest;
    }

    void i_floor_divide(PMC* value) {
MMD_BigInt: {
            bigint_fdiv_bigint(INTERP, SELF, value, SELF);
        }
MMD_Integer: {
            bigint_fdiv_bigint_int(INTERP, SELF, PMC_int_val(value), SELF);
        }
MMD_DEFAULT: {
            real_exception(INTERP, NULL, E_NotImplementedError, "unimp fdiv");
        }
    }

    void i_floor_divide_int(INTVAL value) {
        bigint_fdiv_bigint_int(INTERP, SELF, value, SELF);
    }

    PMC* modulus(PMC* value, PMC* dest) {
MMD_BigInt: {
            if (dest)
                VTABLE_morph(interp, dest, SELF->vtable->base_type);
            else
                dest = pmc_new(INTERP, SELF->vtable->base_type);
            bigint_mod_bigint(INTERP, SELF, value, dest);
            return dest;
        }
MMD_Integer: {
            if (dest)
                VTABLE_morph(interp, dest, SELF->vtable->base_type);
            else
                dest = pmc_new(INTERP, SELF->vtable->base_type);
            bigint_mod_bigint_int(INTERP, SELF, PMC_int_val(value), dest);
            return dest;
         }
MMD_DEFAULT: {
            real_exception(INTERP, NULL, E_NotImplementedError, "unimp mod");
            return dest;
        }
    }

    PMC* cmodulus(PMC* value, PMC* dest) {
MMD_BigInt: {
                return Parrot_BigInt_modulus_BigInt(INTERP, SELF, value, dest);
            }
MMD_Integer: {
                return Parrot_BigInt_modulus_Integer(INTERP, SELF, value, dest);
             }
MMD_DEFAULT: {
                return Parrot_BigInt_modulus(INTERP, SELF, value, dest);
             }
    }

    void i_modulus(PMC* value) {
MMD_BigInt: {
                bigint_mod_bigint(INTERP, SELF, value, SELF);
            }
MMD_Integer: {
                bigint_mod_bigint_int(INTERP, SELF, PMC_int_val(value), SELF);
             }
MMD_DEFAULT: {
            real_exception(INTERP, NULL, E_NotImplementedError, "unimp mod");
        }
    }

    void i_cmodulus(PMC* value) {
MMD_BigInt: {
                bigint_mod_bigint(INTERP, SELF, value, SELF);
            }
MMD_Integer: {
                bigint_mod_bigint_int(INTERP, SELF, PMC_int_val(value), SELF);
             }
MMD_DEFAULT: {
            real_exception(INTERP, NULL, E_NotImplementedError, "unimp cmod");
        }
    }


    INTVAL cmp(PMC* value) {
MMD_BigInt: {
        return bigint_cmp(INTERP, SELF, value);
            }
MMD_Integer: {
        return bigint_cmp_int(INTERP, SELF, PMC_int_val(value));
            }
MMD_DEFAULT: {
            real_exception(INTERP, NULL, E_NotImplementedError, "unimp cmp");
            return 0;
        }
    }

    INTVAL is_equal(PMC* value) {
MMD_BigInt: {
        return bigint_cmp(INTERP, SELF, value) == 0;
            }
MMD_Integer: {
        return bigint_cmp_int(INTERP, SELF, PMC_int_val(value)) == 0;
            }
MMD_DEFAULT: {
            real_exception(INTERP, NULL, E_NotImplementedError, "unimp eq");
            return 0;
        }
    }

/*

=item C<PMC* absolute(PMC* dest)>

=item C<void i_absolute()>

Sets C<dest> to the absolute value of SELF.

=cut

*/

    PMC* absolute(PMC *dest) {
        if (!dest)
            dest = pmc_new(INTERP, SELF->vtable->base_type);
        bigint_abs(INTERP, SELF, dest);
        return dest;
    }

    void i_absolute() {
        bigint_abs(INTERP, SELF, SELF);
    }


/*

=item C<PMC* neg(PMC *dest)>

=item C<void i_neg()>

Set C<dest> to the negated value of C<SELF>.

=cut

*/

    PMC* neg(PMC* dest) {
        if (!dest)
            dest = pmc_new(INTERP, SELF->vtable->base_type);
        bigint_neg(INTERP, SELF, dest);
        return dest;
    }

    void i_neg() {
        bigint_neg(INTERP, SELF, SELF);
    }

/*

=item C<PMC* bitwise_shl(PMC *value, PMC *dest)>

=item C<PMC* bitwise_shl_int(INTVAL value, PMC *dest)>

Returns in C<*dest> the shift left of the BigInt by C<*value>.

=item C<void i_bitwise_shl(PMC *value)>

=item C<void i_bitwise_shl_int(INTVAL value)>

Inplace shift left.

=cut

*/

    PMC* bitwise_shl(PMC* value, PMC* dest) {
MMD_BigInt: {
            if (dest)
                VTABLE_morph(interp, dest, SELF->vtable->base_type);
            else
                dest = pmc_new(INTERP, SELF->vtable->base_type);
            bigint_bitwise_shl_bigint_int(INTERP, SELF,
                                          VTABLE_get_integer(INTERP, value),
                                          dest);
            return dest;
        }
MMD_Integer: {
            if (dest)
                VTABLE_morph(interp, dest, SELF->vtable->base_type);
            else
                dest = pmc_new(INTERP, SELF->vtable->base_type);
            bigint_bitwise_shl_bigint_int(INTERP, SELF, PMC_int_val(value),
                                          dest);
            return dest;
         }
MMD_DEFAULT: {
            real_exception(INTERP, NULL, E_NotImplementedError, "unimp shl");
            return dest;
         }
    }

    PMC* bitwise_shl_int(INTVAL value, PMC* dest) {
        if (dest)
            VTABLE_morph(interp, dest, SELF->vtable->base_type);
        else
            dest = pmc_new(INTERP, SELF->vtable->base_type);
        bigint_bitwise_shl_bigint_int(INTERP, SELF, value, dest);
        return dest;
    }


    void i_bitwise_shl(PMC* value) {
MMD_BigInt: {
            bigint_bitwise_shl_bigint_int(INTERP, SELF,
                                          VTABLE_get_integer(INTERP, value),
                                          SELF);
            }
MMD_Integer: {
            bigint_bitwise_shl_bigint_int(INTERP, SELF, PMC_int_val(value),
                                          SELF);
             }
MMD_DEFAULT: {
            real_exception(INTERP, NULL, E_NotImplementedError, "unimp shl");
        }
    }

    void i_bitwise_shl_int(INTVAL value) {
        bigint_bitwise_shl_bigint_int(INTERP, SELF, value, SELF);
    }

/*

=item C<PMC* bitwise_shr(PMC *value, PMC *dest)>

=item C<PMC* bitwise_shr_int(INTVAL value, PMC *dest)>

Returns in C<*dest> the shift right of the BigInt by C<*value>.

=item C<void i_bitwise_shr(PMC *value)>

=item C<void i_bitwise_shr_int(INTVAL value)>

Inplace shift left.

=cut

*/

    PMC* bitwise_shr(PMC* value, PMC* dest) {
MMD_BigInt: {
            if (dest)
                VTABLE_morph(interp, dest, SELF->vtable->base_type);
            else
                dest = pmc_new(INTERP, SELF->vtable->base_type);
            bigint_bitwise_shr_bigint_int(INTERP, SELF,
                                          VTABLE_get_integer(INTERP, value),
                                          dest);
            return dest;
        }
MMD_Integer: {
            if (dest)
                VTABLE_morph(interp, dest, SELF->vtable->base_type);
            else
                dest = pmc_new(INTERP, SELF->vtable->base_type);
            bigint_bitwise_shr_bigint_int(INTERP, SELF, PMC_int_val(value),
                                          dest);
            return dest;
         }
MMD_DEFAULT: {
            real_exception(INTERP, NULL, E_NotImplementedError, "unimp shr");
            return dest;
         }
    }

    PMC* bitwise_shr_int(INTVAL value, PMC* dest) {
        if (dest)
            VTABLE_morph(interp, dest, SELF->vtable->base_type);
        else
            dest = pmc_new(INTERP, SELF->vtable->base_type);
        bigint_bitwise_shr_bigint_int(INTERP, SELF, value, dest);
        return dest;
    }


    void i_bitwise_shr(PMC* value) {
MMD_BigInt: {
            bigint_bitwise_shr_bigint_int(INTERP, SELF,
                                          VTABLE_get_integer(INTERP, value),
                                          SELF);
            }
MMD_Integer: {
            bigint_bitwise_shr_bigint_int(INTERP, SELF, PMC_int_val(value),
                                          SELF);
        }
MMD_DEFAULT: {
            real_exception(INTERP, NULL, E_NotImplementedError, "unimp shr");
        }
    }

    void i_bitwise_shr_int(INTVAL value) {
        bigint_bitwise_shr_bigint_int(INTERP, SELF, value, SELF);
    }

}

/*

=back

=cut

*/

/*
 * Local variables:
 *   c-file-style: "parrot"
 * End:
 * vim: expandtab shiftwidth=4:
 */
