$APE::Tk::MultiProcess::VERSION = '1.0';

package APE::Tk::MultiProcess;

use Tk::widgets qw/Frame Tree/;
use base qw/Tk::Derived Tk::Frame/;
use strict;
use warnings;
use Carp;
use SITk::BarcodeEntry;
use SITk::SLabel;
use SITk::TabbedView;
use SqlEngine2;

Construct Tk::Widget 'MultiProcess';

use constant TRUE  => 1;
use constant FALSE => 0;
use constant PROCESS_LOGIN => 'gt_process';
use constant SNP_LOGIN     => 'snp';
use constant VALID_PROCESS => { 5643 => "384x1",
				5740 => "48x8",
				6228 => "4x96" };

## Standard Tk mega widget method for creating this mega widget and 
## initialising the super class widget.
sub ClassInit {
    my ($class,$mw) = @_;
    $class->SUPER::ClassInit($mw);
}

## Standard Tk mega widget method for defining mega widget
## characteristics and capturing arguments unknown to Tk.
sub Populate {
    my ($self,$args) = @_;
    $self->{_sqlEngine2}   = new SqlEngine2;
    $args->{-errorcommand} = sub{ croak $_[0]; } unless $args->{-errorcommand};
    #$self->{_lscrollbars}  = delete $args->{-lscrollbars};
    $self->{_findex} = 1;
    $self->SUPER::Populate($args);
    $self->ConfigSpecs(-errorcommand=>[qw/CALLBACK errorcommand ErrorCommand/]);
    $self->_createFrames();
    $self->_createBarcodeEntry();
    $self->_createListbox();
}


## Method to create all of the major frames that divide this widget
## into its major components.
sub _createFrames {
    my $self = shift;
    $self->{topFrame}    = $self->Frame(-relief=>'ridge',-borderwidth =>1)->pack(-fill=>'x');
    $self->{middleFrame} = $self->Frame(-relief=>'ridge',-borderwidth =>1)->pack(-fill=>'both',-expand=>1); 
    $self->{bottomFrame} = $self->Frame(-relief=>'ridge',-borderwidth =>1)->pack(-fill=>'x');

}


## Method to create the widgets that allow the user to scan/type their
## barcode in to this mega widget. We use an SITk::SLabel so that
## state changes made to this widget can be seen in the label. We use
## SITk::BarcodeEntry as it validates a barcode automatically.
sub _createBarcodeEntry {
    my $self = shift;
    $self->{topFrame}->SLabel(-text => "Enter Barcode")->pack(-side=>'left',-pady=>10,-padx=>20);
    $self->{barcode} = $self->{topFrame}->BarcodeEntry(-validcommand=>[\&_input,$self]);
    $self->{barcode}->pack(-side=>'left');
    $self->{barcode}->focus();
}


## Method that is called by the SITk::BarcodeEntry widget after the
## scanned entity has been validated (so the barcode is valid).
sub _input {
    my ($self,$validate) = @_;
    
    ## Check if this entity is of the correct type (oligo plate).
    if ($validate->{Type} ne "EP"){
	$self->Callback(-errorcommand,"Barcode is the incorrect type");
	$self->{barcode}->delete(0,'end');
	return;
    }

    my $id        = $validate->{Number};
    my $entity    = $validate->{Whole};

    ## Check if this entity has already been scanned in.
    if(exists($self->{_ds}{$entity})) {
	$self->Callback(-errorcommand,"Entity '$entity' has ".
			"already been scanned");
	$self->{barcode}->delete(0,'end');
	return;
    }

    ## Use cached data if this entity has previously been scanned and
    ## removed - tell the user this is happening?
    my $tractProcessData;
    if(exists($self->{_cds}{$entity})) {
	$tractProcessData = $self->{_cds}{$entity};
    }
    
    else{
	## Check that the entity is at status created which is the only
	## valid status.
	unless($self->_isAtValidStatus($id)) {
	    $self->Callback(-errorcommand,"Entity '$entity' is not at status created");
	    $self->{barcode}->delete(0,'end');
	    return;
	}
	
	## Check that the entity was created using a valid APE process.
	my $processId = $self->_getEntityProcess($id);
	unless($tractProcessData = $self->_isFromValidProcess($processId)) {
	    $self->Callback(-errorcommand,"Entity '$entity' was not created ".
			    "using a valid APE process");
	    $self->{barcode}->delete(0,'end');
	    return;
	}
	
	## Find the plate type. This should be either 1 or 2 (sense or
	## antisense).
	my $plateType;
	unless($plateType = $self->_getPlateType($id)){
	    $self->Callback(-errorcommand,"Failed to find plate type for entity '$entity'");
	    $self->{barcode}->delete(0,'end');
	    return;
	}
    }
   
    $self->insertEntity($entity,$tractProcessData);
    #$self->{_ds}{$entity} = $tractProcessData; 
    #$self->{listbox}->insert('end', $entity);
    $self->{barcode}->delete(0,'end');
}


## Method to create a listbox. Scanned entities that have been
## validated are inserted into this listbox. So the contents of this
## listbox are ready to be APE'd.
sub _createListbox {
    my $self = shift;
    $self->{_tv} = $self->{middleFrame}->TabbedView
	(-titles => [qw/Entity Tract ProcessId/],
	 -rows   => 10,
	 -cols   => 3)
	->pack(-fill   => 'both',
	       -expand => 1);
}


sub insertEntity {
    my ($self,$entity,$tractData) = @_;

    my $processId   = $tractData->[0];
    my $processType = $tractData->[1];
    my $processDesc = $tractData->[2];

    if($self->{_tv}->exists(1,$entity)) {
	$self->Callback(-errorcommand,"entity '$entity' has already been scanned in");
	return;
    }

    $self->{_tv}->addData([$entity,$processDesc,$processId]);

    #my $index = $self->{_findex};
    #
    #$self->{_entityFrames}{$index} = $self->{_workFrame}->Frame
    #	(-relief=>'ridge',-borderwidth=>1)
    #	->pack(-pady=>2,-padx=>2);
    #
    #$self->{_entityFrames}{$index}->Checkbutton
    #	(-variable=>\$self->{_entityChecks}{$index})
    #	->pack(-side=>'left');
    #
    #foreach my $data ($entity,$processId,$processType,$processDesc) {
    #	my $tEntry = $self->{_entityFrames}{$index}->Entry
    #	    (-state=>'normal',-width=>30)
    #	    ->pack(-side=>'left');
    #	$tEntry->insert('end',$data);
    #	$tEntry->configure(-state=>'disabled');
    #}
    #$self->{_findex}++;
}

## Method to get the process id of the passed entity id. This method
## executes a lookup on the exo_process table using the passed id
## number as the id_output number. This returns the process id of the
## process that was used to create the entity.
sub _getEntityProcess {
    my ($self,$id) = @_;
    my $processId  = $self->{_sqlEngine2}->getRow
	("MultiProcess::GetEpProcess",
	 [$id],SqlEngine2::getDbh(SNP_LOGIN));
    return $processId;
}


## Method to return boolean false if the passed process id numbers
## parent process id is not valid i.e. the scanned entity is not the
## output of a valid process. Otherwise returns the processes tract
## process id and description in an anonymous array.
sub _isFromValidProcess {
    my ($self,$processId) = @_;
    my $processData = $self->{_sqlEngine2}->getAll
	("MultiProcess::GetProcessParentage",
	 [$processId],SqlEngine2::getDbh(PROCESS_LOGIN));
    ## Nothing found - can't be valid.
    return FALSE if !scalar @$processData;
    my $parentProcessId   = $processData->[1][0];
    my $parentProcessDesc = $processData->[1][1];
    if(exists(VALID_PROCESS->{$parentProcessId})) {
	## Return the 2nd to last element that is the tract
	## information.
	return $processData->[scalar(@$processData)-2];
    }
    return FALSE;
}


## Method to find and return the type of the scanned/typed plate. The
## type is found in the oligoplate.plate_type column. Valid plate
## types are 1 and 2 (sense and antisense).
sub _getPlateType {
    my ($self,$id) = @_;
    return $self->{_sqlEngine2}->getRow
	("MultiProcess::GetPlateType",
	 [$id],SqlEngine2::getDbh(SNP_LOGIN));
}


## Method to return boolean true if the passed oligo plate id is at
## the correct status. The correct status is 4 which is
## 'Created'. Otherwise returns false.
sub _isAtValidStatus {
    my ($self,$id) = @_;
    return $self->{_sqlEngine2}->getRow
	("MultiProcess::IsAtValidStatus",
	 [$id],SqlEngine2::getDbh(SNP_LOGIN));
}


## Method to remove all of the selected entries.
sub removeSelected {
    my $self = shift;
    my @selected = $self->{listbox}->curselection;
    foreach my $index (reverse @selected) {
	my $entity = $self->{listbox}->get($index);
	my $val = $self->{_ds}{$entity};
	$self->{_cds}{$entity} = $val;
	delete $self->{_ds}{$entity};
	$self->{listbox}->delete($index);
    }
}



__END__

## Valid EP plates (type 1).

EP185229
EP185253
EP185277
EP185301
EP185363
EP185387
EP185462
EP185486
EP185520
EP185544

## Valid EP plates (type 2).

EP185230
EP185254
EP185278
EP185302
EP185364
EP185388
EP185463
EP185487
EP185521
EP185545

