package GTx::APE::InputController::COL2PLATE_InputController;

use base 'GTx::APE::InputController';

use strict;
use warnings;
use Carp;

use GTx::APE::InputController::Requirement;
use GTx::APE::InputController::EPSA_InputController;

use constant TRUE  => 1;
use constant FALSE => 0;

{
    my %_attributes = ( );

    my %object;

    ## Autoloader wannabe. Inserts the sub calls that I want to be
    ## accessible into the symbol table.
    foreach my $attName (keys(%_attributes)) {
	no strict 'refs';
	*{__PACKAGE__ . "::get\u$attName"} = sub { $object{ $_[0] }->{$attName} }
    }


    #################################################################
    ## Initialise the objects private attributes. The argument is
    ## deleted as it is found, initialised and set.
    #################################################################
    sub _init {	
	my ($self,%args) = @_;
	foreach my $attName (keys(%args)) {
	    if(exists($_attributes{$attName})) { 
		$object{ $self }->{$attName} = delete $args{$attName};
	    }
	} 

  	## Set the description as this is a specialised InputController.
	$args{description} = "An InputController that adds a Requirement object to " . 
	    "the requirementList. The new Requirement object is type 'Input' and " . 
	    "requires the user to enter a plate well coord.";

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


    #################################################################
    ## Overridden method from base class to create the Requirement
    ## objects. Calls the base class method of the same name to build
    ## the basic number of Requirement objects. It then adds its own
    ## Requirement object (type='Input') that requests the user to
    ## enter a plate coord.
    #################################################################
    sub createRequirements {
	my $self = shift;
	print STDERR "Creating Requirement objects now....\n";
	my $atomicProcessContainer = ${$self->getAtomicProcessContainer()};
	## Make sure the ArrayingEntity objects held in the container
	## are aware that there may be missing arraying data from the
	## ArrayPattern objects.
	$atomicProcessContainer->setAllowMissingArrayData(TRUE);
	print STDERR "APC parent process id: ", $atomicProcessContainer->getParentProcessId(), "\n";
	my $arrayingEntityList     = $atomicProcessContainer->getArrayingEntityList();
	print STDERR "ArrayingEntityList has ", scalar(@{$arrayingEntityList}), " ArrayingEntity objects\n";
	my %lookupPlate  =();
	my %lookupColumn =();
	my %columnUsed   =();
	my $indexPlate   =-1;
	my $indexColumn  =-1;
	my @storePlate   =();
	my @storeColumn  =();
	## Create Requirements for each inConcreteEntity as the in is
	## split into 2 X 12 columns.
	for(my $aei=0; $aei < @{$arrayingEntityList}; $aei++) {
	    my $atomicProcessList = $arrayingEntityList->[$aei]->getAtomicProcessList();
	    for(my $api=0; $api < @{$atomicProcessList}; $api++) {
		my $description = $atomicProcessList->[$api]->getInDescription();
		my $inConcreteEntity  = $atomicProcessList->[$api]->getInConcreteEntity();
		my $outConcreteEntity = $atomicProcessList->[$api]->getOutConcreteEntity();
		
		## Sense and Antisense plate Requirement objects are
		## created here and pushed to the queue.
		if(!exists($lookupPlate{$inConcreteEntity})) {
		    $lookupPlate{$inConcreteEntity} = ++$indexPlate;
		    my $localDescription = $description . " - (".$atomicProcessList->[$api]->getInPrefix()."): ";
		    $storePlate[$lookupPlate{$inConcreteEntity}] 
			= new GTx::APE::InputController::Requirement
			(arrayingEntityIndex=>[$aei],
			 atomicProcessIndex =>[$api],
			 type               =>$atomicProcessList->[$api]->getInPrefix(),
			 description        =>$localDescription);
		    #print STDERR "Created a new Requirement -> $inConcreteEntity\n";
		}else{
		    ## Push the ArrayingEntity index to the Requirement object.
		    my $res = $storePlate[$lookupPlate{$inConcreteEntity}]->pushIndexes($aei,$api);
		    #print STDERR "Pushed '$aei' and -1 to '$inConcreteEntity' - ". 
		    #"$lookupPlate{$inConcreteEntity} req size = $res->[0] and $res->[1]\n";
		}

		## Column Requirements are created here but not pushed
		## to the queue so that that queue order can be
		## created.
		my $atomicProcess    = $atomicProcessList->[$api];
		my $arrayPatternHash = $atomicProcess->getArrayPatternHash();
		my $columnNumber     = 0;
		my $key              = $outConcreteEntity;
		## Only find the column number if the in entity is type 'EP'.
		## Set the key to the column lookup to be the 
		if($atomicProcess->getInPrefix() eq 'EP') {
		    foreach my $mapId (keys(%{$arrayPatternHash})) {
			$columnNumber = $arrayPatternHash->{$mapId}->[0]->getInColumn();
			last;
		    }
		    ## Use the out concrete entity unless the column has been used,
		    ## in which case find the out concrete entity of the Requirement 
		    ## object and use that as the key to add new indexes.
		    $key = $columnUsed{$columnNumber} if exists $columnUsed{$columnNumber};
		}
		## Check if the out concrete entity id has been used before i.e if it
		## already has a Requirement object associated with it.
		if(!exists($lookupColumn{$key})) {
		    $lookupColumn{$key} = ++$indexColumn;
		    $storeColumn[$lookupColumn{$key}] = 
			new GTx::APE::InputController::Requirement
			(default            =>'y',
			 arrayingEntityIndex=>[$aei],
			 atomicProcessIndex =>[$api],
			 type               =>'Input',
			 checkingCode       =>qr/[y|n]$/);
		    #print STDERR "Creating Requirement: '$description' in($inConcreteEntity) " . 
		    #" out($outConcreteEntity) key($key) columnNum($columnNumber) - '$aei' '$api'\n"; 
		}else{
		    ## Push more indexes to the Requirement object.
		    $storeColumn[$lookupColumn{$key}]->pushIndexes($aei,$api);
		    #print STDERR "Pushing indexes - '$aei' '$api' - '$description' " . 
		    #	"in($inConcreteEntity) out($outConcreteEntity) key($key)\n";
		}

		## Set the description attribute for this columns Requirement object.
		if(($atomicProcess->getInPrefix() eq 'EP')&&(!exists($columnUsed{$columnNumber}))) {
		    $storeColumn[$lookupColumn{$key}]
			->setDescription("Use column $columnNumber [y|n][def:y]? ");
		    $columnUsed{$columnNumber} = $outConcreteEntity;
		    #print STDERR "Setting description - $columnNumber.\n";
		}
	    }
	}

	## Push the plate and column Requirement objects onto the
	## queue.
	foreach my $requirement (@storePlate,@storeColumn) {
	    $self->pushRequirement($requirement);
	}

	## Push a Study Requirement object if this is a PCR output.
	## Not tidy but it will do for phase 1.
	if($atomicProcessContainer->getParentProcessId() == 5739) {
	    $self->unshiftRequirement
		(new GTx::APE::InputController::Requirement
		 (arrayingEntityIndex=>-1,
		  atomicProcessIndex =>-1,
		  type               =>'Input',
		  default            =>'ExoSeq',
		  description        =>'Enter Study name [def:exoseq]: ',
		  checkingCode       => sub{
		      my ($self,$data) = @_;
		      if(uc($data) ne 'EXOSEQ') {
			  croak "ERROR: 'ExoSeq' is the only " . 
			      "study currently available.\n";
		      }
		  }));
	}

	## The getData method requires an EPSA_InputController method.
	$object{ $self }->{epsaInputController} =
	    new GTx::APE::InputController::EPSA_InputController
	    (atomicProcessContainer=>$self->getAtomicProcessContainer(),
	     sqlEngineObj          =>$self->getSqlEngineObj());
    }


    #################################################################
    ## Overridden method from base class to create the Requirement
    ## objects. Calls the base class method of the same name to build
    ## the basic number of Requirement objects. It then adds its own
    ## Requirement object (type='Input') that requests the user to
    ## enter a plate coord.
    #################################################################
    sub process {
	my ($self,$requirementObj) = @_;
	if($self->isDuplicateData($requirementObj)) {
	    croak "ERROR: '",$requirementObj->getData(), "' has already been entered.\n";
	}
	## Get data associated with Requirement.
	## Ignore 'Input' type Requirement objects.
	unless($requirementObj->getType() eq 'Input') {
	    $requirementObj = 
		eval  { $self->getData($requirementObj); };
	    if($@){ croak $@; }
	}
	## Push this Requirement onto the complete list.
	$self->pushCompletedRequirement($requirementObj);
	## Wait until all of the Requirement objects have been
	## returned.
	print STDERR "Testing: ",$self->getRequirementCount()." == ". 
	    $self->getCompletedRequirementCount()."\n";
	if($self->getRequirementCount() == $self->getCompletedRequirementCount()) {
	    ## Set the sessionId foreach ArrayingEntity via the AtomicProcessContainer.
	    eval  { ${$self->getAtomicProcessContainer()}->setSessionId($self->getSessionId()); };
	    if($@){ croak "ERROR: can't set the sessionId: $@\n";                               }
	    foreach my $req (@{$self->getCompletedRequirementList()}) {
		unless($req->getType() eq 'Input') {
		    $self->arrayData($req);
		}
	    }
	    $self->_clearArrayPatterns();
	}
    }


    #################################################################
    ## Method to array the data gathered from the database for each   
    ## requirement to each ArrayingEntity objects AtomicProcess.
    ## Over-rides the base class arrayData method.
    #################################################################
    sub arrayData {
	my ($self,$requirementObj) = @_;
	## Work out the parentage of this Requirement object. This
	## will be multiple Arraying Entity objects (well the indexes
	## to them in the AtomicProcessContainer).
	#print STDERR "Setting the arraying data for: ", $requirementObj->getData(), "\n";
	my $aeiList = $requirementObj->getArrayingEntityIndex();
	my $apiList = $requirementObj->getAtomicProcessIndex();
	my $atomicProcessContainer = ${$self->getAtomicProcessContainer()};
	for(my $i=0; $i < scalar(@{$aeiList}); $i++) {
	    my $aei = $aeiList->[$i];
	    my $api = $apiList->[$i];
	    my $arrayingEntity = $atomicProcessContainer->getArrayingEntityList()->[$aei];
	    my $atomicProcess  = $arrayingEntity->getAtomicProcessList()->[$api];
	    #print STDERR "Arraying AtomicProcess objects from ArrayingEntity: " . 
	    #$arrayingEntity->getOutConcreteEntity(), "\n";
	    
	    $atomicProcess->setArrayingData($requirementObj->getData(),
					    $requirementObj->getDataStore());
	    ## Verify the arrayed data integrity.
	    $self->verifyArrayingData($atomicProcess);
	    #print STDERR "AtomicProcess with inDataId: " .
	    #$atomicProcess->getInDataId(), "\n";
	}
    }


    #################################################################
    ## Method to alter the array patterns in each of the AtomicProcess
    ## objects pointed to by each of the Requirement objects held in
    ## the completedRequirementsList so that they match the selected
    ## well of the source plate.
    #################################################################
    sub _clearArrayPatterns {
	my $self = shift;
	print STDERR "Clearing ArrayingEntity objects...\n";
	foreach my $requirementObj (@{$self->getCompletedRequirementList()}) {
	    if($requirementObj->getType() eq 'Input') {
		if($requirementObj->getData() eq 'n') {
		    ## Clear the ArrayPattern data within each AtomicProcess
		    ## within each ArrayingEntity object.
		    my $aeiList = $requirementObj->getArrayingEntityIndex();
		    my $apiList = $requirementObj->getAtomicProcessIndex();
		    my $atomicProcessContainer  = ${$self->getAtomicProcessContainer()};
		    for(my $i=0; $i < scalar(@{$aeiList}); $i++) {
			my $aei = $aeiList->[$i];
			my $api = $apiList->[$i];
			my $arrayingEntity = $atomicProcessContainer->getArrayingEntityList()->[$aei];
			my $atomicProcess  = $arrayingEntity->getAtomicProcessList()->[$api];
			print STDERR "Clearing AtomicProcess array pattern data: ",
			$atomicProcess->getProcessId(),"\n";
			$atomicProcess->clearArrayingData();
		    }
		}
	    }
	}
    }

    
    #################################################################
    ## Overridden method from base class to get data associated with 
    ## less well known entity types. This method creates a new       
    ## EPSA_InputController and uses its getData method as it knows 
    ## how to distinquish between an EP sense and EP antisense entity
    ## and thus get the correct information from the database.
    #################################################################
    sub getData {
	my ($self,$requirementObj) = @_;
	print STDERR "Getting data for Requirement: '", $requirementObj->getData(), "'\n";
	my $completedRequirementObj = 
	    eval  { $object{ $self }->{epsaInputController}->getData($requirementObj); };
	if($@){ croak $@; }
	return $completedRequirementObj;
    }


    #################################################################
    ## Method to destroy this object.
    #################################################################
    sub DESTROY {
	my $self = shift;
	delete $object{$self};
	$self->SUPER::DESTROY;
    }
}

1;
