package StudentM;
use lib "/cs/lallip/lib/perl5/site_perl/5.10.0/";
use Moose;
use Moose::Util::TypeConstraints;   #Allows creation of Subtypes
use Moose::Meta::Attribute::Native; #Allows attributes to act like built-in Perl types
use Data::Dumper;

use overload '""' => \&to_string;

################
## ATTRIBUTES ##
################

has name => (        #Attribute 'name' is a required read/write String
  is => 'rw',
  isa => 'Str',
  required => 1,
);

has rin => (         #Attribute 'rin' is a required read-only Integer.
  is => 'ro',        #This means calling $student->rin(76521) will result
  isa => 'Int',      #in an error.
  required => 1,
);

has major => (       #Attribute 'major' is an optional read-write string.
  is => 'rw',        #If not passed into the constructor, 'undecided' will be used.
  isa => 'Str',
  default => 'undecided',
);

subtype 'Grade'      #Subtype 'Grade' is a number that must be greater than or equal to 0.
   => as 'Num'
   => where { $_ >= 0 }
   => message { "Grade $_ is less than 0" };

has grade => (       #Attribute 'grade' is a read-only Grade, which cannot be passed
  is => 'ro',        #into the constructor.  It will be calculated when HWs or ICAs are
  isa => 'Grade',    #added or changed.
  init_arg => undef,
);

has hws => (                    #Attribute 'hws' is a required reference to an array of Grades
  traits => ['Array'],          #If it is not passed into the constructor, an empty array reference
  is => 'rw',                   #Will be used.  When the user sets this parameter, the &_calculate
  isa => 'ArrayRef[Grade]',     #method will be called.
  default => sub { [ ] },       #The attribute has the traits of a Perl built-in Array, and a call
  trigger => \&_calculate,      #to the 'add_hw' method simply calls 'push' on this Attribute.
  handles => {
     add_hw => 'push',
  }
);

after 'add_hw' => \&_calculate;   #After add_hw is called, call the &_calculate method

has icas => (                   #Attribute 'icas' is a required reference to an array of Grades.
  traits => ['Array'],          #If it is not passed into the constructor, an empty array reference
  is => 'rw',                   #will be used.  When the user sets this parameter, the &_calculate 
  isa => 'ArrayRef[Grade]',     #method will be called.
  default => sub { [ ] },       #The attribute has the traits of a Perl built-in Array, and a call
  trigger => \&_calculate,      #to the 'add_ica' method simply calls 'push' on this Attribute.
  handles => {
     add_ica => 'push',
  }
);

after 'add_ica' => \&_calculate;  #After add_ica is called, call the &_calculate method.


#############
## METHODS ##
#############

#Obtain the grade, return passing or failing.
sub status {
  my $self = shift;
  my $final_grade = $self->grade();

  return $final_grade >= 60 ? 'Passing' : 'Failing';
}

#This is a private method that will be called whenever
#a HW or ICA is added or changed.  Calculate and
#store the new final grade.
sub _calculate {
  my $self = shift;

  require List::Util;
  List::Util->import('sum');

  my @hws = @{$self->hws()};
  my @ics = @{$self->icas()};

  my $hwavg = @hws ? sum(@hws)/@hws : 0;
  my $icavg = @ics ? sum(@ics)/@ics : 0;

  $self->{grade} = $hwavg * 0.9 + $icavg;
}

#Print out a bunch of information about this student
sub show_status {
   my $self = shift;

   my $name = $self->name();
   my $rin = $self->rin();
   my $major = $self->major();
   my $grade = $self->grade();
   my $status = $self->status();

   print "$name (RIN: $rin), is a/an $major with a grade of $grade, which is $status\n";
}

#Return the string to use if this student object is used as a string.
sub to_string {
   my $self = shift;

   my $name = $self->name();
   my $rin = $self->rin();
   my $major = $self->major();

   return "$name ($rin)";
}

1;
