#!/usr/bin/perl

use strict;
use warnings;

use Test::More tests => 62;
use Test::Exception;

BEGIN {
    use_ok('Moose');           
}

## Roles

{
    package Eq;
    use Moose::Role;
    
    requires 'equal_to';
    
    sub not_equal_to { 
        my ($self, $other) = @_;
        not $self->equal_to($other);
    }
    
    package Comparable;
    use Moose::Role;
    
    with 'Eq';
    
    requires 'compare';
    
    sub equal_to {
        my ($self, $other) = @_;
        $self->compare($other) == 0;
    }    
    
    sub greater_than {
        my ($self, $other) = @_;
        $self->compare($other) == 1;
    }    
    
    sub less_than {
        my ($self, $other) = @_;
        $self->compare($other) == -1;
    }
    
    sub greater_than_or_equal_to {
        my ($self, $other) = @_;
        $self->greater_than($other) || $self->equal_to($other);
    }        

    sub less_than_or_equal_to {
        my ($self, $other) = @_;
        $self->less_than($other) || $self->equal_to($other);
    }  
    
    package Printable;
    use Moose::Role;
    
    requires 'to_string';    
}

## Classes

{
    package US::Currency;
    use Moose;
    
    with 'Comparable', 'Printable';
    
    has 'amount' => (is => 'rw', isa => 'Num', default => 0);
    
    sub compare {
        my ($self, $other) = @_;
        $self->amount <=> $other->amount;
    }
    
    sub to_string {
        my $self = shift;
        sprintf '$%0.2f USD' => $self->amount
    }
    
    __PACKAGE__->meta->make_immutable(debug => 0);
}

ok(US::Currency->does('Comparable'), '... US::Currency does Comparable');
ok(US::Currency->does('Eq'), '... US::Currency does Eq');
ok(US::Currency->does('Printable'), '... US::Currency does Printable');

my $hundred = US::Currency->new(amount => 100.00);
isa_ok($hundred, 'US::Currency');

can_ok($hundred, 'amount');
is($hundred->amount, 100, '... got the right amount');

can_ok($hundred, 'to_string');
is($hundred->to_string, '$100.00 USD', '... got the right stringified value');

ok($hundred->does('Comparable'), '... US::Currency does Comparable');
ok($hundred->does('Eq'), '... US::Currency does Eq');
ok($hundred->does('Printable'), '... US::Currency does Printable');

my $fifty = US::Currency->new(amount => 50.00);
isa_ok($fifty, 'US::Currency');

can_ok($fifty, 'amount');
is($fifty->amount, 50, '... got the right amount');

can_ok($fifty, 'to_string');
is($fifty->to_string, '$50.00 USD', '... got the right stringified value');

ok($hundred->greater_than($fifty),             '... 100 gt 50');
ok($hundred->greater_than_or_equal_to($fifty), '... 100 ge 50');
ok(!$hundred->less_than($fifty),               '... !100 lt 50');
ok(!$hundred->less_than_or_equal_to($fifty),   '... !100 le 50');
ok(!$hundred->equal_to($fifty),                '... !100 eq 50');
ok($hundred->not_equal_to($fifty),             '... 100 ne 50');

ok(!$fifty->greater_than($hundred),             '... !50 gt 100');
ok(!$fifty->greater_than_or_equal_to($hundred), '... !50 ge 100');
ok($fifty->less_than($hundred),                 '... 50 lt 100');
ok($fifty->less_than_or_equal_to($hundred),     '... 50 le 100');
ok(!$fifty->equal_to($hundred),                 '... !50 eq 100');
ok($fifty->not_equal_to($hundred),              '... 50 ne 100');

ok(!$fifty->greater_than($fifty),            '... !50 gt 50');
ok($fifty->greater_than_or_equal_to($fifty), '... !50 ge 50');
ok(!$fifty->less_than($fifty),               '... 50 lt 50');
ok($fifty->less_than_or_equal_to($fifty),    '... 50 le 50');
ok($fifty->equal_to($fifty),                 '... 50 eq 50');
ok(!$fifty->not_equal_to($fifty),            '... !50 ne 50');

## ... check some meta-stuff

# Eq

my $eq_meta = Eq->meta;
isa_ok($eq_meta, 'Moose::Meta::Role');

ok($eq_meta->has_method('not_equal_to'), '... Eq has_method not_equal_to');
ok($eq_meta->requires_method('equal_to'), '... Eq requires_method not_equal_to');

# Comparable

my $comparable_meta = Comparable->meta;
isa_ok($comparable_meta, 'Moose::Meta::Role');

ok($comparable_meta->does_role('Eq'), '... Comparable does Eq');

foreach my $method_name (qw(
                        equal_to not_equal_to
                        greater_than greater_than_or_equal_to
                        less_than less_than_or_equal_to                            
                        )) {
    ok($comparable_meta->has_method($method_name), '... Comparable has_method ' . $method_name);
}

ok($comparable_meta->requires_method('compare'), '... Comparable requires_method compare');

# Printable

my $printable_meta = Printable->meta;
isa_ok($printable_meta, 'Moose::Meta::Role');

ok($printable_meta->requires_method('to_string'), '... Printable requires_method to_string');

# US::Currency

my $currency_meta = US::Currency->meta;
isa_ok($currency_meta, 'Moose::Meta::Class');

ok($currency_meta->does_role('Comparable'), '... US::Currency does Comparable');
ok($currency_meta->does_role('Eq'), '... US::Currency does Eq');
ok($currency_meta->does_role('Printable'), '... US::Currency does Printable');

foreach my $method_name (qw(
                        amount
                        equal_to not_equal_to
                        compare
                        greater_than greater_than_or_equal_to
                        less_than less_than_or_equal_to     
                        to_string                       
                        )) {
    ok($currency_meta->has_method($method_name), '... US::Currency has_method ' . $method_name);
}

