package APE::FlushControllerFactory::ConcreteFlushController::EP_FlushController;

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

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

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

use APE::InputControllerFactory::ConcreteInputController::DefaultExoApeInputController;
use APE::FlushControllerFactory::ConcreteFlushController::Result;
use APE::BarcodeFactory::EP_BarcodeFactory;

=head1 NAME

APE::FlushControllerFactory::ConcreteFlushController::EP_FlushController
- a flush controller to create a new EP primer plates in the SNP database.

=head1 SYNOPSIS

This is a concrete controller that should be used within a controller
factory, see the relevant controller factory (input, flush or output)
for more information.

=head1 DESCRIPTION

A concrete controller is created by a controller factory.

=head1 CONSTANTS

=over 5

=item TRUE

Boolean true.

=cut

use constant TRUE => 1;


=item FALSE

Boolean false.

=cut

use constant FALSE => 0;


=item LOGIN

Login string used to connect to the database.

=cut

use constant LOGIN => 'snp';


=item OLIGO_PLATE_STATUS

The created status of an oligo primer plate. This is the status that
the new primer plate will be set to during the flush.

=cut

use constant OLIGO_PLATE_STATUS => 4; ## Created


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


{

=back

=head1 CONSTRUCTOR

The following arguments can be passed to the constructor.

=over 5

=item arrayingEntity

Required. An APE::ArrayingEntity object that contains the arrayed
object structure for flushing. Required by the FlushController base
class.

=item programName

Required. The name of the program that is completing this
flush. Required by the DefaultExoApeFlushController base class.

=item userObj

Required. A reference to a User object that is populated with the
details of the user that is completing this flush.

=item login

Optional. The login string to use instead of that provided by the
LOGIN constant.

=back

=cut

    my %_attributes = (dbh => undef);

    my %object=();


=head2 INSTANTIATION PROCESS

This class inherits from the DefaultExoApeFlushController class.

The new method calls the _init() method on $self. If $self is a
derived class then it's _init() method will be called giving it the
chance to remove any arguments it requires from the %args hash. If
there is no _init() method in the derived class then the _init()
method in this class is called.

Derived classes implementing a _init() method should return the %args
hash to the base class:

package Derived::Class;

sub _init {
   my ($self,%args) = @_;
   my $argument = delete $args{classArgument};
   return $self->SUPER::_init(%args);
}

=cut

=head1 METHODS

=over 10

=item $controller->_init(%args);

Private method. This method is called by the new method during object
instantiation (see the INSTANTIATION process). The method removes any
of the arguments required by the object from the %args hash and
returns it.

See the INSTANTIATION section for information on how to use this
method in a derived class.

=cut

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


=item $controller->getArrayingEntity();

Returns the APE::ArrayingEntity object that is to be flushed.

=item $controller->getProgramName();

Returns the string of the program name that is making this flush.

=item $controller->getUserObj();

Returns the reference to this objects User object.

=cut

    foreach my $attName (keys(%_attributes)) {
	no strict 'refs';
	*{__PACKAGE__ . "::get\u$attName"} = sub { $object{ $_[0] }->{$attName} }
    }


=item $controller->beginTransaction();

Implemented method from the FlushController base class. Creates a new
SqlEngine2 object. Calls its statis getDbh() method (see
SqlEngine2::getDbh()) with the LOGIN constant to receive a new
database handle for the flush. Begins a new transaction by calling
beginTransaction($dbh) on the SqlEngine2 object.

=cut

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


=item $controller->flush();

Implemented method from the FlushController base class. Flushes this
objects APE::ArrayingEntity object to the database using the handle
created in the beginTransaction() method.

=cut

    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 $columnNumber;
	my $idOligoPlate;
	my $plateType;
	my $plateTypeDesc;
	my $OLIGO_PLATE_CREATED = FALSE;
	my %horde=();
	my $oligoNameList = "";
	my $inEpName;

	foreach my $atomicProcess (@{$entity->getAtomicProcessList()}) {
	    my $inDataId = $atomicProcess->getInDataId();
	    my $aph      = $atomicProcess->getArrayPatternHash();
	    $inEpName    = $atomicProcess->getInDataId();
	    ($plateType,$plateTypeDesc) = 
		APE::InputControllerFactory::ConcreteInputController::DefaultExoApeInputController::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) {
		warn "Creating the one and only oligo plate now!\n";
		$idOligoPlate = 
		    eval { $self->createOligoPlate(sessionId=> $sessionId,
						   plateType=> $plateType->[0],
						   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') {
			    if(!$columnNumber) {
				$columnNumber = $arrayPattern->getInColumn();
			    }
			    warn "Storing oligo name: $oligoName\n";
			    $oligoNameList = $oligoNameList . $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}); };
			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->[0],$idOo,
					 "@$outData"] );
		    }
		}
	    }

	    ## Populate the exo_process table with parentage data.
	    $self->populateExoProcess(-sqlEngine    => $object{ $self }->{sqlEngine},
				      -dbh          => $object{ $self }->{dbh},
				      -processId    => $atomicProcess->getProcessId,
				      -inputEntity  => $inDataId,
				      -inputPrefix  => $atomicProcess->getInPrefix,
				      -outputId     => $idOligoPlate,
				      -outputPrefix => 'EP');

	}

	$oligoNameList =~ s/\s$//;

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

	my $barcodeObj = $barcodeFactory->getInstance;  ## Returns a ConcreteBarcode object.
	my $barcode    = $barcodeObj->createBarcode;    ## Object creates barcode formatting.

	return new APE::FlushControllerFactory::ConcreteFlushController::Result
	    (sessionId       => $sessionId,
	     id              => $idOligoPlate,
	     type            => 'EP',
	     description     => $barcode,
	     'format'        => $outFormat,
	     report          => $self->createReport(\@summary),
	     flushController => \$self);
    }


=item $controller->createReport($summary);

Creates a formatted report based on the summary data passed to
it. This method is called at the end of the flush() method. The
summary data is a list of arrays containing the following information:

 0  - session id
 1  - oligo plate id
 2  - oligo well id
 3  - plate
 4  - database in format
 5  - in format
 6  - in coord
 7  - out coord
 8  - out map id
 9  - plate type
 10 - oligo order id
 11 - other data used in the flush

Returns an array ref containing formatted strings. The first two
strings are the headers and the underlines.

=cut

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


=item $controller->createOligoPlate(option => value,...?);

Creates a new oligo primer plate in the database. This method calls
the getNextOligoPlateId() method to retrieve a new oligo plate id, it
then calls the newOligoPlate() method. It finally adds a new status to
the new plate. Options are:

=over 5

=item plateType

Required. The plate type to create (sense, antisense or mix).

=item format

Required. The format of the new plate.

=item team

Required. The team this plate belongs to.

=item roleId

Required. The role id number of the user creating this new plate.

=item program

Required. The string program name creating this new plate.

=item sessionId

Required. The session id number used to group other flushes in this
session.

=item status

Required. The status number that the new plate is given. See
OLIGO_PLATE_STATUS constant in the CONSTANTS section.

=back

Returns the id (id_oligoplate) of the new oligo plate.

=cut

    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";  }
	warn "Created oligo plate: $idOligoPlate\n";
	return $idOligoPlate;
    }


=item $controller->getNextOligoPlateId();

Returns the nexy oligo plate id (id_oligoplate) from the database
sequence.

=cut

    sub getNextOligoPlateId {
    	my $self = shift;
    	my $idOligoPlate = 
    	    eval { $object{ $self }->{sqlEngine}
    		   ->getRow('ExoSeq::next_oligoplate',
    			    [],$object{ $self }->{dbh});};
    	if($@) { croak $@;           }
    	else   { return $idOligoPlate; }
    }


=item $controller->getNextOligoWell();

Returns the next oligo well id (id_well) number from the database
sequence.

=cut

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


=item $controller->newOligoPlate($idOligoPlate,$plateTyep,$format,$team);

Creates a new oligo plate in the oligoplate database table. Throws an
error if fails.

=cut

    sub newOligoPlate {
    	my ($self,$idOligoPlate,$plateType,$format,$team,$idOrder) = @_;
    	eval  { $object{ $self }->{sqlEngine}->do
		  ('ExoSeq::insert_oligoplate',
		   [$idOligoPlate,$plateType,($idOrder ? $idOrder : ' '),$format,$team],
		   $object{ $self }->{dbh}); };
    	if($@) { croak $@; }
    }


=item $controller->newOligoPlateStatus($idOligoPlate,$status,$idRole,$sessionId,$program);

Creates a new status of $status and associates it to the new oligo
plate $idOligoPlate.

=cut

    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 $@; }
    }


=item $controller->newOrderWell($idOo,$idWell);

Creates a new entry in the order_well database table. This table links
the well on an oligo plate to an STS.

=cut

    sub newOrderWell {
    	my ($self,$idOo,$idWell) = @_;
    	$object{ $self }->{sqlEngine}->do
	    ('ExoSeq::insert_orderwell',
	     [$idOo,$idWell],$object{ $self }->{dbh});
    }


=item $controller->newOligoPlateWell($idWell,$idMap,$idOligoPlate);

Creates a new oligo plate well entry in the oligoplate_well database
table. Throws an exception on failure.

=cut

    sub newOligoPlateWell {
    	my ($self,$idWell,$idMap,$idOligoPlate) = @_;
    	eval { $object{ $self }->{sqlEngine}->do
		 ('ExoSeq::insert_oligoplatewell',
		  [$idWell,$idMap,$idOligoPlate],
		  $object{ $self }->{dbh}); };
    	if($@) { croak $@; }
    }


=item $controller->rollbackTransaction();

Implemented method from the FlushController base class. Calls this
objects SqlEngine2::rollbackTransaction($dbh) method that should
rollback the current transaction on the databas handle created in this
objects beginTransaction() method.

Outputs a wanrning message.

=cut

    sub rollbackTransaction {
    	my $self = shift;
	warn "Rolling back: $object{ $self }->{sqlEngine} -> $object{ $self }->{dbh}\n";
    	$object{ $self }->{sqlEngine}->rollbackTransaction($object{ $self }->{dbh});
    }


=item $controller->commitTransaction();

Implemented method from the FlushController base class. Calls this
objects SqlEngine2::commitTransaction($dbh) method that should commit
the current transaction on the database handle created in this objects
beginTransaction() method.

=cut

    sub commitTransaction {
    	my $self = shift;
    	$object{ $self }->{sqlEngine}
	  ->commitTransaction
	    ($object{ $self }->{dbh});
    }


=item DESTROY

Class destructor. If you derive from this class please call this
destructor from your destructor.

 package Derived::Class;

 sub new {}

 sub DESTROY {
     my $self = shift;
     $self->SUPER::DESTROY();
 }

=cut

    sub DESTROY {
	my $self = shift;
	delete $object{$self};
	$self->SUPER::DESTROY;
    }
}


=back

=head1 ALSO SEE

APE::FlushControllerFactory::ConcreteController::FlushController,
APE::FlushControllerFactory::ConcreteController::DefaultExoApeFlushController,
APE::FlushControllerFactory::ExoApeFlushControllerFactory,
APE::FlushControllerFactory

=head1 AUTHOR

Simon Holroyd <holroyd@sanger.ac.uk>

=cut

1;
