package Sidef::Types::Number::Fraction;

use utf8;
use 5.016;

use parent qw(
  Sidef::Types::Number::Number
);

use overload
  q{bool} => \&to_n,
  q{0+}   => \&to_n,
  q{""}   => \&__stringify__,
  q{${}}  => \&to_n;

sub new {
    my (undef, $n, $m) = @_;

    # Handle evaluation of polynomials
    if (ref($_[0]) eq __PACKAGE__) {
        return $_[0]->eval($n);
    }

    $n //= Sidef::Types::Number::Number::ZERO;
    $m //= Sidef::Types::Number::Number::ONE;

    bless {a => $n, b => $m};
}

*call = \&new;

sub with_value {
    my ($self, $value_1, $value_2) = @_;
    __PACKAGE__->new($value_1, $value_2);
}

sub stringify {
    my ($x) = @_;
    Sidef::Types::String::String->new(join('/', join('', '(', $x->{a}->stringify, ')'), join('', '(', $x->{b}->stringify, ')')));
}

*pretty = \&stringify;

sub is_nan {
    my ($x) = @_;
    if ($x->{a}->is_zero and $x->{b}->is_zero) {
        return Sidef::Types::Bool::Bool::TRUE;
    }
    $x->{a}->is_nan or $x->{b}->is_nan;
}

sub is_real {
    my ($x) = @_;
    if ($x->{a}->is_zero and $x->{b}->is_zero) {
        return Sidef::Types::Bool::Bool::FALSE;
    }
    $x->{a}->is_real and $x->{b}->is_real;
}

sub eval {
    my ($x, $v) = @_;
    $x->{a}->eval($v)->div($x->{b}->eval($v));
}

sub lift {
    my ($x) = @_;
    __PACKAGE__->new($x->{a}->lift, $x->{b}->lift);
}

sub to_n {
    my ($x) = @_;
    my $r = $x->{a}->to_n->div($x->{b}->to_n);

    if (ref($r) ne 'Sidef::Types::Number::Number') {
        return $r->to_n;
    }

    return $r;
}

*__boolify__ = \&to_n;
*__numify__  = \&to_n;

sub __stringify__ {
    my ($x) = @_;
    'Fraction(' . join(', ', $x->{a}->dump, $x->{b}->dump) . ')';
}

sub to_s {
    my ($x) = @_;
    Sidef::Types::String::String->new($x->__stringify__);
}

sub dump {
    my ($x) = @_;
    Sidef::Types::String::String->new($x->__stringify__);
}

sub nu {
    $_[0]->{a};
}

*num       = \&nu;
*numerator = \&nu;

sub de {
    $_[0]->{b};
}

*den         = \&de;
*denominator = \&de;

sub nude {
    ($_[0]->{a}, $_[0]->{b});
}

sub parts {
    Sidef::Types::Array::Array->new([$_[0]->nude]);
}

sub neg {
    my ($x) = @_;
    __PACKAGE__->new($x->{a}->neg, $x->{b});
}

sub dec {
    my ($x) = @_;
    __PACKAGE__->new($x->{a}->sub($x->{b}), $x->{b},);
}

sub inc {
    my ($x) = @_;
    __PACKAGE__->new($x->{a}->add($x->{b}), $x->{b},);
}

sub is_zero {
    my ($x) = @_;
    $x->eq(Sidef::Types::Number::Number::ZERO);
}

sub is_one {
    my ($x) = @_;
    $x->eq(Sidef::Types::Number::Number::ONE);
}

sub is_mone {
    my ($x) = @_;
    $x->eq(Sidef::Types::Number::Number::MONE);
}

sub is_proper {
    my ($x) = @_;
    $x->{a}->lt($x->{b});
}

sub is_improper {
    my ($x) = @_;
    $x->is_proper->not;
}

sub as_mixed {
    my ($x) = @_;
    my ($q, $r) = $x->{a}->divmod($x->{b});
    Sidef::Types::Array::Array->new([$q, $r, $x->{b}]);
}

sub add {
    my ($x, $y) = @_;

    if (ref($y) eq __PACKAGE__) {
        return __PACKAGE__->new($x->{a}->mul($y->{b})->add($y->{a}->mul($x->{b})), $x->{b}->mul($y->{b}));
    }

    __PACKAGE__->new($x->{a}->add($y->mul($x->{b})), $x->{b});
}

sub sub {
    my ($x, $y) = @_;
    $x->add($y->neg);
}

sub mul {
    my ($x, $y) = @_;

    if (ref($y) eq __PACKAGE__) {
        return __PACKAGE__->new($x->{a}->mul($y->{a}), $x->{b}->mul($y->{b}));
    }

    __PACKAGE__->new($x->{a}->mul($y), $x->{b});
}

sub sqr {
    my ($x) = @_;
    __PACKAGE__->new($x->{a}->mul($x->{a}), $x->{b}->mul($x->{b}));
}

sub inv {
    my ($x) = @_;
    __PACKAGE__->new($x->{b}, $x->{a});
}

sub invmod {
    my ($x, $n) = @_;
    $x->inv->mod($n);
}

sub div {
    my ($x, $y) = @_;

    if (ref($y) eq __PACKAGE__) {
        return __PACKAGE__->new($x->{a}->mul($y->{b}), $x->{b}->mul($y->{a}));
    }

    __PACKAGE__->new($x->{a}, $x->{b}->mul($y));
}

sub pow {
    my ($x, $n) = @_;

    if ($n->is_neg) {
        my $abs_n = $n->neg;
        return __PACKAGE__->new($x->{b}->pow($abs_n), $x->{a}->pow($abs_n));
    }

    __PACKAGE__->new($x->{a}->pow($n), $x->{b}->pow($n));
}

sub powmod {
    my ($x, $n, $m) = @_;

    $x = $x->mod($m);

    my $negative_power = 0;

    if ($n->is_neg) {
        $n              = $n->abs;
        $negative_power = 1;
    }

    my $c = __PACKAGE__->new(Sidef::Types::Number::Number::ONE, Sidef::Types::Number::Number::ONE);

    foreach my $bit (reverse split(//, $n->as_bin)) {

        # c = (c*x)%m if bit
        # x = (x*x)%m

        $c = $c->mul($x)->mod($m) if $bit;
        $x = $x->sqr->mod($m);
    }

    if ($negative_power) {
        $c = $c->invmod($m);
    }

    return $c;
}

sub lsft {
    my ($x, $n) = @_;
    __PACKAGE__->new($x->{a}->lsft($n), $x->{b});
}

sub rsft {
    my ($x, $n) = @_;
    __PACKAGE__->new($x->{a}, $x->{b}->lsft($n));
}

sub floor {
    my ($x) = @_;

    my $y = $x->{a}->div($x->{b});

    if (ref($y) ne __PACKAGE__) {
        return $y->floor;
    }

    $x->{a}->idiv_floor($x->{b});
}

sub ceil {
    my ($x) = @_;

    my $y = $x->{a}->div($x->{b});

    if (ref($y) ne __PACKAGE__) {
        return $y->ceil;
    }

    $x->{a}->idiv_ceil($x->{b});
}

sub round {
    my ($x) = @_;

    my $y = $x->{a}->div($x->{b});

    if (ref($y) ne __PACKAGE__) {
        return $y->round;
    }

    $x->{a}->idiv_round($x->{b});
}

sub trunc {
    my ($x) = @_;

    my $y = $x->{a}->div($x->{b});

    if (ref($y) ne __PACKAGE__) {
        return $y->trunc;
    }

    $x->{a}->idiv_trunc($x->{b});
}

sub mod {
    my ($x, $y) = @_;

    if (ref($y) ne __PACKAGE__) {
        return __PACKAGE__->new(Sidef::Types::Number::Mod->new($x->{a}, $y), Sidef::Types::Number::Mod->new($x->{b}, $y));
    }

    $x->sub($y->mul($x->div($y)->floor));
}

{
    no strict 'refs';

    foreach my $method (qw(eq ne lt le gt ge cmp or xor and)) {
        *{__PACKAGE__ . '::' . $method} = sub {
            my ($x, $y) = @_;

            if (ref($y) ne __PACKAGE__) {
                $y = __PACKAGE__->new($y);
            }

            $x->{a}->mul($y->{b})->$method($x->{b}->mul($y->{a}));
        };
    }

    *{__PACKAGE__ . '::' . '%'}   = \&mod;
    *{__PACKAGE__ . '::' . '/'}   = \&div;
    *{__PACKAGE__ . '::' . '÷'}   = \&div;
    *{__PACKAGE__ . '::' . '*'}   = \&mul;
    *{__PACKAGE__ . '::' . '+'}   = \&add;
    *{__PACKAGE__ . '::' . '-'}   = \&sub;
    *{__PACKAGE__ . '::' . '**'}  = \&pow;
    *{__PACKAGE__ . '::' . '++'}  = \&inc;
    *{__PACKAGE__ . '::' . '--'}  = \&dec;
    *{__PACKAGE__ . '::' . '<'}   = \&lt;
    *{__PACKAGE__ . '::' . '>'}   = \&gt;
    *{__PACKAGE__ . '::' . '&'}   = \&and;
    *{__PACKAGE__ . '::' . '|'}   = \&or;
    *{__PACKAGE__ . '::' . '^'}   = \&xor;
    *{__PACKAGE__ . '::' . '<<'}  = \&lsft;
    *{__PACKAGE__ . '::' . '>>'}  = \&rsft;
    *{__PACKAGE__ . '::' . '<=>'} = \&cmp;
    *{__PACKAGE__ . '::' . '<='}  = \&le;
    *{__PACKAGE__ . '::' . '≤'}   = \&le;
    *{__PACKAGE__ . '::' . '>='}  = \&ge;
    *{__PACKAGE__ . '::' . '≥'}   = \&ge;
    *{__PACKAGE__ . '::' . '=='}  = \&eq;
    *{__PACKAGE__ . '::' . '!='}  = \&ne;
}

1
