package APE::FlushControllerFactory::ConcreteFlushController::PC_FlushController;

use base 'APE::FlushControllerFactory::ConcreteFlushController::DefaultExoApeFlushController';

$APE::FlushControllerFactory::ConcreteFlushController::PC_FlushController::VERSION = '0.6';

use strict;
use warnings;
use Carp;
use SqlEngine2;
use Class::Throwable;

use APE::FlushControllerFactory::ConcreteFlushController::Result;
use APE::BarcodeFactory::PC_BarcodeFactory;

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

use constant LOGIN            => 'snp';
use constant PCR_PLATE_STATUS => 1;   ## Created.
use constant PCR_PLATE_TYPE   => 1;   ## ExoSeq.

$Class::Throwable::Debug = 0;
$SqlEngine2::verbosity   = 0;


## Constant indexes into the reportRaw array ref produced and stored
## in this objects Result object. If you add a new column then
## rememeber to increment the REPORT_COLUMN_COUNT value accordingly.
use vars qw/$SESSIONID $EP_PLATE_NAME $EP_IN_COORD $EP_IN_MAPID
    $EP_ID_WELL $DN_PLATE_NAME $DN_IN_COORD $DN_IN_MAPID $ID_DNA_WELL
    $ID_PCR_PLATE $EP_OUT_COORD $EP_OUT_MAPID $DN_OUT_COORD
    $DN_OUT_MAPID $ID_PCR_WELL   $REPORT_COLUMN_COUNT/;

$SESSIONID      = 0;
$EP_PLATE_NAME  = 1;
$EP_IN_COORD    = 2;
$EP_IN_MAPID    = 3;
$EP_ID_WELL     = 4;
$DN_PLATE_NAME  = 5;
$DN_IN_COORD    = 6;
$DN_IN_MAPID    = 7;
$ID_DNA_WELL    = 8;
$ID_PCR_PLATE   = 9;
$EP_OUT_COORD   = 10;
$EP_OUT_MAPID   = 11;
$DN_OUT_COORD   = 12;
$DN_OUT_MAPID   = 13;
$ID_PCR_WELL    = 14;

$REPORT_COLUMN_COUNT = 15;

    
{
    my %_attributes = ();

    my %object; # key = stringified object reference, value = hash of { key = attName, value = data }
    
    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};
	    }
	}   
	return $self->SUPER::_init(%args);
    }


    #################################################################
    ## Start a new transaction.
    #################################################################
    sub beginTransaction {
    	my $self = shift;
    	$object{ $self }->{sqlEngine} = new SqlEngine2();
    	$object{ $self }->{dbh} = SqlEngine2::getDbh(LOGIN);
    	$object{ $self }->{sqlEngine}->beginTransaction( SqlEngine2::getDbh(LOGIN) );
    }

    
    #################################################################
    ## Over-ridden method from FlushController base class.
    #################################################################
    sub flush {
	my $self = shift;
	
	my $entity    = $self->getArrayingEntity();
	my $user      = ${$self->getUserObj};
	my $parentProcessId = $entity->getParentProcessId();
	my $outFormat       = $entity->getOutFormat();
	my $sessionId       = $entity->getSessionId();
	my $programName     = $self->getProgramName();


	my $team   = $user->getWorkingTeam()->getTeamName();
	my $roleId = $user->getWorkingTeam()->getWorkingRole()->getRoleId();

	croak "ERROR: there is no working team set.\n" unless $team;
	croak "ERROR: there is no working role set.\n" unless $roleId;
	croak "ERROR: there is no sessionId set.\n"    unless $sessionId;
	croak "ERROR: there is no program name set.\n" unless $programName;

	## This flushes mixer primer ArrayPattern hash.
	#my $mixerAtomicProcessHash; 
	my @mixerAtomicProcessList = ();
	#my @mixerApHashList = ();
	my @mixerEpList     = ();
	my ($idStudy,$epCount,$barcodeEpName);

	## Find the mixer AtomicProcess object.
	foreach my $atomicProcess (@{$entity->getAtomicProcessList()}) {
	    if($atomicProcess->getInPrefix() eq 'EP') {
		my $epAtomicProcessId = $atomicProcess->getProcessId;
		my $data = $atomicProcess->getInDataId;
		warn "*** ID_STUDY(before): $data->[1] -> ".ref $data->[1]."\n";

		## Get the study id and the name of this EP source plate.
		my $epName  = $data->[0];
		$idStudy    = ${$data->[1]};
		warn "*** ID_STUDY(after): $idStudy -> ".ref $idStudy."\n";

		#$mixerAtomicProcessHash = $atomicProcess->getArrayPatternHash();
		my $mixHash = $atomicProcess->getArrayPatternHash();


		## Store multiple EP plate information.
		push(@mixerAtomicProcessList,$atomicProcess);
		#push(@mixerApHashList,$mixHash);
		push(@mixerEpList,[$epName,$epAtomicProcessId]);

		## Set the name to be put on the barcode. If there are multiple 
		## EP source plates then just prefix the name of the first plate
		## with 'multi'.
		if(!$epCount)     { $barcodeEpName = $epName;                }
		elsif($epCount==1){ $barcodeEpName = "multi:$barcodeEpName"; }
		$epCount++;
	    }
	}

	## No EP mixer source plate or study id means we can't carry on.
	## keys %{$mixerAtomicProcessHash};#@mixerApHashList);
	croak "ERROR: no primer mixer plate found in ArrayingEntity.\n" unless scalar(@mixerAtomicProcessList);
	croak "ERROR: no id_study found in 'EP' AtomicProcess object: >>$idStudy<<" unless $idStudy;

	warn "\n\n**** Using study id: $idStudy ****\n\n";

	## Create the one and only PCR plate.
	my $idPcrPlate = eval {
	    $self->createPcrPlate(sessionId=> $sessionId,
				  plateType=> PCR_PLATE_TYPE,
				  status   => PCR_PLATE_STATUS,
				  studyId  => $idStudy,
				  'format' => $outFormat,
				  team     => $team,
				  roleId   => $roleId,
				  program  => $programName); };
	if($@){ croak "ERROR: can't create new oligo plate: $@\n"; }

	warn "Created new PCR plate....\n";
	warn "SessionId: $sessionId\nPlateType: ". PCR_PLATE_TYPE. "\nStatus: ". PCR_PLATE_STATUS . 
	    "\nFormat: $outFormat\nTeam: $team\nRoleId: $roleId\n";
	my @summary;
	my $columnNumber;

	## Create the new PCR plate based on the array data.
	foreach my $atomicProcess (@{$entity->getAtomicProcessList()}) {
	    next if $atomicProcess->getInPrefix() eq 'EP';
	    my $aph         = $atomicProcess->getArrayPatternHash();
	    my $dnInData    = $atomicProcess->getInDataId();
	    my $inPrefix    = $atomicProcess->getInPrefix();
	    my $dnPlateName = $dnInData->[0];
	    foreach my $inMapId (keys(%{$aph})) {
		foreach my $arrayPattern (sort{$a->getOutMapId() <=> $b->getOutMapId()} @{$aph->{$inMapId}}) {
		    ## Grab ArrayPattern data.
		    my $inMapId         = $arrayPattern->getInMapId();
		    my $inRow           = $arrayPattern->getInRow();
		    my $inCol           = $arrayPattern->getInColumn();
		    my $outRow          = $arrayPattern->getOutRow();
		    my $outCol          = $arrayPattern->getOutColumn();
		    my $outMapId        = $arrayPattern->getOutMapId();
		    my $outData         = $arrayPattern->getOutData()->[0];
		    my $epArrayPattern  = undef;
		    my $epAtomicProcess = undef;

		    ## Match the primer ArrayPattern object to the DN
		    ## ArrayPattern object using the outMapId
		    ## attribute. This returns the parent EP mixer
		    ## AtomicProcess object and the matching
		    ## ArrayPattern object.
		    unless(($epAtomicProcess,$epArrayPattern) 
			   = _matchAtomicProcessArrayPattern(\@mixerAtomicProcessList,$arrayPattern)) {
			croak "ERROR: can't match ArrayPattern object output to EP ArrayPattern object output\n";
		    }
		    #warn "EP ArrayPattern has '". scalar(@{$epArrayPattern->getOutData()}). "' with '".
		    #scalar(@{$epArrayPattern->getOutData()->[0]}). "' data in the 0 index.\n";

		    ## Get the well details to match the DNA to the primer.
		    my $idDnaWell   = $outData->[3];
		    my $epInData    = $epAtomicProcess->getInDataId;
		    my $epInMapId   = $epArrayPattern->getInMapId;
		    my $epInRow     = $epArrayPattern->getInRow();
		    my $epInCol     = $epArrayPattern->getInColumn();
		    my $epOutData   = $epArrayPattern->getOutData();
		    my $epOutMapId  = $epArrayPattern->getOutMapId;
		    my $epOutRow    = $epArrayPattern->getOutRow;
		    my $epOutCol    = $epArrayPattern->getOutColumn;
		    my $idWell      = $epOutData->[0][6];
		    my $idWell2     = $epOutData->[1][6];

		    ## Find and store the EP plate name.
		    my $epPlateName = $epInData->[0];
		    
		    ## Capture the column number.
		    if($inRow . $inCol eq 'a1' && !$columnNumber) {
			$columnNumber = $epArrayPattern->getInColumn();
			warn "Storing column number $columnNumber\n";
		    }

		    ## There should always be an id_dnawell.
		    croak "ERROR: id_dnawell is missing from ArrayPattern within AtomicProcess " . 
			"with parent process id: $parentProcessId\n"
			unless $idDnaWell;
		    
		    ## There should always be an id_well.
		    croak "ERROR: an id_well is missing from ArrayPattern within AtomicProcess " . 
			"with parent process id: $parentProcessId\n"
			unless $idWell && $idWell2;

		    ## The well id's should always be the same - sense and antisense.
		    croak "ERROR: id_well of primer 1 does not match id_well of primer 2: $idWell != $idWell2\n"
			unless $idWell == $idWell2;

		    ## Get the next PCR well id.
		    my $idPcrWell = eval  { $self->getNextPcrWell(); };
		    if($@){ croak $@;                                }

		    ## Gather and store the summary data that shows
		    ## source and target information.
		    push(@summary,[$sessionId,
				   $epPlateName,
				   $epInRow.$epInCol, 
				   $epInMapId,
				   $idWell, 
				   $dnPlateName,
				   $inRow.$inCol, 
				   $inMapId,
				   $idDnaWell, 
				   $idPcrPlate, 
				   $epOutRow.$epOutCol, 
				   $epOutMapId,
				   $outRow.$outCol, 
				   $outMapId,
				   $idPcrWell]);
		    
		    ## Create the new PCR plate well with contents.
		    eval  { $self->newPcrPlateWell($idPcrWell,$arrayPattern->getOutMapId(),
						   $idPcrPlate,$idDnaWell,$idWell); };
		    if($@){ croak "ERROR: can't create new PCR plate well: $@\n"    }
		}
	    }


	    ## Populate the exo_process table with parentage
	    ## data. This will/should mean a new entry for each of the
	    ## DNA (DN) plates used to create this PCR (PC) plate.
	    $self->populateExoProcess(-sqlEngine    => $object{ $self }->{sqlEngine}, 
				      -dbh          => $object{ $self }->{dbh},
				      -processId    => $atomicProcess->getProcessId,
				      -inputEntity  => $dnPlateName,
				      -inputPrefix  => $atomicProcess->getInPrefix, 
				      -outputId     => $idPcrPlate, 
				      -outputPrefix => 'PC');
	}


	## Add a new parentage entry to the exo_process table for each
	## of the source EP mixer plates used to create this PCR (PC)
	## plate.
	foreach my $epPlateInfo (@mixerEpList) {
	    my $name      = $epPlateInfo->[0];
	    my $processId = $epPlateInfo->[1];
	    ## Populate the exo_process table with each EP primer plate parentage data.
	    $self->populateExoProcess(-sqlEngine    => $object{ $self }->{sqlEngine}, 
				      -dbh          => $object{ $self }->{dbh},
				      -processId    => $processId,
				      -inputEntity  => $name,
				      -inputPrefix  => 'EP', 
				      -outputId     => $idPcrPlate, 
				      -outputPrefix => 'PC');
	}

	## Delegate the creation of the description to the
	## BarcodeFactory - god knows what it'll produce!
	my $barcodeFactory = new APE::BarcodeFactory::PC_BarcodeFactory
	    (flushController => $self,
	     processId       => $parentProcessId,
	     entityId        => $idPcrPlate,
	     reservedData    => [$barcodeEpName,$columnNumber]);

	my $barcodeObj = $barcodeFactory->getInstance; ## Returns a ConcreteBarcode object.
	my $barcode    = $barcodeObj->createBarcode;   ## Object creates barcode formatting.
 
	## Return a newly created Result object.
	return new APE::FlushControllerFactory::ConcreteFlushController::Result
	    (sessionId       => $sessionId,
	     id              => $idPcrPlate,
	     type            => 'PC',
	     description     => $barcode,
	     'format'        => $outFormat,
	     report          => $self->createReport(\@summary),
	     reportRaw       => \@summary,
	     flushController => \$self);
    }



    #################################################################
    ## Method to match an ArrayPattern from a DNA plate to an
    ## ArrayPattern from an EP plate using the output id_map. This
    ## method searches through the list of EP mix plate AtomicProcess
    ## objects searching their ArrayPattern hashes for a match to the
    ## passed in DNA plate ArrayPattern objects outMapId. If a match is
    ## made then the parent AtomicProcess object and the matching
    ## ArrayPattern object are returned, otherwise FALSE is returned.
    #################################################################
    sub _matchAtomicProcessArrayPattern {
	my ($mixerAtomicProcessList,$arrayPattern) = @_;
	my $outMapId = $arrayPattern->getOutMapId();
	foreach my $atomicProcess (@$mixerAtomicProcessList) {
	    my $mixerAtomicProcessHash = $atomicProcess->getArrayPatternHash();
	    foreach my $mapId (keys(%{$mixerAtomicProcessHash})) {
		foreach my $arrayPattern (@{$mixerAtomicProcessHash->{$mapId}}) {
		    if($arrayPattern->getOutMapId() == $outMapId) {
			return ($atomicProcess,$arrayPattern);
		    }
		}
	    }
	}
	return FALSE;
    }

    #################################################################
    ## Method to match an ArrayPattern from a DNA plate to an
    ## ArrayPattern from an EP plate using the output id_map.
    ################################################################# 
    #sub _matchArrayPattern {
    #	#my ($mixerAtomicProcessHash,$arrayPattern) = @_;
    #	my ($mixerApList,$arrayPattern) = @_;
    #	my $outMapId = $arrayPattern->getOutMapId();
    #	foreach my $mixerAtomicProcessHash (@$mixerApList) {
    #	    foreach my $mapId (keys(%{$mixerAtomicProcessHash})) {
    #		foreach my $arrayPattern (@{$mixerAtomicProcessHash->{$mapId}}) {
    #		    if($arrayPattern->getOutMapId() == $outMapId) {
    #			return $arrayPattern;
    #		    }
    #		}
    #	    }
    #	}
    #	return FALSE;
    #}


    #################################################################
    ## Method to output a sorted summary of the insertion.
    #################################################################
    sub createReport {
	my ($self,$summary) = @_;
	my @report=();
	my $count =0;

	push(@report,sprintf("%-13s %-13s %-13s %-13s %-13s %-13s %-13s ".
			     "%-13s %-13s %-13s %-13s %-13s %-13s %-13s %-13s\n",
			     "SessionId","EpPlateName","EpCoord","EpInMapId","EpIdWell",
			     "DnPlateName","DnCoord","DnInMapId","DnIdWell","PcPlateName",
			     "PcEpCoord","PcEpMapId","PcDnCoord","PcDnMapId","PcIdWell"));
	push(@report,sprintf("%-13s %-13s %-13s %-13s %-13s %-13s %-13s ".
			     "%-13s %-13s %-13s %-13s %-13s %-13s %-13s %-13s\n",
			     "-"x13,"-"x13,"-"x13,"-"x13,"-"x13,"-"x13,"-"x13,"-"x13,
			     "-"x13,"-"x13,"-"x13,"-"x13,"-"x13,"-"x13,"-"x13));
	foreach my $line (sort{ $a->[13] <=> $b->[13]  } @{$summary}) {
	    push(@report,sprintf("%-13s %-13s %-13s %-13s %-13s %-13s %-13s %-13s ".
				 "%-13s %-13s %-13s %-13s %-13s %-13s %-13s\n",
				 $line->[0],$line->[1],$line->[2],$line->[3],$line->[4],
				 $line->[5],$line->[6],$line->[7],$line->[8],$line->[9],
				 $line->[10],$line->[11],$line->[12],$line->[13],$line->[14]));
	    $count++;
	}
	push(@report,"\nCount: $count\n");
	return \@report;
    }


    #################################################################
    ## Method to create a new DNA plate with a status.
    #################################################################
    sub createPcrPlate {
	my ($self,%options) = @_;
	croak "ERROR: argument plateType is missing."   unless $options{plateType};
	croak "ERROR: argument format is missing."      unless $options{format};
	croak "ERROR: argument team is missing."        unless $options{team};
	croak "ERROR: argument roleId is missing."      unless $options{roleId};
	croak "ERROR: argument program is missing."     unless $options{program};
	croak "ERROR: argument sessionId is missing."   unless $options{sessionId};
	croak "ERROR: argument status is missing."      unless $options{status};
	croak "ERROR: argument studyId is missing."     unless $options{studyId};
	#croak "ERROR: argument createdFrom is missing." unless $options{createdFrom};
	## Get the next id_pcrplate.
	my $idPcrPlate = eval { $self->getNextPcrPlateId(); };
	if($@) { croak "ERROR: can't get new id_pcrplate: $@\n"; }
	## Create the new pcr plate.
	eval  { $self->newPcrPlate($idPcrPlate,$options{format},$options{studyId},
				   $options{team},$options{plateType}); };
	if($@){ croak "ERROR: can't create new pcrplate: $@\n";  }
	## Create a new pcr plate status.
	eval  { $self->newPcrPlateStatus($idPcrPlate,$options{status},$options{roleId},
					   $options{sessionId},$options{program}); };
	if($@){ croak "ERROR: can't create new pcrplate status: $@\n";  }
	warn "Created pcr plate: $idPcrPlate\n";
	return $idPcrPlate;
    }


    #################################################################
    ## Method to get the next id_pcrplate from the sequence.
    #################################################################
    sub getNextPcrPlateId {
    	my $self = shift;
    	my $idPcrPlate = 
    	    eval { $object{ $self }->{sqlEngine}
    		   ->getRow('ExoSeq::next_pcrplate',
    			    [],$object{ $self }->{dbh});};
    	
    	if($@) { croak $@;           }
    	else   { return $idPcrPlate; }
    }
    

    #################################################################
    ## Method to get the next id_pcrwell from the sequence.
    #################################################################
    sub getNextPcrWell {
    	my $self = shift;
    	my $idPcrWell = 
    	    eval { $object{ $self }->{sqlEngine}->getRow('ExoSeq::next_pcrwell',
    							 [],$object{ $self }->{dbh}); };
    	if($@) { croak $@;          }
    	else   { return $idPcrWell; }
    }
    
    
    #################################################################
    ## Method to create a new Pcr plate.
    #################################################################
    sub newPcrPlate {
    	my ($self,$idPcrPlate,$format,$studyId,$team,$pcrPlateType) = @_;
    	eval  { $object{ $self }->{sqlEngine}->do('ExoSeq::insert_pcrplate_study',
    						  [$idPcrPlate,$format,$studyId,$team,$pcrPlateType], 
    						  $object{ $self }->{dbh}); };
    	if($@) { croak $@; }
    }


    #################################################################
    ## Method to create a new Pcr plate status.
    #################################################################
    sub newPcrPlateStatus {
    	my ($self,$idPcrPlate,$status,$idRole,$sessionId,$program) = @_;
    	eval { $object{ $self }->{sqlEngine}->do('ExoSeq::insert_pcrplatestatus',
    						 [$idPcrPlate,$status,1,$idRole,$sessionId,$program],
    						 $object{ $self }->{dbh}); };
    	if($@) { croak $@; }
    }


    #################################################################
    ## Method to create a Pcr well in a Pcr plate.
    #################################################################
    sub newPcrPlateWell {
    	my ($self,$idPcrWell,$idMap,$idPcrPlate,$idDnaWell,$idWell) = @_;
    	eval { $object{ $self }->{sqlEngine}->do('ExoSeq::insert_pcrwell',
    						 [$idPcrWell,$idMap,$idPcrPlate,$idDnaWell,$idWell],
    						 $object{ $self }->{dbh}); };
    	if($@) { croak $@; }
    }


    #################################################################
    ## Over-ridden method from FlushController base class.
    #################################################################
    sub rollbackTransaction {
    	my $self = shift;
    	$object{ $self }->{sqlEngine}->rollbackTransaction( $object{ $self }->{dbh} );
    }


    #################################################################
    ## Over-ridden method from FlushController base class.
    #################################################################
    sub commitTransaction {
    	my $self = shift;
    	$object{ $self }->{sqlEngine}->commitTransaction( $object{ $self }->{dbh} );
    }

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

1;
