package GTx::APE::FlushController::EP_FlushController;

BEGIN {
    #$ENV{WRAPDBI_TEST_CONFIG} = "/nfs/team71/usg/holroyd/badger-cvs/GTx/APE/DBlogs";
    #$ENV{DEBUG_WRAPDBI} = 1;
};

use base 'GTx::APE::FlushController';

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

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

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

use constant LOGIN              => 'snp';
use constant OLIGO_PLATE_STATUS => 4; ## Created

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


{
    my %_attributes = ( 
			## ** Add your object attributes here **
			## They will automatically be given a get method.
			## e.g. attribute 'personForename' can be accessed
			## using getPersonForname(). There are no automatic 
			## set routines, they must be declared or the attribute 
			## value must be parsed in the constructor 
			## i.e new FlushController(personForename=>'Simon');
			
			## You get a reference to this objects ArrayingEntity 
			## object free of charge when this class is created by 
			## by the same ArrayingEntity object! The arrayingEntity 
			## attribute is in the base class and should be accessed 
			## like this: my $entity = ${$self->getArrayingEntity()};

			## You get a reference to a Login::User object that contains
			## a working Login::User::Team and working 
			## Login::User::Team::Role. Access is the same as above.
			## my $userObj = ${$self->getUserObj()};
		       );

    my %object;
    
    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 @summary;
	my $idOligoPlate;
	my $plateType;
	my $plateTypeDesc;
	my $OLIGO_PLATE_CREATED = FALSE;
	my %horde=();
	my $description = "";

	foreach my $atomicProcess (@{$entity->getAtomicProcessList()}) {
	    my $inDataId = $atomicProcess->getInDataId();
	    my $aph      = $atomicProcess->getArrayPatternHash();
	    ($plateType,$plateTypeDesc) = 
		GTx::APE::InputController::EPSA_InputController::getType($atomicProcess->getOutEntityTypeId());
	    croak "ERROR: can't get out plate type and description.\n" unless $plateType && $plateTypeDesc;
	    ## Create the one and only oligo plate. It's done
	    ## here because this is the first time we now what
	    ## the plate type is (sense, antisense or mixer).
	    if(!$OLIGO_PLATE_CREATED) {
		print STDERR "Creating the one and only oligo plate now!\n";
		$idOligoPlate = 
		    eval { $self->createOligoPlate(sessionId=> $sessionId,
						   plateType=> $plateType,
						   status   => OLIGO_PLATE_STATUS,
						   'format' => $outFormat,
						   team     => $team,
						   roleId   => $roleId,
						   program  => $programName); };
		if($@){ croak "ERROR: can't create new oligo plate: $@\n"; }
		$OLIGO_PLATE_CREATED = TRUE;
	    }
	    ## Go through the ArrayPattern objects making inserts.
	    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();
		    ## Go through each block of in data that
		    ## represents a piece of well data.
		    foreach my $outData (@{$arrayPattern->getOutData()}) {
			my $dbInFormat    = $outData->[0];
			my $dbInMapId     = $outData->[1];
			my $idOo          = $outData->[2];
			my $oligoName     = $outData->[5];
			
			## Find the oligo being put into the first well.
			if($outRow . $outCol eq 'a1') {
			    print STDERR "Storing oligo name: $oligoName\n";
			    $description = $description . $oligoName . " ";
			}

			## Only create a single oligoplate_well entry.
			unless(exists($horde{$outMapId})) {
			    ## Find the next oligoplate_well
			    my $idOligoWell = eval { $self->getNextOligoWell(); };
			    if($@) { croak "ERROR: can't get new id_oligowell: $@\n"; }
			    ## Create a new oligoplate_well entry.
			    eval  { $self->newOligoPlateWell($idOligoWell,$outMapId,$idOligoPlate); };
			    if($@){ croak "ERROR: can't create oligo_well entry: $@\n"; }
			    ## Store the id_well.
			    $horde{$outMapId} = $idOligoWell;
			}

			## Create a new order_well entry.
			eval  { $self->newOrderWell($idOo,$horde{$outMapId}); }; #$idOligoWell
			if($@){ croak "ERROR: can't enter a new order_well: $@\n"; }
			
			push( @summary, [$sessionId,$idOligoPlate,$horde{$outMapId},
					 $inDataId,$dbInFormat,$inFormat,"$inRow$inCol",
					 "$outRow$outCol",$outMapId,$plateType,$idOo,
					 "@$outData"] );
		    }
		}
	    }
	}

	$description =~ s/\s$//;

	my $processFormat;
	if($parentProcessId =~ /^[5740|5741|5762]/) {
	    $processFormat = "48 DNA x 8 STS";
	}elsif($parentProcessId =~ /^[5643|5644|5678]/) {
	    $processFormat = "384 DNA x 1 STS";
	}else{
	    $processFormat = "Unknown";
	}

	return new GTx::APE::FlushController::Result(sessionId       => $sessionId,
						     id              => $idOligoPlate,
						     type            => 'EP',
						     description     => [$description,$processFormat],
						     'format'        => $outFormat,
						     report          => $self->createReport(\@summary),
						     flushController => \$self);

    }


    #################################################################
    ## Method to report on the flush that has just happened BEFORE
    ## it's commited.
    #################################################################
     sub createReport {
	my ($self,$summary) = @_;
	my @report=();
	push(@report,sprintf("%-11s %-13s %-13s %-10s %-12s %-12s %-8s %-9s %-9s %-9s %-9s %-40s\n",
			     "SessionId","OligoPlatId","OligoWellId","Plate","DbInFormat","InFormat","InCoord",
			     "OutCoord","OutMapId","PlateType","OligoOrderId","OtherData"));
	push(@report,sprintf("%-11s %-13s %-13s %-10s %-12s %-12s %-8s %-9s %-9s %-9s %-9s %-40s\n",
			     "-"x11,"-"x13,"-"x13,"-"x10,"-"x12,"-"x12,"-"x8,"-"x9,"-"x9,"-"x9,"-"x9,"-"x40));
	foreach my $line (sort{ $a->[8] <=> $b->[8]  } @{$summary}) {
	    push(@report,sprintf("%-11s %-13s %-13s %-10s %-12s %-12s %-8s %-9s %-9s %-9s %-9s %-25s\n",
				 $line->[0],$line->[1],$line->[2],$line->[3],$line->[4],
				 $line->[5],$line->[6],$line->[7],$line->[8],$line->[9],
				 $line->[10],$line->[10]));
	}
	return \@report;
    }


    #################################################################
    ## Method to create a new DNA plate with a status.
    #################################################################
    sub createOligoPlate {
	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};
	## Get the next id_oligoplate.
	my $idOligoPlate = eval { $self->getNextOligoPlateId(); };
	if($@) { croak "ERROR: can't get new id_oligoplate: $@\n"; }
	## Create the new oligo plate.
	eval  { $self->newOligoPlate($idOligoPlate,$options{plateType},$options{format},$options{team}); };
	if($@){ croak "ERROR: can't create new oligoplate: $@\n";  }
	## Create a new oligo plate status.
	eval  { $self->newOligoPlateStatus($idOligoPlate,$options{status},$options{roleId},
					   $options{sessionId},$options{program}); };
	if($@){ croak "ERROR: can't create new oligoplate status: $@\n";  }
	print STDERR "Created oligo plate: $idOligoPlate\n";
	return $idOligoPlate;
    }


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

    #################################################################
    ## Method to get the next id_oligowell from the sequence.
    #################################################################
    sub getNextOligoWell {
    	my $self = shift;
	my $idOligoWell;
	my $isUsed = FALSE;
	#unless(($idOligoWell,$isUsed) = $self->isWellUsed($outMapId,$idOligoPlate)) {
	    $idOligoWell = 
		eval { $object{ $self }->{sqlEngine}->getRow('ExoSeq::next_oligowell',
							     [],$object{ $self }->{dbh}); };
	    if($@) { croak "ERROR: can't get new id_well: $@\n"; }
	#}
	return $idOligoWell;#,$isUsed;
    }
    
    
    #sub isWellUsed {
    #	my ($self,$outMapId,$idOligoPlate) = @_;
    #	my $usedIdOligoWell =
    #	    eval { $object{ $self }->{sqlEngine}->getRow('ExoSeq::is_well_used',
    #							 [$idOligoPlate,$outMapId],
    #							 $object{ $self }->{dbh}); };
    #	if($@) { croak "ERROR: can't find if well is used: $@\n"; }
    #	return $usedIdOligoWell,$usedIdOligoWell ? TRUE : FALSE;
    #}


    #################################################################
    ## Method to create a new Oligo plate.
    #################################################################
    sub newOligoPlate {
    	my ($self,$idOligoPlate,$plateType,$format,$team) = @_;
    	eval  { $object{ $self }->{sqlEngine}->do('ExoSeq::insert_oligoplate',
    						  [$idOligoPlate,$plateType,' ',$format,$team], 
    						  $object{ $self }->{dbh}); };
    	if($@) { croak $@; }
    }


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


    #################################################################
    ## Method to create an order_well entry that links an oligo to a
    ## well in an oligo plate.
    #################################################################
    sub newOrderWell {
    	my ($self,$idOo,$idWell) = @_;
    	eval { $object{ $self }->{sqlEngine}->do('ExoSeq::insert_orderwell',
    						 [$idOo,$idWell],
    						 $object{ $self }->{dbh}); };
    	if($@) { croak $@; }
    }


    #################################################################
    ## Method to create a Oligo well in a Oligo plate.
    #################################################################
    sub newOligoPlateWell {
    	my ($self,$idWell,$idMap,$idOligoPlate) = @_;
    	eval { $object{ $self }->{sqlEngine}->do('ExoSeq::insert_oligoplatewell',
    						 [$idWell,$idMap,$idOligoPlate],
    						 $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;
