package Math::Bacovia::Exp;

use 5.014;
use warnings;

use Class::Multimethods qw();
use parent qw(Math::Bacovia);

our $VERSION = $Math::Bacovia::VERSION;

sub new {
    my ($class, $value) = @_;
    Math::Bacovia::_check_type(\$value);
    bless {value => $value}, $class;
}

sub get {
    $_[0]->{value};
}

#
## Operations
#

Class::Multimethods::multimethod mul => (__PACKAGE__, __PACKAGE__) => sub {
    my ($x, $y) = @_;
    __PACKAGE__->new($x->{value} + $y->{value});
};

Class::Multimethods::multimethod div => (__PACKAGE__, __PACKAGE__) => sub {
    my ($x, $y) = @_;
    __PACKAGE__->new($x->{value} - $y->{value});
};

#~ Class::Multimethods::multimethod pow => (__PACKAGE__, 'Math::Bacovia') => sub {
#~ my ($x, $y) = @_;
#~ __PACKAGE__->new($x->{value} * $y);
#~ };

sub inv {
    my ($x) = @_;
    $x->{_inv} //= __PACKAGE__->new($x->{value}->neg);
}

#
## Equality
#

Class::Multimethods::multimethod eq => (__PACKAGE__, __PACKAGE__) => sub {
    my ($x, $y) = @_;

    (ref($x->{value}) eq ref($y->{value}))
      && ($x->{value}->eq($y->{value}));
};

Class::Multimethods::multimethod eq => (__PACKAGE__, '*') => sub {
    !1;
};

#
## Transformations
#

sub numeric {
    my ($x) = @_;
    CORE::exp($x->{value}->numeric);
}

sub pretty {
    my ($x) = @_;
    $x->{_pretty} //= "exp(" . $x->{value}->pretty() . ")";
}

sub stringify {
    my ($x) = @_;
    $x->{_str} //= "Exp(" . $x->{value}->stringify() . ")";
}

#
## Alternatives
#
sub alternatives {
    my ($self, %opt) = @_;

    $self->{_alt} //= do {

        my @alt;
        foreach my $o ($self->{value}->alternatives(%opt)) {

            push @alt, __PACKAGE__->new($o);

            # Identity: exp(log(x) * y) = x^y
            if (ref($o) eq 'Math::Bacovia::Product' and @{$o->{values}} == 2) {
                my ($x, $y) = @{$o->{values}};

                if (ref($x) eq 'Math::Bacovia::Log') {
                    push @alt, 'Math::Bacovia::Power'->new($x->{value}, $y);
                }
                elsif (ref($y) eq 'Math::Bacovia::Log') {
                    push @alt, 'Math::Bacovia::Power'->new($y->{value}, $x);
                }
            }

            # Identity: exp(log(x)) = x
            elsif (ref($o) eq 'Math::Bacovia::Log') {
                push @alt, $o->{value};
            }

            if ($opt{full}) {

                # Identity: exp(a + b) = exp(a) * exp(b)
                if (ref($o) eq 'Math::Bacovia::Sum') {
                    push @alt, 'Math::Bacovia::Product'->new(map { __PACKAGE__->new($_) } @{$o->{values}});
                }

                # Identity: exp(a/b) = exp(a)^(1/b)
                #~ if (ref($o) eq 'Math::Bacovia::Fraction') {
                #~ push @alt, (__PACKAGE__->new($o->{num}) ** $o->{den}->inv)->alternatives(%opt);
                #~ }

#<<<
                # Identity: exp(a - b) = exp(a) / exp(b)
                if (ref($o) eq 'Math::Bacovia::Difference') {
                    push @alt, 'Math::Bacovia::Fraction'->new(__PACKAGE__->new($o->{minuend}), __PACKAGE__->new($o->{subtrahend}));
                }
#>>>
            }
        }

        [List::UtilsBy::uniq_by { $_->stringify } @alt];
    };

    @{$self->{_alt}};
}

1;
