package User::Printer::BarcodePrinter;

use base 'User::Printer';

use strict;
use warnings;
use Carp;
use SqlEngine2;

use constant LOGIN => 'reports';

=head1 NAME

User::Team::BarcodePrinter - A class that represents a barcode printer
(part of the User library).

Note: this class does not print to a barcode printer.

=head1 SYNOPSIS

See User class for details of this classes use in the User library.

A User::Team::BarcodePrinter object can be used as a place holder.

use User::Printer::BarcodePrinter;

my $barcodePrinter = new User::Printer::BarcodePrinter(name => 'd318bc');

print "Barcode printer name       : ", $barcodePrinter->getName(),       "\n";
print "Barcode printer id         : ", $barcodePrinter->getPrinterId(),  "\n";
print "Barcode printer offset     : ", $barcodePrinter->getOffset(),     "\n";
print "Barcode printer density    : ", $barcodePrinter->getDensity(),    "\n";
print "Barcode printer activeLabel: ", $barcodePrinter->getActiveLabel(),"\n";


=head1 DESCRIPTION

This class represents a barcode printer that's details are held in the
Sanger Institute Oracle database. This class will populate itself
depending on the constructor used to instantiate the object (see
CONSTRUCTOR section).

The printer id is the key to the BARCODE_PRINTER@CAS database
table. The offset is the distance between labels that are used on this
barcode printer. The active label dictates the type of label that can
be printed on this barcode printer (see SIBarcode for more info on
barcode printer label types and barcode printing).

This is a reusable object class. This means that once a
User::Printer::BarcodePrinter object has been instantiated and
populated it can be recycled by calling the $barcodePrinter->reset()
method with any of the valid constructor options (see CONSTRUCTOR
section).


=head1 CONSTANTS

=over 10

=item User::Team::TRUE

Boolean true.

=cut

use constant TRUE => 1;

=item User::Team::FALSE

Boolean false.

=back

=cut

use constant FALSE => 0;


=head1 CONSTRUCTORS

=over 10

=item $barcodePrinter = new User::Printer::BarcodePrinter(name => value);

The name of a barcode printer (DESCRIPTION.BARCODE_PRINTER@CAS).

=item $barcodePrinter = new User::Printer::BarcodePrinter(teamName => value);

A team name to find the barcode printer for (TEAMNAME.DEFAULT_PRINTERS@CAS).

=back


=head1 METHODS

=over 10

=cut


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

{
    my %_attributes = (
		       printerId   => undef,
		       offset      => undef,
		       density     => undef,
		       activeLabel => undef
		       );

    my %object;


=item User::Printer::BarcodePrinter->new(arg => value,... => ?);

This is the constructor for the User::Printer class (see the
CONSTRUCTOR section for more information on valid arguments). This
method calls the _init(%args) method which allows deriving classes
access to the argument list passed to the constructor (see
User::Printer::_init(%args) method). An error is thrown if there are
still arguments left in the %args hash after the _init() method has
been called.

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

Private method. Documented to aid sub classing. Derived classes should
implement their own _init() methods that removes the arguments they
are interested in from the passed %args hash. The %args hash should be
returned from _init() to populate the base class:

Derived class:

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

This method executes database lookups to find the barcode printer for
a team or to lookup the details of a passed barcode printer name - see
the findTeamBarcodePrinter() and findBarcodePrinter() methods.

=cut

    sub _init {
	my ($self,%args) = @_;
	## Use the passed SqlEngine2 object or create our own.
	if($args{sqlEngineObj}) {
	    $object{ $self }->{sqlEngineObj}     = delete $args{sqlEngineObj};
	}elsif(!$object{ $self }->{sqlEngineObj}){
	    $object{ $self }->{sqlEngineObj} = new SqlEngine2();
	}

	## ADD NEW POPULATION METHOD CALLS HERE ##
	## Populate based on the parsed teamName.
	if($args{teamName}) {
	    %args = $self->findTeamBarcodePrinter(delete $args{teamName});
	}

	## Populate based on the parsed barcode printer name.
	elsif($args{name}) {
	    %args = $self->findBarcodePrinter(delete $args{name});
	}

	## Initialise this objects attributes.
	foreach my $attName (keys(%args)) {
	    if(exists($_attributes{$attName})) { 
		$object{ $self }->{$attName} = delete $args{$attName};
	    }
	}

	## Make sure that a name is being passed to the base class so
	## that this object can carry on living regardless of its state.
	#$args{name} = 'Not Set' unless $args{name};

	return $self->SUPER::_init(%args);
    }


=over 5

One of the following methods is called during the instantiation
process in order to retrieve database data about a barcode printer.


=item $bcPrinter->findBarcodePrinter($barcodePrinterName)

Executes a database lookup to find the details of the passed barcode
printer name. Passes the results to the private _sortData() method and
returns the hash it returns.

=cut

    sub findBarcodePrinter {
	my ($self,$bpName) = @_;
	my $printerData = 
	    eval { $object{ $self }->{sqlEngineObj}
		   ->getAll('User/Printer/BarcodePrinter::findBarcodePrinter',
			    [$bpName],SqlEngine2::getDbh(LOGIN)); };
	if($@)   { croak "ERROR: can't get barcode printer data for '$bpName': $@\n"; }
	return _sortData($printerData->[0]);
    }

=item $bcPrinter->findTeamBarcodePrinter($teamName)

Executes a database lookup to find the details of the barcode printer
assigned to the passed team $teamName. Passes the results to the
private _sortData() method and returns the hash it returns.

=cut

    sub findTeamBarcodePrinter {
	my ($self,$teamName) = @_;
	my $printerData = 
	    eval { $object{ $self }->{sqlEngineObj}
		   ->getAll('User/Printer/BarcodePrinter::findTeamDefaultBarcodePrinter',
			    [$teamName],SqlEngine2::getDbh(LOGIN)); };
	if($@)   { croak "ERROR: can't get default printer data: $@\n"; }
	## Return the sorted data hash.
	return _sortData($printerData->[0]);
    }


=back

=item $bc->_sortData($printerData)

Private method that sorts the data returned by the
findBarcodePrinter() and findTeamBarcodePrinter() methods into a hash
reference that will be used to populate this object in the _init()
method.

Returns a hash reference:

printerId   => $printerData->[0],
name        => $printerData->[1],  ## description
offset      => $printerData->[2],  ## offset,
density     => $printerData->[3],  ## density,
activeLabel => $printerData->[4]

If $printerData contains no data, due to a failed lookup or the
barcode printer not being found in the database, then the name
attribute is set to 'Not Set' and is the only key value pair returned
in the hash reference. This results in a User::Printer::BarcodePrinter
object containing nothing.

=cut

    sub _sortData {
	my $printerData = shift;
	## Check if anything was returned.
	if(defined($printerData) && @{$printerData}) {
	    #if((my $rowCount = scalar(@{$printerData}))>1)
	    #{ warn "more than one barcode printer found: $rowCount\n"; }
	    ## Return the new argument list for object initialisation.
	    return(printerId  =>$printerData->[0],
		   name       =>$printerData->[1],  ## description
		   offset     =>$printerData->[2],  ## offset,
		   density    =>$printerData->[3],  ## density,
		   activeLabel=>$printerData->[4]); ## activeLabel
	}else {
	    ## We MUST return a name as the base class requires this
	    ## attribute to be set when initialised.
	    return(name=>'Not Set');
	}
    }


=item $bcPrinter->getPrinterId()

Returns this barcode printers id number.

=item $bcPrinter->getOffset()

Returns this barcode printers offset.

=item $bcPrinter->getDensity()

Returns this barcode printers density.

=item $bcPrinter->getActiveLabel()

Returns this barcode printers active label number.

=cut

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


=item $bcPrinter->canPrintPrefix($prefix)

Performs a database lookup using $prefix and the activeLabel object
attribute value to determine if this barcode printer can print this
type of prefixes barcode labels.

Returns boolean true if this barcode printer can print $prefix
barcodes, otherwise returns false.

=cut

    sub canPrintPrefix {
	my ($self,$prefix) = @_;
	croak "ERROR: argument 'prefix' is missing.\n"   unless $prefix;
	croak "ERROR: this BarcodePrinter has no data\n" unless $self->hasData();
	my $printerName = $self->getName();
	my $activeLabel = $object{ $self }->{activeLabel};
	my $prefixData  = 
	    eval { $object{ $self }->{sqlEngineObj}
		   ->getRow('User/Printer/BarcodePrinter::canPrintPrefix',
			    [$prefix,$activeLabel],SqlEngine2::getDbh(LOGIN)); };
	if($@){ 
	    croak "ERROR: can't find if printer '$printerName' can print '$prefix' labels.: $@\n";
	}
	return defined $prefixData ? TRUE : FALSE;
    }


=item $bcPrinter->hasData()

Returns boolean true if this barcode printer contains data. This is
decided based on the value of this barcode printers name attribute. If
the getName() method returns 'Not Set' then the object has not been
populated and false is returned, otherwise true is returned.

Returns boolean true if the getName() method returns something other
than 'Not Set'.

=cut

    sub hasData {
	my $self = shift;
	return $self->getName() eq 'Not Set' ? FALSE : TRUE;
    }


=item $bcPrinter->reset(%attData)

Resets this barcode printer object using %attributeData as the object
arguments. This method calls the _init() method and passes it %attData
to re-initialise this object.

=cut

    sub reset {
	my ($self,%attData) = @_;
	croak "ERROR: argument 'attData' is missing.\n" unless %attData;
	$self->_init(%attData);
    }


=item $bcPrinter->test()

Overrides test() method in the User::Printer base class. This method
tests this barcode printer by executing an 'lpr -P[printer_name]' and
analysing the output. If the output does not match the following
pattern then an error is thrown:

qr/Printer OK/

Dies if this barcode printer is not available, off-line or anything
other than OK.

=cut

    sub test {
	my $self = shift;
	my $printerName   = $self->getName;
	my $printerStatus = `lpq -P$printerName`;
	unless(0 == $? >> 8) {
	    die "ERROR: can't execute lpq command ".
	      "'lpq -P$printerName: ", ($? >> 8), "\n";
	}
	if((length($printerStatus)<1) 
	   || ($printerStatus !~ /Printer OK/)) {
	    die "ERROR: printer '$printerName' is ".
	      "unavailable.\n";
	}
    }


=item DESTROY

Destructor method.

=cut

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

1;


=back

=head1 SEE ALSO

User, User::Team, User::Team::Role, User::Printer

=head1 AUTHOR

 Simon Holroyd <holroyd@sanger.ac.uk>
 04th April 2007

=cut
