package APE::InputControllerFactory::ConcreteInputController::RequirementContainer;

$APE::InputControllerFactory::ConcreteInputController::RequirementContainer::VERSION = '1.1';

use strict;
use warnings;
use Carp;

use APE::Iterator;


=head1 NAME

APE::InputControllerFactory::ConcreteInputController::RequirementContainer
- a container class used to manage Requirement objects.


=head1 SYNOPSIS

 use APE::InputControllerFactory::ConcreteInputController::RequirementContainer;
 use APE::InputControllerFactory::ConcreteInputController::Requirement;

 my $requirementContainer =
   APE::InputControllerFactory::ConcreteInputController::RequirementContainer->new
     ();

 foreach(1..10) {
   my $requirement = APE::InputControllerFactory::ConcreteInputController::Requirement->new
     (id   => $_,
      type => "DN",
      description => "DNA plate $_");
   $requirementContainer->pushRequirement($requirement);
 }

 ## Use a non-static iterator to iterate through
 ## the Requirement objects.
 my $iterator = $requirementContainer->iterator();

 while($iterator->hasMore()) {
   my $requirement = $iterator->next();
   $requirement->setData("DN1234");
 }


=head1 CONSTANTS

=over 5

=item TRUE

Boolean true.

=cut

use constant TRUE => 1;


=item FALSE

Boolean false.

=cut

use constant FALSE => 0;

{

=back

=head1 CONSTRUCTOR

=over 5

=item $rc = APE::InputControllerFactory::ConcreteInputController::RequirementContainer(option => value,...?)

Use this syntax to create a new RequirementContainer instance. The
following arguments may be passed to the constructor.

=over 5

=item outDescription

Optional. A description of the out entities this containers
Requierement objects are going to create.

=item requirementList

Optional. A list of Requirement objects. See pushRequirement() and
unshiftRequirement() methods for alternative ways of populating this
list after object construction.

=cut

    my %_attributes = (outDescription           => undef,
		       requirementList          => [],
		       linkedData               => [],
		       otherData                => []);
    my %object;


=head2 INSTANTIATION PROCESS

The new method for this object calls the _init() method on $self. If
$self is a derived class then it's _init() method will be called
giving it the chance to remove any arguments it requires from the
%args hash. If there is no _init() method in the derived class then
the _init() method in this class is called.

Derived classes implementing a _init() method should return the %args
hash to the base class:

package Derived::Class;

sub _init {
   my ($self,%args) = @_;
   my $argument = delete $args{classArgument};
   return $self->SUPER::_init(%args);
}

=cut

    sub new {
	my ($proto,%args) = @_;
	my $class = ref($proto) || $proto;
	my $self  = bless {}, $class;
	%args = $self->_init(%args);
	my @leftover = keys %args;
	confess "Error: the following arguments are unknown - @leftover\n" if @leftover;
	return $self;
    }


=head1 METHODS

=over 10

=item $controllerFactory->_init(%args);

Private method. This method is called by the new method during object
instantiation (see the INSTANTIATION process). The method removes any
of the arguments required by the object from the %args hash and
returns it.

See the INSTANTIATION section for information on how to use this
method in a derived class.

This method sets the requirementList attribute to be an empty array
reference. This ensures that calls to getRequirementList() always
returns an array reference.

Creates the objects static APE::Iterator object. Use the
staticIterator() method to retrieve it. This iterator maintains its
index pointer to this objects list of Requirement objects for the life
of this object - see staticIterator() method.

=cut

    sub _init {
	my ($self,%args) = @_;

	foreach my $attName (keys(%args)) {
	    if(exists($_attributes{$attName})) {
		$object{ $self }->{$attName} = delete $args{$attName};
	    }
	}

	## The requirement list must be an array reference.
	$object{ $self }->{requirementList} = [];

	## Grab a static APE::Iterator.
	$object{ $self }->{staticIterator} =
	  $self->iterator();

	return %args;
    }

=item $rc->getOutDescription()

Returns a string description of the entities that will be created by
the Requirement objects (actually it's the APE::AtomicProcess objects
registered to the Requirement objects that create the out entities)
being managed in this container.

=item $rc->getRequirementList()

Returns an array reference containg the Requirement objects that this
container is managing.

=item $rc->getLinkedData()

Returns an array reference of data linked (spuriously) to each of this
objects Requirement objects. Spurious because there isn't really a
hard link, the link is just the index into the requiremntList and the
linkedData list.

=item $rc->getOtherData()

Returns an array reference of other data. This data can be anything
and is provided as a support method.

=cut

    foreach my $attName (keys(%_attributes)) {
	no strict 'refs';
	*{__PACKAGE__ . "::get\u$attName"} = sub { $object{ $_[0] }->{$attName} }
    }


=item $rc->staticIterator()

Returns this objects static APE::Iterator object. This method
guarantees to returnt he same iterator however many times it is
called. The returned iterator will remain at the same position in the
Requirement object list - APE::Iterator for more information on
resetting the iterator.

=cut

    sub staticIterator {
	my $self = shift;
	return $object{ $self }->{staticIterator};
    }


=item $rc->iterator()

Returns a new APE::Iterator with the dataStore pointing to this
objects list of Requirement objects.

=cut

    sub iterator {
	my $self = shift;
	return APE::Iterator->new
	  (dataStore => $object{ $self }->{requirementList});
    }


=item $rc->getDescription()

Returns a string description of this RequirementContainer and the
number of Requirement objects it contains.

=cut

    sub getDescription {
	my $self = shift;
	my $reqCount = scalar(@{$object{ $self }->{requirementList}});
	return "$self containing '$reqCount' Requirement objects.";
    }


=item $rc->setOutDescription($outDescription)

Sets the description of the out entities that are created by the
Requirement objects being managed by this object. It's actually not
the Requirement objects that create the out entities, it's the
APE::AtomicProcess objects that are registered with each Requirement
object being managed by this object.

This method is usually called by an
APE::InputControllerFactory::ConcreteInputController::InputController
object as it creates RequirementContainer objects to represent its
arraying process.

=cut

    sub setOutDescription {
	my ($self,$outDescription) = @_;
	$object{ $self }->{outDescription} = $outDescription;
    }


=item $rc->isInput()

Tests if this RequirementContainer only contains Requirement objects
of type 'Input'. Returns boolean true if one or more Requirement
objects are of type 'Input'.

XXX: Requires updating to use Requirement->isInput() method.

=cut

    sub isInput {
	my $self = shift;
	foreach my $requirement (@{$object{ $self }->{requirementList}}) {
	    if($requirement->getType ne 'Input') {
		return FALSE;
	    }
	}
	return TRUE;
    }


=item $rc->pushLinkedData($data)

Pushes $data to the linkedData attribute list and returns the new
count of items in the list.

=cut

    sub pushLinkedData {
	my ($self,$otherData) = @_;
	return push(@{$object{ $self }->{linkedData}},$otherData);
    }


=item $rc->pushOtherData($data)

Pushes $data to the otherData attribute list and returns the new count
of items in the list.

=cut

    sub pushOtherData {
	my ($self,$otherData) = @_;
	return push(@{$object{ $self }->{otherData}},$otherData);
    }


=item $rc->clearOtherData()

Clears the data held in the otherData attribute.

XXX: Should this return the old data that has just been cleared?

=cut

    sub clearOtherData {
	my $self = shift;
	$object{ $self }->{otherData} = [];
    }


=item $rc->clearLinkedData()

Clears the data held in the linkedData attribute.

XXX: Should this return the old data that has just been cleared?

=cut

    sub clearLinkedData {
	my $self = shift;
	$object{ $self }->{linkedData} = [];
    }


=item $rc->getOtherDataAt($index)

Returns the 'other' data held at $index in the otherData attribute
list. Undef if no element is found in the list using $index.

=cut

    sub getOtherDataAt {
	my ($self,$index) = @_;
	return $object{ $self }->{otherData}->[$index];
    }


=item $rc->pushRequirement(APE::InputControllerFactory::ConcreteInputController::Requirement)

Pushes the passed Requirement object to the array of managed
Requirement objects, lengthening the array by one. Returns the new
number of objects in the array.

XXX: Should perform type check on the passed object.

=cut

    sub pushRequirement {
	my ($self,$requirementObj) = @_;
	return push(@{$object{ $self }->{requirementList}}, $requirementObj);
    }

=item $rc->popRequirement()

Pops the last Requirement object off the array of managed Requirement
objects and returns it, shortening the array by one. Returns undef if
the array contains no Requirement objects.

=cut

    sub popRequirement {
	my $self = shift;
	return pop(@{$object{ $self }->{requirementList}});
    }

=item $rc->shiftRequirement

Shifts the first Requirement object off the array of managed
Requirement objects and returns it, shortening the array by one and
moving everything down. Returns undef if the array contains no
Requirement objects.

=cut

    sub shiftRequirement {
	my $self = shift;
	return shift(@{$object{ $self }->{requirementList}});
    }


=item $rc->unshiftRequirement(APE::InputControllerFactory::ConcreteInputController::Requirement)

Prepends the passed Requirement object to the array of managed
Requirement objects, lengthening the array by one. Returns the new
number of Requirement objects in the array.

=cut

    sub unshiftRequirement {
	my ($self,$requirementObj) = @_;
	return unshift(@{$object{ $self }->{requirementList}},$requirementObj);
    }


=item $rc->insertRequirement(APE::InputControllerFactory::ConcreteInputController::Requirement,$index)

Inserts the passed Requirement object at position $index in the array
of managed Requirement objects, lengthening the array by one.

=cut

    sub insertRequirement {
	my ($self,$requirementObj,$index) = @_;
	return splice(@{$object{ $self }->{requirementList}},$index,$index,$requirementObj);
    }

=item $rc->deleteRequirement($index)

Deletes the Requirement object at $index in the array of managed
Requirement objects. Returns the deleted Requirement object of undef
if no object is at $index.

=cut

    sub deleteRequirement {
	my ($self,$index) = @_;
	return delete($object{ $self }->{requirementList}->[$index]);
    }

=item $rc->getRequirementAt($index)

Returns the Requirement object at $index in the array of managed
Requirement objects.

=cut

    sub getRequirementAt {
	my ($self,$index) = @_;
	return $object{ $self }->{requirementList}->[$index];
    }


=item $rc->hasRequirements()

Returns boolean true if this object contains one or more Requirement
objects, otherwise false.

=cut

    sub hasRequirements {
	my $self = shift;
	if(defined $object{ $self }->{requirementList}) {
	    return scalar(@{$object{ $self }->{requirementList}}) ? TRUE : FALSE;
	} return FALSE;
    }


=item DESTROY

Class destructor. If you derive from this class please call this
destructor from your destructor.

 package Derived::Class;

 sub new {}

 sub DESTROY {
     my $self = shift;
     $self->SUPER::DESTROY();
 }

=cut

    sub DESTROY { 
	my $self = shift;
	delete $object{ $self };
    }
}


=back

=head1 ALSO SEE

APE::InputControllerFactory::ConcreteInputController::Requirement,
APE::InputControllerFactory::ConcreteInputController::InputController

=head1 AUTHOR

Simon Holroyd <holroyd@sanger.ac.uk>

=cut

1;
