################################################################## 
## Class Name: InputController                                  ##
##                                                              ##
## Description: This abstract class represents a concrete input ##
##              controller and should be implemented by any     ##
##              input controller class.                         ##
##                                                              ##
## Arguments: arrayingData - array of arrays containing arraying##
##                           arraying data.                     ##
##            rules - hash ref containing an rules to execute.  ##
##            description - string description of the controller##
##            default - the default value for requirements.     ##
##                                                              ##
##################################################################
package APE::InputControllerFactory::ConcreteInputController::InputController;

$APE::InputControllerFactory::ConcreteInputController::InputController::VERSION = '0.6';

use base qw(APE::Controller);

use strict;
use warnings;
use Carp;

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


=head1 NAME

APE::InputControllerFactory::ConcreteInputController::InputController
- base class of all concrete input controller objects.


=head1 SYNOPSIS

package My::InputController;

use base qw(APE::InputControllerFactory::ConcreteInputController::InputController);


## An example of overriding the createRequirements() method. This
## example is a simple replication of the default behaviour offered by
## this classes createRequirements() method. Overriding may be needed
## to add Requirement objects and/or RequirementContainers and/or to
## order them in a unique way.
sub createRequirements {
  my $self = shift;
  return if $self->hasRequirements();

  my $atomicProcessContainer = $self->getAtomicProcessContainer();
  my $arrayingEntityList     = $atomicProcessContainer->getArrayingEntityList();

  foreach my $arrayingEntity (@$arrayingEntityList) {

      ## Create the container.
      my $requirementContainer
	= APE::InputControllerFactory::ConcreteInputController::RequirementContainer->new();

      ## Get the APE::AtomicProcess objects, each representing a
      ## single mapping between a source and target well.
      my $atomicProcessList = $arrayingEntity->getAtomicProcessList();

      foreach my $atomicProcess (@$atomicProcessList) {
	  $requirmentContainer->pushRequirement
	    (APE::InputControllerFactory::ConcreteInputController::Requirement->new
	     (description   => $atomicProcess->getDescription(),
	      type          => $atomicProcess->getInPrefix(),
	      default       => $self->getDefault(),
	      atomicProcess => $atomicProcess));
      }
      $self->pushRequirementContainer($requirementContainer);
  }
}


## This method requires implementing in a class derived from this
## class. The method should use the input to the passed Requirement
## object to find the data to array.
sub getData {
  my ($self,$requirement) = @_;
  my $data = $requirement->getData();

  ## Perform a lookup or similar. Lookup should return an array of
  ## arrays. The inner array should represent each piece of data to
  ## array, its first element should be a valid id_map number.
  my $lookupData = performLookup($data);

  return $lookupData;
}


## This method requires implementing in a class derived from this
## class. The method should return a session id number that may be
## used to group the sessions flushes.
sub getSessionId {
  my $self = shift;

  ## Use this method to retrieve a session id number.
  my $sessionId = 1;

  return $sessionId;
}


=head1 DESCRIPTION

This is the base class for all concrete input controller classes. An
input controller manages the creation of requirements for an arraying
process, the processing of the data gathered by the requirements and
the triggering of the arraying process.

The APE framework uses controller factories to retrieve concrete input
controller objects - see APE::ControllerFactory and
APE::InputControllerFactory classes.

A single arraying process, represented by a single
APE::AtomicProcessContainer object, allocates itself a single input
controller object via an input controller factory.

The application using the framework is provided with a single
APE::AtomicProcessContainer. The following chain of events should be
followed to interact with the input controller object attached to the
APE::AtomicProcessContainer object in order to complete the arraying
process:

=over5

=item Retrieve input controller

Retrieve the attached input controller using the
APE::AtomicProcessContainer::getInputController() method - see
APE::AtomicProcessContainer::getInputController() method. This causes
the framework to call the input controller objects
initInputController() method that, amongst other things, calls this
objects createRequirements() method.

=item Create the requirements

Automatically triggered by retrieving the input controller from the
APE::AtomicProcessContainer object unless this objects
initInputController() object has been overridden, in which case this
objects createRequirements() method should be called explicitly.

The default behaviour for this objects createRequirements() method is
described below:

RequirementContainer objects are created for each of the
APE::ArrayingEntity objects. An APE::ArrayingEntity object represents
a single output from the arraying process and are made up of
APE::AtomicProcess objects that define a 1:1 mapping between a source
and target well.

A Requirement object is created for each APE::AtomicProcess within the
APE::ArrayingEntity object and stored in the RequirementContainer
object.

=item Retrieve the requirements

Requirement objects are stored in RequirementContainer
objects. Retrieve RequirementContainer objects by calling this objects
getRequirementContainerList() method. Retrieve Requirement objects by
calling the RequirementContainer objects getRequirementList() method.

=item Fulfill requirements

Complete the arraying processes requirements by setting data into the
Requirement objects. For instance, if a Requirement object is for a
DNA plate then the application should use the Requirement objects
setData() method to store the id number of the DNA plate.

=item Process requirements

Once a Requirement has been fulfilled it requires processing. Pass the
Requirement object to this objects process() method.

The default behaviour of the process() method is to call this objects
getData() method and to store the resulting data in the
Requirement. The getData() method should return an array of arrays
representing the data to array from source to target. The inner arrays
represent a well on the source entity, and the first element of this
array should be a valid id_map number indicating the position of the
data on the source entity.

=back


=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

This is an abstract class. No instances of this class can be made. The
following arguments are required by this class.

=over 5

=item atomicProcessContainer

An APE::AtomicProcessContainer object that represents the arraying
process.

=item rules

An anoymous hash of rules to execute after the createRequirements()
method has been called (if using the initInputController() method to
initialise).

=item description

A string description of the InputController.

=item default

The default value to ask Requirement objects to use if they are no
fulfilled at processing time.

=back

=cut

    my %_attributes = (
		       arrayingData             => undef,
		       rules                    => undef,
		       description              => undef,
		       atomicProcessContainer   => undef,
		       default                  => undef,
		       requirementContainerList => [],
		       );


    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) = @_;
	## This is an abstract class, instances of it can not be created.
	die "Error: instances of class 'APE::InputControllerFactory::".
	  "ConcreteInputController::InputController' can not be made."
	    if $proto eq
	      'APE::InputControllerFactory::ConcreteInputController::InputController';
	return $proto->SUPER::new(%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 throws an exception if the atomicProcessContainer argument
has has not been passed or it is not an APE::AtomicProcessContainer
object. It is possible to prevent this error from being thrown by
passing 'DEBUG' as the atomicProcessContainer argument value.

=cut

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

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

	## Verify that all of the attributes have been set.
	croak "Error: attribute 'atomicProcessContainer' is required by ".
	    "the InputController abstract class."
	    unless exists $object{ $self }->{atomicProcessContainer};


	## Verify that the atomicProcessContainer is the correct
	## type. If set to 'DEBUG' then its assumed the user knows
	## what they are doing and that they are debugging the class
	## with no support from an APE::AtomicProcessContainer class.
	unless(UNIVERSAL::isa
               ($object{ $self }->{atomicProcessContainer},
                'APE::AtomicProcessContainer')
	       || $object{ $self }->{atomicProcessContainer} eq 'DEBUG') {
	    croak "Error: attribute atomicProcessContainer is not ".
                "of type APE::AtomicProcessContainer";
	}

	return $self->SUPER::_init(%args);
    }

=item $ic->getArrayingData()

Returns the arraying data that has been set using the
setArrayingData() method. This classes process() method sets the
arraying data using the setArrayingData() method, but this is not
always the case in application specific InputController objects.

=item $ic->getRules()

Returns an anonymous hash containing all of the rules that will be
executed after the createRequirements() method has executed (this
order of execution is provided by the initInputController() method.

=item $ic->getDescription()

Returns a string description of the InputController object as set with
the description argument.

=item $ic->getAtomicProcessContainer()

Returns the APE::AtomicProcessContainer object passed into this object
during instantiation. This object represents the arraying process that
this InputController is to create and manage Requirement objects for.

=item $ic->getDefault()

Returns the default value that will be used for all Requirement
objects.

=item $ic->getRquirementContainerList()

Returns an anonymous array containing the RequirementContainer objects
being managed by this InputController.

=cut

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


=item $ic->getSessionId()

Abstract method that requires implementing. Should return a session id
number that will group all of the out entities in the arraying
process.

=cut

    sub getSessionId { die "Error: abstract method." }


=item $ic->getData()

Abstract method that requires implementing. Should fetch the arraying
data associated with a fulfilled Requirement object and set it.

=cut

    sub getData { die "Error: abstract method." }


=item $ic->initInputController()

Method to initialise the use of this DefaultInputController. This
method calls the relevant functions in order of importance:

createRequirements();
execRules();
setContainerOutDescriptions()

=cut

    sub initInputController {
	my $self = shift;
	$self->createRequirements;
	$self->execRules;
	$self->setContainerOutDescriptions;
    }


=item $ic->createRequirements()

Method to create the requirements based on the contents of the
AtomicProcessContainer.

=cut

    sub createRequirements {
    	my $self = shift;
    	return if $self->hasRequirements();
    	warn "Creating Requirement objects now....\n";
    	my $apc = $self->getAtomicProcessContainer;
    	my $ael = $apc->getArrayingEntityList();
    	warn "ArrayingEntityList has ". scalar(@{$ael}).
            " ArrayingEntity objects\n";
    	for(my $aei=0; $aei < @{$ael}; $aei++) {
    	    my $apl = $ael->[$aei]->getAtomicProcessList();
    	    my $requirementContainer =
	      new APE::InputControllerFactory::ConcreteInputController::RequirementContainer();
    	    for(my $api=0; $api < @{$apl}; $api++) {
    		my $atomicProcess = $apl->[$api];
		my $description   = $apl->[$api]->getInDescription();
    		my $position      = " [".($aei+1)."\/".($api+1)."]";
    		my $default       = "[" .$self->getDefault()."]" if $self->getDefault();
    		$description      = $description." - (".$apl->[$api]->getInPrefix().")";
    		$description      = $description.$position;
    		$description      = $description.$default if $default;
    		$requirementContainer->pushRequirement
		    (new APE::InputControllerFactory::ConcreteInputController::Requirement
                     (default             => $self->getDefault,
		      atomicProcess       => $atomicProcess,
                      #arrayingEntityIndex => $aei,
                      #atomicProcessIndex  => $api,
                      type                => $apl->[$api]->getInPrefix,
                      description         => $description . ": "));
    		warn "Created a new RequirementContainer: $description\n";
    	    }
    	    $self->pushRequirementContainer($requirementContainer);
    	}
    }


=item $ic->process(APE::InputControllerFactory::ConcreteInputController::Requirement,$arrayingData)

Method to process a Requirement object. The default action is to
process Requirement objects one at a time. Complete Requirement
objects are pushed to the completeRequirementList. Requirement objects
with duplicat data fields are not allowed by default.  For a list of
prefix types that this process method can lookup please see the
DefaultInputController.sql_lib.

=cut

    sub process {
	my ($self,$requirementObj,$arrayingData) = @_;
	return if $requirementObj->getType eq 'Input';
	warn "*** Using base objects process method: ".
            $requirementObj->getData. "\n";
	if(defined $arrayingData) {
	    $self->setArrayingData($arrayingData);
	}
	$self->getData($requirementObj);
    }


=item $ic->setArrayingData($arrayingData)

Method to set the arrayingData attribute.

=cut

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


=item $ic->arrayContainerData(APE::InputControllerFactory::ConcreteInputController::RequirementContainer)

Method to start the arraying process. This method submits all of the
completed (processed) Requirement objects to the arrayData method and
sets the session id.

=cut

    sub arrayContainerData {
	my ($self,$reqContainer) = @_;
	foreach my $req (@{$reqContainer->getRequirementList}) {
	    unless($req->getType eq 'Input') {
		warn "*** Arraying Requirement: <$req> '".
                    $req->getData."'\n";
		$self->arrayData($req);
	    }
	}
    }


=item $ic->arrayData(APE::InputControllerFactory::ConcreteInputController::Requirement)

Method to complete a generic arraying of a single AtomicProcess object
that may or may not be in a RequirmentContainer object.  This method
is happy to handle arraying entity and atomic process index numbers
contained within arrays i.e. multiple index pointers to mulitple
ArrayingEntity and/or AtomicProcess objects to array single pieces of
data to.

=cut

    sub arrayData {
	my ($self,$requirement) = @_;
	return if $requirement->getType() eq 'Input';
	$requirement->notify;
    }



    #################################################################
    ## Method to set the sessionId attribute for this object.
    #################################################################
    sub setSessionId {
	my ($self,$sessionId) = @_;
	unless($sessionId) { 
 	    eval  { $sessionId = $self->getSessionId; };
 	    if($@){ croak "ERROR: failed to get session id: $@"; }
	}
	croak "ERROR: argument 'sessionId' must be numeric.\n" 
            unless $sessionId =~ /^\d+$/;
	$object{ $self }->{sessionId} = $sessionId;
	eval  { 
            $self->getAtomicProcessContainer->setSessionId
                ($object{ $self }->{sessionId})
            };
	if($@){ 
            croak "ERROR: can't set the sessionId: $@\n";
        }
    }


=item $ic->deleteSessionId()

Method to delete the sessionId private attribute.

=cut

    sub deleteSessionId {
	my $self = shift;
	return delete $object{ $self }->{sessionId};
    }


=item $ic->setContainerOutDescriptions()

Method to set the outDescription attribute of the RequirementContainer
objects after thay have been created.

=cut

    sub setContainerOutDescriptions {
	my $self = shift;
	foreach my $rc (@{$self->getRequirementContainerList}) {
	    next if $rc->isInput;
	    $rc->setOutDescription
		($self->getContainerSummary($rc));
	}
    }


=item $ic->getContainerSummary(APE::InputControllerFactory::ConcreteInputController::RequirementContainer)

Method to get a string summary of the output of the passed
RequirementContainer object.

=cut

    sub getContainerSummary {
	my ($self,$requirementContainer) = @_;
	return if $requirementContainer->isInput;
	my %lookup =();
	my %summary=();
	my $apc     = $self->getAtomicProcessContainer;
	my $reqList = $requirementContainer->getRequirementList;
	foreach my $requirement (@{$reqList}) {
	    next if $requirement->getType eq 'Input';
	    my $attachedList = $requirement->getAttachedList;
#	    my $aei = $requirement->getArrayingEntityIndex;
#	    my $api = $requirement->getAtomicProcessIndex;
#	    if(ref $aei ne 'ARRAY') {
#		my $taei = $aei;
#		my $tapi = $api;
#		$aei = [ $taei ];
#		$api = [ $tapi ];
#	    }
#	    for(my $i=0; $i < scalar(@{$aei}); $i++) {
#		my $ap = $apc
#		    ->getArrayingEntityList->[$aei->[$i]]
#		    ->getAtomicProcessList->[$api->[$i]];
	    foreach my $ap (@$attachedList) {
		my $outDesc           = $ap->getOutDescription;
		my $outConcreteEntity = $ap->getOutConcreteEntity;
		if(!exists($lookup{$outConcreteEntity})) {
		    $lookup{$outConcreteEntity}++;
		    $summary{$outDesc}++;
		}
	    }
	}
	return _summaryToString(\%summary);
    }


=item $ic->_summaryToString($summary)

Method to get a string summary of the output of the passed
RequirementContainer object.

=cut

    sub _summaryToString {
	my $summary = shift;
	my $summaryString;
	foreach (keys(%{$summary})) { 
            $summaryString .= "$summary->{$_} X $_, ";
        }
	$summaryString =~ s/,\s+$//;
	return $summaryString;
    }


=item $ic->execRules()

Method to execute any rules that are associated with this
InputController.

=cut

    sub execRules {
	my $self = shift;
	if(my $rulesHash = $self->getRules) {
	    while(my($key,$val)=each(%{$rulesHash})) {
		if(my $method = $self->can("rule_".ucfirst($key))) {
		    $self->$method($val);
		}else{
		    croak "ERROR: rule '$key' is unrecognised.";
		}
	    }
	}
    }


=item $ic->rule_Group($groupCount)

Method to group the requirements already created into new
RequriementContainers in batches of $groupCount Requirement
objects. This method is used to for example group sense and antisense
Requirement objects into a single RequirementContainer.

=cut

    sub rule_Group {
	my ($self,$groupCount) = @_;
	my @newReqContainers=();
	my $totalCount=0;
	my $newReqContainer;
	while(my $rc = $self->shiftRequirementContainer) {
	    push(@newReqContainers,$rc),
	    next if $rc->isInput;
	    my $requirementList  = $rc->getRequirementList;
	    my $requirementCount = scalar(@{$requirementList});
	    for(my $i=0; $i < $requirementCount; $i++, $totalCount++) {
		if($totalCount % $groupCount == 0 || !$totalCount) {
		    push(@newReqContainers, $newReqContainer)
			if defined $newReqContainer;
		    $newReqContainer = 
		    new APE::InputControllerFactory::ConcreteInputController::RequirementContainer();
		}
		$newReqContainer->pushRequirement($requirementList->[$i]);
	    }
	}
	push(@newReqContainers, $newReqContainer)
	    if defined $newReqContainer;

	foreach my $newRc (@newReqContainers) {
	    $self->pushRequirementContainer($newRc);
	}
    }


    #################################################################
    ## Method to find and return the AtomicProcess object based on the
    ## index values stored in the passed Requirement object. These
    ## indexes are the index values into the AtomicProcessContainer 
    ## objects array of ArrayingEntity objects and then into the 
    ## ArrayingEntity objects array of AtomicProcess objects.
    #################################################################
    #sub getAtomicProcess {
    #	my ($self,$requirementObj) = @_;
    #	return if $requirementObj->getType eq 'Input';
    #	my @atomicProcessList=();
    #	## Find the parentage of this Requirement object.
    #	my $apc = $self->getAtomicProcessContainer;
    #	my $aei = $requirementObj->getArrayingEntityIndex();
    #	my $api = $requirementObj->getAtomicProcessIndex();
    #	## If the indexes aren't in an array then put them in one.
    #	unless(ref($aei) eq 'ARRAY') {
    #	    $aei = [$aei];
    #	    $api = [$api];
    #	}
    #	## Find each of the AtomicProcess objects.
    #	for(my $i=0; $i < scalar(@{$aei}); $i++) {
    #        my $ael = $apc->getArrayingEntityList;
    #        my $ae  = $ael->[$aei->[$i]];
    #        my $apl = $ae->getAtomicProcessList;
    #        my $ap  = $apl->[$api->[$i]];
    #	    push(@atomicProcessList,$ap);
    #	}
    #	return \@atomicProcessList;
    #}


=item $ic->pushRequirmentContainer(APE::InputControllerFactory::ConcreteInputController::RequirementContainer)

Method to push a new Requirement Container object to the list.

=cut

    sub pushRequirementContainer {
	my ($self,$requirementContainer) = @_;
	return push
            (@{$object{ $self }->{requirementContainerList}},
             $requirementContainer);
    }


=item $ic->shiftRequirementContainer()

Method to shift a Requirement object off the list.

=cut

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


=item $ic->unshiftRequirementContainer(APE::InputControllerFactory::ConcreteInputController::RequirementContainer)

Method to unshift a RequirementContainer object on to the front of the
requirementContainerList attribute array value.

=cut

    sub unshiftRequirementContainer {
	my ($self,$requirementContainer) = @_;
	return unshift
            (@{$object{ $self }->{requirementContainerList}},
             $requirementContainer);
    }


=item $ic->hasRequirements()

Method to return boolean true if the Requirements have been made.

=cut

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


=item $inputController->getRequirements()

Gathers a list of Requirement objects from this InputController
objects list of RequirementContainer objects and returns it in an
array reference.

=cut

    sub getRequirementList {
	my $self = shift;
	my @requirements = ();
	my $rcList = $self->getRequirementContainerList();
	foreach my $rc (@$rcList) {
	    my $reqList = $rc->getRequirementList();
	    foreach my $requirement (@$reqList) {
		push(@requirements,$requirement);
	    }
	}
	return \@requirements;
    }


=item DESTROY

Destroys an instance of this class. Derived classes should call this
method as part of their destructor.

=cut

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

}


=back

=head1 ALSO SEE

APE::InputControllerFactory::ConcreteInputController::DefaultInputController

=head1 AUTHOR

Simon Holroyd <holroyd@sanger.ac.uk>

=cut

1;
