package Venus::Role::Comparable;

use 5.018;

use strict;
use warnings;

use Venus::Role 'with';

require Scalar::Util;
require Venus::Type;

# METHODS

sub eq {
  my ($self, $data) = @_;

  $data = Venus::Type->new(value => $data)->deduce;

  if (Scalar::Util::refaddr($self) eq Scalar::Util::refaddr($data)) {
    return true;
  }
  if (Scalar::Util::blessed($data) && !$data->isa('Venus::Kind')) {
    return false;
  }
  if ($self->comparer('eq') eq 'numified') {
    return $self->numified == $data->numified ? true : false;
  }
  elsif ($self->comparer('eq') eq 'stringified') {
    return $self->stringified eq $data->stringified ? true : false;
  }
  elsif (my $method = $self->comparer('eq')) {
    return $self->$method eq $data->$method ? true : false;
  }
  else {
    return false;
  }
}

sub ge {
  my ($self, $data) = @_;

  if ($self->gt($data) || $self->eq($data)) {
    return true;
  }
  else {
    return false;
  }
}

sub gele {
  my ($self, $ge, $le) = @_;

  if ($self->ge($ge) && $self->le($le)) {
    return true;
  }
  else {
    return false;
  }
}

sub gt {
  my ($self, $data) = @_;

  $data = Venus::Type->new(value => $data)->deduce;

  if (Scalar::Util::refaddr($self) eq Scalar::Util::refaddr($data)) {
    return false;
  }
  if (Scalar::Util::blessed($data) && !$data->isa('Venus::Kind')) {
    return false;
  }
  if ($self->comparer('gt') eq 'numified') {
    return $self->numified > $data->numified ? true : false;
  }
  elsif ($self->comparer('gt') eq 'stringified') {
    return $self->stringified gt $data->stringified ? true : false;
  }
  elsif (my $method = $self->comparer('gt')) {
    return $self->$method gt $data->$method ? true : false;
  }
  else {
    return false;
  }
}

sub gtlt {
  my ($self, $gt, $lt) = @_;

  if ($self->gt($gt) && $self->lt($lt)) {
    return true;
  }
  else {
    return false;
  }
}

sub is {
  my ($self, $data) = @_;

  if (!ref $data) {
    return false;
  }
  if (Scalar::Util::refaddr($self) eq Scalar::Util::refaddr($data)) {
    return true;
  }
  else {
    return false;
  }
}

sub lt {
  my ($self, $data) = @_;

  $data = Venus::Type->new(value => $data)->deduce;

  if (Scalar::Util::refaddr($self) eq Scalar::Util::refaddr($data)) {
    return false;
  }
  if (Scalar::Util::blessed($data) && !$data->isa('Venus::Kind')) {
    return false;
  }
  if ($self->comparer('lt') eq 'numified') {
    return $self->numified < $data->numified ? true : false;
  }
  elsif ($self->comparer('lt') eq 'stringified') {
    return $self->stringified lt $data->stringified ? true : false;
  }
  elsif (my $method = $self->comparer('lt')) {
    return $self->$method lt $data->$method ? true : false;
  }
  else {
    return false;
  }
}

sub le {
  my ($self, $data) = @_;

  if ($self->lt($data) || $self->eq($data)) {
    return true;
  }
  else {
    return false;
  }
}

sub ne {
  my ($self, $data) = @_;

  return $self->eq($data) ? false : true;
}

sub st {
  my ($self, $data) = @_;

  if (!Scalar::Util::blessed($data)) {
    return false;
  }
  if (Scalar::Util::refaddr($self) eq Scalar::Util::refaddr($data)) {
    return true;
  }
  if ($data->isa($self->class)) {
    return true;
  }
  else {
    return false;
  }
}

sub tv {
  my ($self, $data) = @_;

  if (!Scalar::Util::blessed($data)) {
    return false;
  }
  if (Scalar::Util::refaddr($self) eq Scalar::Util::refaddr($data)) {
    return true;
  }
  if ($data->isa($self->class)) {
    return $self->eq($data);
  }
  else {
    return false;
  }
}

# EXPORTS

sub EXPORT {
  ['eq', 'ge', 'gele', 'gt', 'gtlt', 'is', 'lt', 'le', 'ne', 'st', 'tv']
}

1;



=head1 NAME

Venus::Role::Comparable - Comparable Role

=cut

=head1 ABSTRACT

Comparable Role for Perl 5

=cut

=head1 SYNOPSIS

  package Example;

  use Venus::Class;

  base 'Venus::Kind';

  with 'Venus::Role::Comparable';

  sub numified {
    return 2;
  }

  package main;

  my $example = Example->new;

  # my $result = $example->eq(2);

=cut

=head1 DESCRIPTION

This package modifies the consuming package and provides methods for performing
numerical and stringwise comparision operations or any object or raw data type.

=cut

=head1 METHODS

This package provides the following methods:

=cut

=head2 eq

  eq(Any $arg) (Bool)

The eq method performs an I<"equals"> operation using the invocant and the
argument provided. The operation will be performed as either a numerical or
stringwise operation based upon the preference (i.e. the return value of the
L</comparer> method) of the invocant.

I<Since C<0.08>>

=over 4

=item eq example 1

  package main;

  my $example = Example->new;

  my $result = $example->eq($example);

  # 1

=back

=over 4

=item eq example 2

  package main;

  my $example = Example->new;

  my $result = $example->eq([1,2]);

  # 0

=back

=over 4

=item eq example 3

  package main;

  my $example = Example->new;

  my $result = $example->eq({1..4});

  # 0

=back

=cut

=head2 ge

  ge(Any $arg) (Bool)

The ge method performs a I<"greater-than-or-equal-to"> operation using the
invocant and argument provided. The operation will be performed as either a
numerical or stringwise operation based upon the preference (i.e. the return
value of the L</comparer> method) of the invocant.

I<Since C<0.08>>

=over 4

=item ge example 1

  package main;

  my $example = Example->new;

  my $result = $example->ge(3);

  # 0

=back

=over 4

=item ge example 2

  package main;

  my $example = Example->new;

  my $result = $example->ge($example);

  # 1

=back

=over 4

=item ge example 3

  package main;

  my $example = Example->new;

  my $result = $example->ge([1,2,3]);

  # 0

=back

=cut

=head2 gele

  gele(Any $arg1, Any $arg2) (Bool)

The gele method performs a I<"greater-than-or-equal-to"> operation on the 1st
argument, and I<"lesser-than-or-equal-to"> operation on the 2nd argument. The
operation will be performed as either a numerical or stringwise operation based
upon the preference (i.e. the return value of the L</comparer> method) of the
invocant.

I<Since C<0.08>>

=over 4

=item gele example 1

  package main;

  my $example = Example->new;

  my $result = $example->gele(1, 3);

  # 1

=back

=over 4

=item gele example 2

  package main;

  my $example = Example->new;

  my $result = $example->gele(2, []);

  # 0

=back

=over 4

=item gele example 3

  package main;

  my $example = Example->new;

  my $result = $example->gele(0, '3');

  # 1

=back

=cut

=head2 gt

  gt(Any $arg) (Bool)

The gt method performs a I<"greater-than"> operation using the invocant and
argument provided. The operation will be performed as either a numerical or
stringwise operation based upon the preference (i.e. the return value of the
L</comparer> method) of the invocant.

I<Since C<0.08>>

=over 4

=item gt example 1

  package main;

  my $example = Example->new;

  my $result = $example->gt({1..2});

  # 0

=back

=over 4

=item gt example 2

  package main;

  my $example = Example->new;

  my $result = $example->gt(1.9998);

  # 1

=back

=over 4

=item gt example 3

  package main;

  my $example = Example->new;

  my $result = $example->gt(\1_000_000);

  # 0

=back

=cut

=head2 gtlt

  gtlt(Any $arg1, Any $arg2) (Bool)

The gtlt method performs a I<"greater-than"> operation on the 1st argument, and
I<"lesser-than"> operation on the 2nd argument. The operation will be performed
as either a numerical or stringwise operation based upon the preference (i.e.
the return value of the L</comparer> method) of the invocant.

I<Since C<0.08>>

=over 4

=item gtlt example 1

  package main;

  my $example = Example->new;

  my $result = $example->gtlt('1', 3);

  # 1

=back

=over 4

=item gtlt example 2

  package main;

  my $example = Example->new;

  my $result = $example->gtlt({1..2}, {1..4});

  # 0

=back

=over 4

=item gtlt example 3

  package main;

  my $example = Example->new;

  my $result = $example->gtlt('.', ['.']);

  # 1

=back

=cut

=head2 is

  is(Any $arg) (Bool)

The is method performs an I<"is-exactly"> operation using the invocant and the
argument provided. If the argument provided is blessed and exactly the same as
the invocant (i.e. shares the same address space) the operation will return
truthy.

I<Since C<1.80>>

=over 4

=item is example 1

  package main;

  my $example = Example->new;

  my $result = $example->is($example);

  # 1

=back

=over 4

=item is example 2

  package main;

  my $example = Example->new;

  my $result = $example->is([1,2]);

  # 0

=back

=over 4

=item is example 3

  package main;

  my $example = Example->new;

  my $result = $example->is(Example->new);

  # 0

=back

=cut

=head2 le

  le(Any $arg) (Bool)

The le method performs a I<"lesser-than-or-equal-to"> operation using the
invocant and argument provided. The operation will be performed as either a
numerical or stringwise operation based upon the preference (i.e. the return
value of the L</comparer> method) of the invocant.

I<Since C<0.08>>

=over 4

=item le example 1

  package main;

  my $example = Example->new;

  my $result = $example->le('9');

  # 1

=back

=over 4

=item le example 2

  package main;

  my $example = Example->new;

  my $result = $example->le([1..2]);

  # 1

=back

=over 4

=item le example 3

  package main;

  my $example = Example->new;

  my $result = $example->le(\1);

  # 0

=back

=cut

=head2 lt

  lt(Any $arg) (Bool)

The lt method performs a I<"lesser-than"> operation using the invocant and
argument provided. The operation will be performed as either a numerical or
stringwise operation based upon the preference (i.e. the return value of the
L</comparer> method) of the invocant.

I<Since C<0.08>>

=over 4

=item lt example 1

  package main;

  my $example = Example->new;

  my $result = $example->lt(qr/.*/);

  # 1

=back

=over 4

=item lt example 2

  package main;

  my $example = Example->new;

  my $result = $example->lt('.*');

  # 0

=back

=over 4

=item lt example 3

  package main;

  my $example = Example->new;

  my $result = $example->lt('5');

  # 1

=back

=cut

=head2 ne

  ne(Any $arg) (Bool)

The ne method performs a I<"not-equal-to"> operation using the invocant and
argument provided. The operation will be performed as either a numerical or
stringwise operation based upon the preference (i.e. the return value of the
L</comparer> method) of the invocant.

I<Since C<0.08>>

=over 4

=item ne example 1

  package main;

  my $example = Example->new;

  my $result = $example->ne([1,2]);

  # 1

=back

=over 4

=item ne example 2

  package main;

  my $example = Example->new;

  my $result = $example->ne([2]);

  # 1

=back

=over 4

=item ne example 3

  package main;

  my $example = Example->new;

  my $result = $example->ne(qr/2/);

  # 1

=back

=cut

=head2 st

  st(Object $arg) (Bool)

The st method performs a I<"same-type"> operation using the invocant and
argument provided. If the argument provided is an instance of the invocant, or
a subclass, the operation will return truthy.

I<Since C<1.80>>

=over 4

=item st example 1

  package main;

  my $example = Example->new;

  my $result = $example->st($example);

  # 1

=back

=over 4

=item st example 2

  package main;

  use Venus::Number;

  my $example = Example->new;

  my $result = $example->st(Venus::Number->new(2));

  # 0

=back

=over 4

=item st example 3

  package main;

  use Venus::String;

  my $example = Example->new;

  my $result = $example->st(Venus::String->new('2'));

  # 0

=back

=over 4

=item st example 4

  package Example2;

  use base 'Example';

  package main;

  use Venus::String;

  my $example = Example2->new;

  my $result = $example->st(Example2->new);

  # 1

=back

=cut

=head2 tv

  tv(Any $arg) (Bool)

The tv method performs a I<"type-and-value-equal-to"> operation using the
invocant and argument provided. The operation will be performed as either a
numerical or stringwise operation based upon the preference (i.e. the return
value of the L</comparer> method) of the invocant.

I<Since C<0.08>>

=over 4

=item tv example 1

  package main;

  my $example = Example->new;

  my $result = $example->tv($example);

  # 1

=back

=over 4

=item tv example 2

  package main;

  use Venus::Number;

  my $example = Example->new;

  my $result = $example->tv(Venus::Number->new(2));

  # 0

=back

=over 4

=item tv example 3

  package main;

  use Venus::String;

  my $example = Example->new;

  my $result = $example->tv(Venus::String->new('2'));

  # 0

=back

=over 4

=item tv example 4

  package main;

  use Venus::String;

  my $example = Example->new;

  my $result = $example->tv(Example->new);

  # 1

=back

=cut