package APE::FlushController::DT_FlushController;

use base 'APE::FlushController';

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

use APE::FlushController::Result;
use APE::BarcodeFactory::DT_BarcodeFactory;

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

use constant LOGIN => 'snp';
use constant DNATUBE_STATUS_CREATED => 2;

$Class::Throwable::Debug = 0;
$SqlEngine2::verbosity   = 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};
	    }
	}   
	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 programName set.\n"  unless $programName;
	
	my @results;
	my @summary;

	foreach my $atomicProcess (@{$entity->getAtomicProcessList()}) {
	    my $inDataId  = $atomicProcess->getInDataId();
	    my $aph       = $atomicProcess->getArrayPatternHash();
	    my $processId = $atomicProcess->getProcessId;
	    my $inPrefix  = $atomicProcess->getInPrefix;
	    foreach my $inMapId (keys(%{$aph})) {
		foreach my $arrayPattern (sort{$a->getOutMapId() cmp $b->getOutMapId()} @{$aph->{$inMapId}}) {
		    my $inFormat = $arrayPattern->getInFormat();
		    my $inRow    = $arrayPattern->getInRow();
		    my $inCol    = $arrayPattern->getInColumn();
		    my $outRow   = $arrayPattern->getOutRow();
		    my $outCol   = $arrayPattern->getOutColumn();
		    my $outMapId = $arrayPattern->getOutMapId();
		    foreach my $outData (@{$arrayPattern->getOutData()}) {
			## Get the out data - this is the input
			## entities data and should be specific to the
			## creation of a DNA tube.
			my $dbInFormat        = $outData->[0];
			my $dbInMapId         = $outData->[1];
			my $dbContentId       = $outData->[2];
			my $dbClonename       = $outData->[3];
			my $dbIdInd           = $outData->[4];
			my $dbContentDict     = $outData->[5];
			my $dbCategory        = $outData->[6];
			my $dbCreatedFrom     = $outData->[7];
			my $dbCreatedFromType = $outData->[8];
			
			## Create the new DNA tube.
			my $idDnaTube = 
			    eval  { $self->createDnaTube(idInd       => $dbIdInd,
							 team        => $team,
							 category    => $dbCategory,
							 createdFrom => $dbContentId,
							 status      => DNATUBE_STATUS_CREATED,
							 roleId      => $roleId,
							 sessionId   => $sessionId,
							 program     => $programName,) };
			if($@){ croak "ERROR: can't create new dna tube: $@\n"; }

			eval {
			    ## Populate the exo_process table with parentage data.
			    $self->populateExoProcess(-sqlEngine    => $object{ $self }->{sqlEngine}, 
						      -dbh          => $object{ $self }->{dbh},
						      -processId    => $processId,
						      -inputEntity  => $inDataId,
						      -inputPrefix  => $inPrefix, 
						      -outputId     => $idDnaTube, 
						      -outputPrefix => 'DT');
			};

			if($@){ croak "ERROR: failed to populate the exo_process table with parentage data: $@"; }

			## Store summary data.
			push( @summary, [$sessionId,$idDnaTube,$inDataId,$dbInFormat,
					 $inFormat,"$inRow$inCol","$outRow$outCol",$outMapId,
					 $dbIdInd,$dbContentDict,$dbCategory,$dbContentId,$dbCreatedFromType] );

			## Create a new BarcodeFactory object.
			my $barcodeFactory = new APE::BarcodeFactory::DT_BarcodeFactory
			    (flushController => $self,
			     processId       => $parentProcessId,
			     entityId        => $idDnaTube,
			     reservedData    => [$dbClonename]);
			
			## Create a new ConcreteBarcode instance.
			my $barcodeObj = $barcodeFactory->getInstance;  
			my $barcode    = $barcodeObj->createBarcode;

			## Store the new DNA tube in a Result object.
			push(@results, new APE::FlushController::Result
			     (sessionId       => $sessionId,
			      id              => $idDnaTube,
			      type            => 'DT',
			      description     => $barcode,
			      'format'        => $outFormat,
			      report          => $self->createReport(\@summary),
			      flushController => \$self));

			@summary = undef;
		    }
		}
	    }
	}
	

	## Experiment: I'm unsure how APE will deal with a 96 well DNA
	## plate going to 96 well DNA tubes. APE may consider each
	## well transfer as a separate ArrayingEntity containing one
	## AtomicProcess that contains just one ArrayPattern object,
	## but if APE doesn't do this then we could end up breaking
	## the library by ignoring the rule that a single
	## FlushController object flushes a single object - if that
	## happens all hell will break loose. This code allows multiple
	## DNA tubes to be created but will barf in the code below
	## because of it. This will allow me to see if this is ever
	## going to happen - hopefully it won't.
	if(scalar(@results) != 1) {
	    croak "ERROR(DT_FlushController): FlushController rule broken - single " . 
		"FlushController object has created more than one entity - please " .
		"issue a HelpDesk ticket and refrain from using this process until " . 
		"authorised.";
	}

	return $results[0];
    }


    #################################################################
    ## Method to output a sorted summary of the insertion.
    #################################################################
    sub createReport {
	my ($self,$summary) = @_;
	my @report=();
	push(@report,sprintf("%-13s %-13s %-10s %-12s %-12s %-8s %-9s %-9s %-11s %-13s %-12s %-14s  %-13s\n",
			     "SessionId","NewDnaTubeId","InDataId","DbInFormat","InFormat","InCoord",
			     "OutCoord","OutMapId","DbIdInd","DbContentDict","DbCategory",
			     "DbCreatedFrom","DbCreatedFromType"));
	push(@report,sprintf("%-13s %-13s %-10s %-12s %-12s %-8s %-9s %-9s %-11s %-13s %-12s %-14s  %-13s\n",
			     "-"x13,"-"x13,"-"x10,"-"x12,"-"x12,"-"x8,"-"x9,"-"x9,"-"x11,"-"x13,"-"x12,"-"x13,"-"x13));
	foreach my $line (sort{ $a->[7] <=> $b->[7]  } @{$summary}) {
	    push(@report,sprintf("%-13s %-13s %-10s %-12s %-12s %-8s %-9s %-9s %-11s %-13s %-12s %-14s  %-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->[11] : "null",
				 $line->[12] ? $line->[12] : "null"));
	}
	return \@report;
    }


    #################################################################
    ## Method to create a new DNA tube and assigned it a status.
    #################################################################
    sub createDnaTube {
	my ($self,%options) = @_;
	croak "ERROR: argument idInd is missing."        unless $options{idInd};
	croak "ERROR: argument team is missing."         unless $options{team};
	croak "ERROR: argument category is missing."     unless $options{category};
	croak "ERROR: argument status is missing."       unless $options{status};
	croak "ERROR: argument roleId is missing."       unless $options{roleId};
	croak "ERROR: argument sessionId is missing."    unless $options{sessionId};
	croak "ERROR: argument program is missing."      unless $options{program};
	## Get the next id_dnatube.
	my $idDnaTube = eval { $self->getNextDnaTubeId(); };
	if($@) { croak "ERROR: can't get new id_dnatube: $@\n"; }
	## Create the new DNA tube.
	eval  { $self->newDnaTube($idDnaTube,$options{idInd},$options{team},
				  $options{category},$options{createdFrom}); };
	if($@){ croak "ERROR: can't create new dnatube: $@\n";  }
	## Create a new DNA tube status.
	eval  { $self->newDnaTubeStatus($idDnaTube,$options{status},$options{roleId},
					$options{sessionId},$options{program}); };
	if($@){ croak "ERROR: can't create new dnatube status: $@\n";  }
	warn "Created DNA tube: $idDnaTube\n";
	return $idDnaTube;
    }
    

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


    #################################################################
    ## Method to create a new DNA tube. 
    #################################################################
    sub newDnaTube {
    	my ($self,$idDnaTube,$idInd,$team,$category,$createdFrom) = @_;
    	eval  { $object{ $self }->{sqlEngine}->do('ExoSeq::make_dnatube',
    						  [$idDnaTube,$idInd,$team,$category,
						   $createdFrom ? $createdFrom : " "], 
    						  $object{ $self }->{dbh}); };
    	if($@) { croak $@; }
    }
    

    #################################################################
    ## Method to create a new DNA tube status.
    #################################################################
    sub newDnaTubeStatus {
    	my ($self,$idDnaTube,$status,$idRole,$sessionId,$program) = @_;
    	eval { $object{ $self }->{sqlEngine}->do('ExoSeq::make_dnatubestatus',
    						 [$idDnaTube,$status,1,$idRole,$sessionId,$program],
    						 $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} );
    	#$self->rollbackTransaction();
    }
    

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

1;
