package Sidef::Types::Number::Mod;

use utf8;
use 5.016;

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

use overload
  q{bool} => sub { (@_) = ($_[0]); goto &__boolify__ },
  q{0+}   => sub { (@_) = ($_[0]); goto &__numify__ },
  q{""}   => sub { (@_) = ($_[0]); goto &__stringify__ },
  q{${}}  => sub { $_[0]{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->new($n) if !UNIVERSAL::isa($n, 'Sidef::Types::Number::Number');
    #$m = Sidef::Types::Number::Number->new($m) if !UNIVERSAL::isa($m, 'Sidef::Types::Number::Number');

    if (ref($n) eq __PACKAGE__) {
        $n = $n->real->mod($m);
    }

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

    if (ref($n) eq 'Sidef::Types::Number::Polynomial' and ref($m) ne 'Sidef::Types::Number::Polynomial') {
        return $n;
    }

    if (ref($n) eq 'Sidef::Types::Number::Fraction' and ref($m) ne 'Sidef::Types::Number::Fraction') {
        return $n;
    }

    if (ref($n) eq __PACKAGE__) {
        return $n;
    }

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

*call = \&new;

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

sub to_n {
    $_[0]->{n};
}

*lift = \&to_n;

sub eval {
    my ($x, $v) = @_;

    #$x->{n}->eval($v)->mod($x->{m});
    __PACKAGE__->new($x->{n}->eval($v), $x->{m});
}

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_neg {
    $_[0]->{n}->is_neg;
}

sub is_pos {
    $_[0]->{n}->is_pos;
}

sub is_nan {
    $_[0]->{n}->is_nan;
}

sub is_inf {
    $_[0]->{n}->is_inf;
}

sub is_ninf {
    $_[0]->{n}->is_ninf;
}

sub is_real {
    $_[0]->{n}->is_real;
}

sub real {
    $_[0]->{n}->is_zero ? $_[0]->{m} : $_[0]->{n};
}

*re = \&real;

sub norm {
    $_[0]->real->norm;
}

sub modulus {
    $_[0]->{m};
}

sub __boolify__ {
    $_[0]->{n};
}

sub __numify__ {
    $_[0]->{n};
}

sub __stringify__ {
    my ($x) = @_;
    "Mod($x->{n}, $x->{m})";
}

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

sub dump {
    my ($x) = @_;
    Sidef::Types::String::String->new(join('', "Mod(", join(', ', $x->{n}->dump, $x->{m}->dump), ')'));
}

sub pretty {
    my ($x) = @_;
    Sidef::Types::String::String->new(join('', "Mod(", join(', ', $x->{n}->pretty, $x->{m}->pretty), ')'));
}

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

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

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

    $y = $y->{n} if (ref($y) eq __PACKAGE__);

    __PACKAGE__->new($x->{n}->mul($y->invmod($x->{m})), $x->{m});
}

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

sub abs {
    my ($x) = @_;
    $x->{n}->abs;
}

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

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

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

sub chinese {
    my (@values) = @_;

#<<<
    my $crt = __PACKAGE__->new(
        Sidef::Types::Number::Number::ZERO,
        Sidef::Types::Number::Number::ONE
    );
#>>>

    foreach my $mod (@values) {

        ref($mod) eq __PACKAGE__ or next;

#<<<
        $crt = __PACKAGE__->new(
            Sidef::Math::Math->chinese(
                [$crt->{n}, $crt->{m}],
                [$mod->{n}, $mod->{m}]
            ),
            Sidef::Types::Number::Number::lcm($crt->{m}, $mod->{m})
        );
#>>>
    }

    $crt;
}

sub sqrt {
    my ($x) = @_;
    __PACKAGE__->new($x->{n}->sqrtmod($x->{m}), $x->{m});
}

sub sqr {
    my ($x) = @_;
    __PACKAGE__->new($x->{n}->sqr, $x->{m});
}

sub znorder {
    my ($x) = @_;
    $x->{n}->znorder($x->{m});
}

*multiplicative_order = \&znorder;

sub pow {
    my ($x, $y) = @_;
    __PACKAGE__->new($x->{n}->powmod($y, $x->{m}), $x->{m});
}

sub factorial {
    my ($x) = @_;
    __PACKAGE__->new($x->{n}->factorialmod($x->{m}), $x->{m});
}

sub lucas {
    my ($x) = @_;
    __PACKAGE__->new($x->{n}->lucasmod($x->{m}), $x->{m});
}

sub lucasu {
    my ($x, $P, $Q) = @_;
    __PACKAGE__->new(Sidef::Types::Number::Number::lucasUmod($P, $Q, $x->{n}, $x->{m}), $x->{m});
}

*lucasU = \&lucasu;
*LucasU = \&lucasu;

sub lucasv {
    my ($x, $P, $Q) = @_;
    __PACKAGE__->new(Sidef::Types::Number::Number::lucasVmod($P, $Q, $x->{n}, $x->{m}), $x->{m});
}

*lucasV = \&lucasv;
*LucasV = \&lucasv;

sub fibonacci {
    my ($x) = @_;
    __PACKAGE__->new($x->{n}->fibmod($x->{m}), $x->{m});
}

*fib = \&fibonacci;

sub chebyshevu {
    my ($x, $n) = @_;
    __PACKAGE__->new(Sidef::Types::Number::Number::chebyshevUmod($n, $x->{n}, $x->{m}), $x->{m});
}

*ChebyshevU = \&chebyshevu;
*chebyshevU = \&chebyshevu;

sub chebyshevt {
    my ($x, $n) = @_;
    __PACKAGE__->new(Sidef::Types::Number::Number::chebyshevTmod($n, $x->{n}, $x->{m}), $x->{m});
}

*chebyshevT = \&chebyshevt;
*ChebyshevT = \&chebyshevt;

sub cyclotomic {
    my ($x, $n) = @_;
    __PACKAGE__->new(Sidef::Types::Number::Number::cyclotomicmod($n, $x->{n}, $x->{m}), $x->{m});
}

sub shift_left {    # x * 2^n
    my ($x, $n) = @_;
    $x->mul(Sidef::Types::Number::Number::TWO->powmod($n, $x->{m}));
}

*lsft = \&shift_left;

sub shift_right {    # x / 2^n
    my ($x, $n) = @_;
    $x->div(Sidef::Types::Number::Number::TWO->powmod($n, $x->{m}));
}

*rsft = \&shift_right;

{
    no strict 'refs';

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

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

            $y = $y->{n} if (ref($y) eq __PACKAGE__);

            $x->{n}->$method($y);
        };
    }

    foreach my $method (qw(mul add sub xor or and)) {
        *{__PACKAGE__ . '::' . $method} = sub {
            my ($x, $y) = @_;

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

            $y = $y->{n} if (ref($y) eq __PACKAGE__);

            __PACKAGE__->new($x->{n}->$method($y), $x->{m});
        };
    }

    *{__PACKAGE__ . '::' . '/'}   = \&div;
    *{__PACKAGE__ . '::' . '÷'}   = \&div;
    *{__PACKAGE__ . '::' . '*'}   = \&mul;
    *{__PACKAGE__ . '::' . '+'}   = \&add;
    *{__PACKAGE__ . '::' . '-'}   = \&sub;
    *{__PACKAGE__ . '::' . '%'}   = \&mod;
    *{__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;
    *{__PACKAGE__ . '::' . '!'}   = \&factorial;
}

1
