package GTx::APE::AtomicProcessContainer;

BEGIN {
    $ENV{'FLUSH_CONTROLLER_HOME'} =  "/usr/local/badger/lib/perl5/"; #"/nfs/team71/usg/holroyd/badger-cvs/";
    $ENV{'INPUT_CONTROLLER_HOME'} =  "/usr/local/badger/lib/perl5/"; #"/nfs/team71/usg/holroyd/badger-cvs/"; 
};

use lib $ENV{'FLUSH_CONTROLLER_HOME'};
use lib $ENV{'INPUT_CONTROLLER_HOME'};

use strict;
use warnings;
use Carp;

use GTx::APE::ArrayingEntity;
use GTx::APE::InputController;
use GTx::APE::FlushController;

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

my %icLookup = ( ## key   = process id 
		 ## value = { classPrefix => '', description => '' }
		 5643     => { classPrefix => 'COORD2PLATE',
			       description => 'Request plate coord and maps coord to new plate -5643.' },
		 5740     => { classPrefix => 'COL2PLATE',
			       description => 'Request default or plate columns to create new plates -5740.' },
		 5741     => { classPrefix => 'EPSA',
			       description => 'Creates default Requirements. Knows about sense and antisense -5741.'},
		 5672     => { classPrefix => 'PCSTUDY',
			       description => 'Creates default Requirements. Knows about sense and antisense -5672.'},
		 5644     => { classPrefix => 'EPSA',
			       description => 'Creates default Requirements. Knows about sense and antisense -5644.'},
		 5678     => { classPrefix => 'EPSA',
			       description => 'Creates default Requirements. Knows about sense and antisense -5678.'},
		 5683     => { classPrefix => 'EPSA',
			       description => 'Creates default Requirements. Knows about sense and antisense -5683.'},
		 5739     => { classPrefix => 'COL2PLATE',
			       description => 'Request default or plate columns to create new plates -5739.' },
		 5675     => { classPrefix => 'DTUNIQUEIND',
			       description => 'Checks for duplicate individual DNA in DT entities. -5675.'},
		 5762     => { classPrefix => 'PLATECOUNT',
			       description => 'Asks for the number of plates and configures requirements -5762.'},
		); 

{
    my %_attributes = (
		       parentProcessId    => undef,
		       inputController    => undef,
		       userObj            => undef,
		       atomicProcessList  => [],
		       arrayingEntityList => [],
		       );
    
    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';
	## Don't include the InputController.
	next if $attName eq 'inputController';
	*{__PACKAGE__ . "::get\u$attName"} = sub { $object{ $_[0] }->{$attName} }
    }

    
    #################################################################
    ## Constructor method for this object. Returns a new
    ## AtomicProcessContainer object.
    #################################################################
    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;

	$self->_createArrayingEntities();
	$self->_addInputController();

	return $self;
    }


    #################################################################
    ## 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};
	    }
	}

	## Attribute parentProcessId must be set.
	croak "ERROR: attribute parentProcessId must be set."
	    unless $object{ $self }->{parentProcessId};

	## Attribute atomicProcessList must be set.
	croak "ERROR: attribute atomicProcessList must be set."
	    unless $object{ $self }->{atomicProcessList};

	## Attribute userObj must be set.
	croak "ERROR: attribute userObj must be set with a User object."
	    unless $object{ $self }->{userObj} && 
	    ${$object{ $self }->{userObj}}->isa('User');

	return %args;
    }
    

    #################################################################
    ## Method to create the ArrayingEntity objects that contain the data
    ## for a single output. 
    #################################################################
    sub _createArrayingEntities {
	my $self = shift;
	my %lookup = ();
	
	## Create the new ArrayingEntity objects based on their output
	## concrete entity id number.
	foreach my $ap (@{$object{ $self }->{atomicProcessList}}) {
	    $ap->clearArrayingData; ## Make sure everything is clean.
	    if(!exists($lookup{ $ap->getOutConcreteEntity() })) {
		$lookup{ $ap->getOutConcreteEntity() } = 
		    new GTx::APE::ArrayingEntity(parentProcessId => $object{ $self }->{parentProcessId},
						 outPrefix       => $ap->getOutPrefix(),
						 outDescription  => $ap->getOutDescription(),
						 outFormat       => $ap->getOutFormat());
	    }
	    $lookup{ $ap->getOutConcreteEntity() }->newAtomicProcess($ap);
	}
	## Sort and push the new ArrayingEntity objects to the
	## arrayingEntityList attribute.
	foreach my $key (sort{$lookup{$a}->getOutConcreteEntity() <=> $lookup{$b}->getOutConcreteEntity()} 
			 keys %lookup) {
	    $self->_addFlushController( $lookup{$key} );
	    push( @{$object{ $self }->{arrayingEntityList}}, $lookup{$key} );
	}
    }
    

    #################################################################
    ## Method to add a FluchController object to each of the newly 
    ## created ArrayingEntity objects.    
    #################################################################
    sub _addFlushController {
	my ($self,$arrayingEntity) = @_;
	my $flushControlClass;
	my $className = $arrayingEntity->getOutPrefix() . "_FlushController";
	## Add a specialised flush controller to the ArrayingEntity
	## object. The flush controller will take charge of flushing
	## the contents of this ArrayingEntity object.
	if(-e $ENV{'FLUSH_CONTROLLER_HOME'} . "GTx/APE/FlushController/$className\.pm") {
	    require "GTx/APE/FlushController/$className\.pm";
	    #warn "*** Creating new specialised FlushController object - $className\n";
	    $flushControlClass = 
		"GTx::APE::FlushController::$className"->new(arrayingEntity => \$arrayingEntity,
							     userObj        => $object{ $self }->{userObj},
							     programName    => $0);
	}
	## Add the default flush controller N.B. The default flush
	## controller is the base class to all specialised fluch
	## controller
	else{
	    warn "WARNING: specialised FlushController class NOT found - '$className'\n" .
		"WARNING: using the default FlushController class.\n";
	    $flushControlClass = new GTx::APE::FlushController(arrayingEntity => \$arrayingEntity,
							       userObj        => $object{ $self }->{userObj},
							       programName    => $0);
	}

	$arrayingEntity->addFlushController($flushControlClass);
    }


    #################################################################
    ## Method to add a new InputController to this AtomicProcessContainer
    ## object. This object know how to deal with data from outside this
    ## library.
    #################################################################
    sub _addInputController {
	my $self = shift;
	if(exists($icLookup{ $object{ $self }->{parentProcessId} })) { 
	    my $className = $icLookup{$object{ $self }->{parentProcessId}}{classPrefix} . "_InputController";
	    ## Add a specialised input controller to this AtomicProcessContainer
	    ## object. The input controller will take charge of receiving
	    ## external data and the arraying of object.
	    if(-e $ENV{'INPUT_CONTROLLER_HOME'} . "GTx/APE/InputController/$className\.pm") {
		require "GTx/APE/InputController/$className\.pm";
		$object{ $self }->{inputController} = 
		    "GTx::APE::InputController::$className"
		    ->new(atomicProcessContainer=>\$self);
		warn "*** ATTACHED SPECIALISED InputController class: '$className' to parent process id: " .
		    $object{ $self }->{parentProcessId} . "\n";
		return;
	    }
	    warn "WARNING: Process Id: ", $object{ $self }->{parentProcessId}, " exists in icLookup hash but not " .
		"in the INPUT_CONTROLLER_HOME: '", $ENV{'INPUT_CONTROLLER_HOME'}, "'";
	}
	
	## Add the default input controller N.B. The default input
	## controller is the base class to all specialised input
	## controller objects.
	warn "WARNING: Attaching the default InputController to AtomicProcessController with parent process id: " .
	    $object{ $self }->{parentProcessId} . "\n";
	$object{ $self }->{inputController} = 
	    new GTx::APE::InputController(description           =>"Default InputController",
					  atomicProcessContainer=>\$self);
    }


    #################################################################
    ## Method to summarise the AtomicProcess objects stored in this
    ## container in to the in's and out's.
    #################################################################
    sub getSummary {
	my $self = shift;
	my @summary = ();
	my %lookup  = ();
	my %count   = ();
	foreach my $ap (@{$object{ $self }->{atomicProcessList}}) {
	    if(!exists($lookup{in}{ $ap->getInConcreteEntity() })) { 
		$lookup{in}{ $ap->getInConcreteEntity() }++;
		$count {in}{ $ap->getInDescription() }++;
	    }
	    if(!exists($lookup{out}{ $ap->getOutConcreteEntity() })) { 
		$lookup{out}{ $ap->getOutConcreteEntity() }++;
		$count {out}{ $ap->getOutDescription() }++;
	    }
	}
	foreach my $io (sort keys(%count)) {
	    foreach my $desc (sort {$count{$io}{$a} cmp $count{$io}{$b}} keys(%{$count{$io}})) {
		push( @summary, sprintf("%-3s - %-5d X %-s",$io,$count{$io}{$desc},$desc) );
	    }
	}
	return \@summary;
    }


    #################################################################
    ## Method to return the attached InputController object. Calls the
    ## createRequirements() method on the InputController.
    #################################################################
    sub getInputController {
	my $self = shift;
	$object{ $self }->{inputController}->createRequirements();
	#print STDERR "Printer in AtomicProcessContainer is: ",
	#${$object{ $self }->{userObj}}->getWorkingTeam()->getDefaultBarcodePrinter()->getName(), "\n";
	return $object{ $self }->{inputController};
    }


    #################################################################
    ## Method to signify the begining of a transaction. See the
    ## ArrayingEntity for more information.
    #################################################################
    sub beginTransaction {
    	my $self = shift;
	print STDERR "AtomicProcessContainer: beginTransaction\n";
	foreach my $arrayingEntity (@{$object{ $self }->{arrayingEntityList}}) {
	    eval  { $arrayingEntity->beginTransaction(); };
	    if($@){ croak $@;                            }
	}
	
    }


    #################################################################
    ##Method to flush an ArrayingEntity objects array data. Calls the
    ##flush method of the ArrayingEntity object. See the
    ##ArrayingEntity for more information.
    #################################################################
    sub flush {
	my $self = shift;
	print STDERR "AtomicProcessContainer: flush\n";
	my @store=();
	foreach my $arrayingEntity (@{$object{ $self }->{arrayingEntityList}}) {
	    my $result;
	    eval  { $result = $arrayingEntity->flush(); };
	    if($@){ croak $@;                           }
	    else  { push(@store, $result) if $result;   }
	}
	return \@store;
    }

    
    #################################################################
    ## Method to rollback an unsuccessful transaction. Calls the
    ## ArrayingEntity objects rollbackTransaction. See the
    ## ArrayingEntity for more information.
    #################################################################
    sub rollbackTransaction {
	my $self = shift;
	print STDERR "AtomicProcessContainer: rollbackTransaction\n";
	foreach my $arrayingEntity (@{$object{ $self }->{arrayingEntityList}}) {
	    eval  { $arrayingEntity->rollbackTransaction(); };
	    if($@){ croak $@;                               }
	}
    }


    #################################################################
    ## Method to commit a transaction that has been completed
    ## successfully.  Calls the ArrayingEntity objects
    ## commitTransaction method.  See the ArrayingEntity for more
    ## information.
    #################################################################
    sub commitTransaction {
	my $self = shift;
	print STDERR "AtomicProcessContainer: commitTransaction\n";
	foreach my $arrayingEntity (@{$object{ $self }->{arrayingEntityList}}) {
	    $arrayingEntity->commitTransaction();
	}
    }

    
    #################################################################
    ## Method to return the number of ArrayingEntity objects in this
    ## container.
    #################################################################
    sub getArrayingEntityCount {
	my $self = shift;
	return scalar( @{$object{ $self }->{arrayingEntityList}});
    }


    #################################################################
    ## Method to return the number of AtomicProcess objects in this
    ## container.
    #################################################################
    sub getAtomicProcessCount {
	my $self = shift;
 	return scalar( @{$object{ $self }->{atomicProcessList}} );
    }


    #################################################################
    ## Method to set the sessionId number for all of the ArrayingEntity
    ## objects.
    #################################################################
    sub setSessionId {
	my ($self,$sessionId) = @_;
	croak "ERROR: argument 'sessionId' is missing.\n"       unless $sessionId;
	croak "ERROR: argument 'sessionId' must be numberic.\n" unless $sessionId =~/^\d+$/;
	foreach my $ae (@{$object{ $self }->{arrayingEntityList}}) {
	    $ae->setSessionId($sessionId);
	}
    }

    
    #################################################################
    ## Method to the allowMissingArrayData flag for each of this
    ## containers ArrayingEntity objects.
    #################################################################
    sub setAllowMissingArrayData {
	my ($self,$state) = @_;
	foreach my $arrayingEntity (@{$object{ $self }->{arrayingEntityList}}) {
	    $arrayingEntity->setAllowMissingArrayData($state ? TRUE : FALSE);
	}
    }


    #################################################################
    ## Method to clean up after this AtomicProcessContainer has been
    ## manipulated by either an InputController or a FlushController
    ## object. This method simply recreates the ArrayingEntity objects
    ## based on the list of AtomicProcess objects held in the
    ## atomicProcessList attribute that should not be touched during
    ## process execution (no functions are offered to manipulate this
    ## attribute on purpose).
    #################################################################
    sub clean {
	my $self = shift;
	print STDOUT "**** CLEANING UP ****\n";
	$object{ $self }->{arrayingEntityList} = [];
	$object{ $self }->{inputController}    = undef;
	$self->_createArrayingEntities();
	$self->_addInputController();
    }


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

1;
