$APE::Tk::ProcessRoleManager::::ProcessRoleManagerVERSION = '0.1';

package APE::Tk::ProcessRoleManager::ProcessRoleManager;

use Tk::widgets qw/Frame BrowseEntry DialogBox/;
use base qw/Tk::Derived Tk::Frame/;
use strict;
use warnings;
use SqlEngine2;

Construct Tk::Widget 'ProcessRoleManager';

use constant TRUE  => 1;
use constant FALSE => 0;
use constant LOGIN => 'gt_process';

use APE::AtomicProcessEngine;
use APE::InputControllerFactory::ExoApeInputControllerFactory;
use APE::FlushControllerFactory::ExoApeFlushControllerFactory;
use APE::OutputControllerFactory::ExoApeOutputControllerFactory;
use APE::Tk::ProcessTree;
#use APE::Tk::ProcessRoleSelector;
use User;

use vars qw/$iRole/;

$iRole = 0;

my @roles = (qw/None ExoSeqAdmin/);

## 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->SUPER::Populate($args);
    $self->{_topLevelProcessIdList} = delete $args->{-topLevelProcessIdList};
    $self->{_roleType}              = delete $args->{-roleType};
    die "Error: argument -topLevelProcessIdList should be an array ref."
	if $self->{_topLevelProcessIdList} && ref $self->{_topLevelProcessIdList} ne 'ARRAY';
    $self->{_user}       = new User(noPopulate => TRUE);
    $self->{_sqlEngine2} = new SqlEngine2;
    $self->createRoleFrame;
    $self->createTopFrame;
    $self->insertRoles($self->findAvailableApeRoles);
    $self->autoLoad($self->{_topLevelProcessIdList}) 
	if $self->{_topLevelProcessIdList} && scalar $self->{_topLevelProcessIdList};
}


## Method to auto load a list of process id numbers. If a role type
## has been set then the auto loaded processes will be subjected to
## role type screening before being displayed. This method also turns
## on the check button that concatenates APE objects into the process
## tree instead of clearing it for each process id number in the
## process list.
sub autoLoad {
    my ($self,$processList) = @_;
    $self->{_cb}->select;
    $self->selectRoleType($self->{_roleType}) if $self->{_roleType};
    foreach my $processId (@$processList) {
	$self->{_p} = $processId;
	refreshProcessTree($self->{_processBeEntry},$self);
    }
}


## Method to create the role frame that contains the controls to alter
## the view within the ProcessTree widget. These controls send
## commands to the ProcessTree widget on how the APE objects should be
## constructed.
sub createRoleFrame {
    my $self = shift;
    my $refreshFrame = $self->Frame(-relief=>'ridge',-borderwidth=>1)->pack(-fill=>'x');
    $refreshFrame->Label(-text=>"Alter Process Tree View",-bg=>"white",-fg=>"black")->pack(-fill=>'x');
    my $f = $refreshFrame->Frame->pack(-side=>'left',-ipadx=>3,-ipady=>3);
    $f->Label(-text=>'Role:')->grid(-column=>0,-row=>0);
    $self->{_roleBe}      = $f->BrowseEntry(-variable=>\$self->{_r});
    $self->{_roleBeEntry} = $self->{_roleBe}->Subwidget('entry');
    $self->{_roleBe}->grid(-column=>1,-row=>0,-sticky=>'e');
    $f->Label(-text=>'Process:')->grid(-column=>0,-row=>1);
    $self->{_processBe} = $f->BrowseEntry(-variable=>\$self->{_p})->grid(-column=>1,-row=>1,-sticky=>'e');
    $self->{_processBe}->bind("<Return>",[\&refreshProcessTree,$self]);
    $self->{_processBeEntry} = $self->{_processBe}->Subwidget('entry');
    $self->{_cb} = $f->Checkbutton(-text=>"Add new processes to existing?",-variable=>\$self->{_addFlag})
	->grid(-column=>2,-row=>0,-padx=>10,-sticky=>'w');
    $f->Button(-text=>'Refresh Process Tree',
	       -command=>[\&refreshProcessTree,$self->{_processBeEntry},$self])
	->grid(-column=>2,-row=>1,-padx=>10,-sticky=>'w');
}


## Method to create the top frame that contains the ProcessTree
## widget. This mega widget creates new APE objects based on passed
## criteria (see createRoleFrame). The APE object structure (process
## tree) is displayed by the ProcessTree widget. The user may expand
## and collapse branches of the tree as well as select them to add new
## roles to.
sub createTopFrame {
    my $self = shift;
    my $topFrame = $self->Frame->pack(-fill=>'both',-expand=>1);
    $self->{_processTree} = 
	$topFrame->ProcessTree
	(-user           =>\$self->{_user},
	 -discoveryMode  => TRUE,
	 -showProcessIds => TRUE,
	 -noButtons      => TRUE,
	 -selectmode     => 'extended',
	 -apecommand     => [\&_createApe,$self])
	->pack(-side   => 'left',
	       -fill   => 'both',
	       -expand => 1);
    $self->{_cntrlBttnFrame} = $topFrame->Frame
	(-relief      => 'ridge',
	 -borderwidth => 1)
	->pack(-side  => 'left',
	       -fill  => 'y');
    $self->addControlBttn("Select All",[\&_selectAll,$self]);
    $self->addControlBttn("Add Role",[\&_addRole,$self]);
}


## Method to insert a list of role type strings into the role
## BrowseEntry widget so that they become selectable (see insertRole
## method).
sub insertRoles {
    my ($self,$roleTypeList) = @_;
    foreach my $role (@$roleTypeList) {
	$self->insertRole('end',$role);
    }
}


## Method to insert a single role type string into the role
## BrowseEntry widget so that it becomes selectable. Also pushes the
## role type string to the objects list of available roles for future
## reference.
sub insertRole {
    my ($self,$index,$roleType) = @_;
    $self->{_roleBe}->insert($index,$roleType);
    push(@{$self->{_availableRoles}},$roleType);
}


## Method to select a role from the list of available roles that have
## been inserted into the role BrowseEntry widget for selection. If
## the passed role type string is found in the role widget then it is
## selected. If it's not found then it is added to the list of
## available role types and this method is recalled to select it.
sub selectRoleType {
    my ($self,$roleTypeToAdd) = @_;
    my $roleFound = FALSE;
    my @roleTypeList = @{$self->{_availableRoles}};
    for(my $i=0; $i < scalar(@roleTypeList); $i++) {
	if($roleTypeList[$i] eq $roleTypeToAdd) {
	    $roleFound = TRUE;
	    $self->{_r} = $roleTypeToAdd;
	}
    }
    unless($roleFound) {
	$self->insertRole('end',$roleTypeToAdd);
	$self->selectRoleType($roleTypeToAdd);
    }
}


### Callback method for the ProcessTree widget. This method is called
### when a new APE object is required by the ProcessTree widget. This
### method create the new APE object using the roleType argument and
### passes it back.
sub _createApe {
    my ($self,$processTree,$topLevelProcessId) = @_;
    my %args = 
	(topLevelProcessId       => $topLevelProcessId,
	 discoveryMode           => TRUE,
	 inputControllerFactory  => new APE::InputControllerFactory::ExoApeInputControllerFactory,
	 outputControllerFactory => new APE::OutputControllerFactory::ExoApeOutputControllerFactory,
	 flushControllerFactory  => new APE::FlushControllerFactory::ExoApeFlushControllerFactory
	 (userObj => \$self->{_user}));
    my $role = $self->{_r};
    unless(!$role || $role eq 'None') {
	$args{roleType} = $role;
    }
    return new APE::AtomicProcessEngine(%args);
}


## Method to add a control button to the control button frame. This
## method simply creates a button with the passed text and the command
## and packs it into the control button frame.
sub addControlBttn {
    my ($self,$bttnTitle,$cmd) = @_;
    $self->{_cntrlBttnFrame}->Button(-text=>$bttnTitle,-command=>$cmd)->pack;
}


## Method to refresh the view within the ProcessTree. This method
## requests the ProcessTree to load a new APE object based on the view
## criteria specified in the top frame. The new APE object is viewed
## in the ProcessTree widget. The _addFlag determines if the new APE
## object should be concatenated onto the current view or if the view
## should be cleared before the new APE object is displayed.
sub refreshProcessTree {
    my ($entry,$self) = @_;
    my $processId = $entry->get;
    if($processId) {
	$self->{_processTree}->unloadAllProcessIds unless $self->{_addFlag};
	$self->{_processTree}->loadProcessId($processId);
	$self->{_processBe}->Subwidget('entry')->delete(0,'end');
	unless(exists($self->{_viewedProcesses}{$processId})) {
	    $self->{_viewedProcesses}{$processId} = $processId;
	    $self->{_processBe}->insert('end',$processId);
	}
    }
}


## Callback method from the add role button. This method gathers the
## list of selected processes before showing the role dialog that
## allows the user to select the role that they wish to add to the
## selected processes.
sub _addRole {
    my ($self) = @_;
    my $selectedProcesses = $self->{_processTree}->getSelectedProcess;
    return unless $selectedProcesses;
    if(my $role = $self->_roleDialog($selectedProcesses)) {
	my $dbh = SqlEngine2::getDbh(LOGIN);
	$self->{_sqlEngine2}->beginTransaction($dbh);
	eval {
	    foreach my $process (@$selectedProcesses) {
		my $apeProcess = $process->[2];
		my $processId  = $apeProcess->getProcessId;
		$self->addRole($role,$processId);
	    }
	};
	if($@) { print "Rolling back: $@\n"; $self->{_sqlEngine2}->rollbackTransaction($dbh); }
	else   { print "Committing\n"; $self->{_sqlEngine2}->commitTransaction($dbh);   }
    }
}


## Method to actually add a role to a process with the passed process
## id number. This method checks to see if the passed process id
## already has the passed role type before adding a new row into the
## process_roletype table in gtr (or tgtr).
sub addRole {
    my ($self,$role,$processId) = @_;
    my $currentRoles = $self->findCurrentRoles($processId);
    if($self->roleExists($currentRoles,$role)) {
	die "Process with id_process '$processId' already has roletype '$role'.\n";
    }else{
	$self->{_sqlEngine2}->do
	    ('APE/Tk/ProcessRoleManager/ProcessRoleManager::AddRole',
	     [$processId,$role],SqlEngine2::getDbh(LOGIN));
    }
}


## Method that returns boolean true if the passed roletype exists in
## the passed list of current roles. The current list of roletypes
## should be as that returned from findCurrentRoles.
sub roleExists {
    my ($self,$currentRoles,$roleToSearchFor) = @_;
    foreach(@$currentRoles) {
	my $role = $_->[$iRole];
	if($role eq $roleToSearchFor) {
	    return TRUE;
	}
    }
    return FALSE;
}


## Method to find all of the roletypes that may execute/view the
## process with the passed process id number.
sub findCurrentRoles {
    my ($self,$processId) = @_;
    my $roles = $self->{_sqlEngine2}->getAll
	('APE/Tk/ProcessRoleManager/ProcessRoleManager::FindCurrentRoles',
	 [$processId],SqlEngine2::getDbh(LOGIN));
    return $roles;
}


## Method to show a dialog that contains a BrowseEntry widget that
## allows the user to select the role that they wish to add to the
## selected processes.
sub _roleDialog {
    my ($self,$processList) = @_;
    my @lRoles  = @{$self->{_availableRoles}};
    shift @lRoles;
    $self->{_roleDialog} 
    = $self->DialogBox
	(-title   => 'Select Role',
	 -buttons => ['OK','Cancel'],
	 -default_button => 'OK');
    my $be = $self->{_roleDialog}->add
	('BrowseEntry',-label=>"Select role to add:",-choices=>\@lRoles)->pack();
    my $result   = $self->{_roleDialog}->Show;
    my $selected = $be->Subwidget('entry')->get 
	if $result eq 'OK';
    return $selected;
}


## Method to find all of the APE roles from the database. These roles
## are prefixed with "APE_". Returns an array ref to the list of role
## names found.
sub findAvailableApeRoles {
    my $self = shift;
    my $roleData = $self->{_sqlEngine2}->getAll
	('APE/Tk/ProcessRoleManager/ProcessRoleManager::FindAvailableRoles',
	 [],SqlEngine2::getDbh(LOGIN));
    my @roles = map { $_->[0] } @$roleData;
    unshift(@roles,"None");
    return \@roles;
}

1;
