#-----------------------------------------------------------------
# MOSES::MOBY::Package
# Author: Edward Kawas <edward.kawas@gmail.com>,
#         Martin Senger <martin.senger@gmail.com>
# For copyright and disclaimer see below.
#
# $Id: Package.pm,v 1.4 2008/04/29 19:45:01 kawas Exp $
#-----------------------------------------------------------------

#-----------------------------------------------------------------
#
# MOSES::MOBY::Package
#
#-----------------------------------------------------------------
package MOSES::MOBY::Package;
use base qw( MOSES::MOBY::Base );
use XML::LibXML;
use MOSES::MOBY::Tags;
use MOSES::MOBY::ServiceException;
use strict;

# add versioning to this module
use vars qw /$VERSION/;
$VERSION = sprintf "%d.%02d", q$Revision: 1.4 $ =~ /: (\d+)\.(\d+)/;

#-----------------------------------------------------------------
# A list of allowed attribute names. See MOSES::MOBY::Base for details.
#-----------------------------------------------------------------
{
    my %_allowed =
	(
	 authority     => undef,
	 jobs          => {type => 'MOSES::MOBY::Job', is_array => 1},
	 exceptions    => {type => 'MOSES::MOBY::ServiceException', is_array => 1},
	 serviceNotes  => undef,
         cdata         => {type => MOSES::MOBY::Base->BOOLEAN},
	 );

    sub _accessible {
	my ($self, $attr) = @_;
	exists $_allowed{$attr} or $self->SUPER::_accessible ($attr);
    }
    sub _attr_prop {
	my ($self, $attr_name, $prop_name) = @_;
	my $attr = $_allowed {$attr_name};
	return ref ($attr) ? $attr->{$prop_name} : $attr if $attr;
	return $self->SUPER::_attr_prop ($attr_name, $prop_name);
    }
}

#-----------------------------------------------------------------
# init
#-----------------------------------------------------------------
sub init {
    my ($self) = shift;
    $self->SUPER::init();
    $self->cdata ('no');
}

#-----------------------------------------------------------------
# job_by_id
#-----------------------------------------------------------------

=head2 job_by_id

In this package, find and return a C<MOSES::MOBY::Job> object with the given
ID. Or throw an exception if such job does not exist. (TBD)

=cut

sub job_by_id {
    my ($self, $jobId) = @_;
    foreach my $job (@{ $self->jobs }) {
	return $job if $job->jid eq $jobId;
    }
    $self->throw ("Job '$jobId' not found.");
}


#-----------------------------------------------------------------
# size
#-----------------------------------------------------------------
sub size {
    my $self = shift;
    return 0 unless $self->jobs;
    return 0+@{ $self->jobs };
}

#-----------------------------------------------------------------
# toXML
#-----------------------------------------------------------------
sub toXML {
    my $self = shift;
    $self->increaseXMLCounter;
    my $root = $self->createXMLElement (MOBY);
    my $elemContent = $self->createXMLElement (MOBYCONTENT);
    $self->setXMLAttribute ($elemContent, AUTHORITY, $self->authority);

    if ($self->serviceNotes or $self->{exceptions}) {
	my $sNotes = $self->createXMLElement (SERVICENOTES);
	if ($self->serviceNotes) {
	    my $notes =	$self->createXMLElement (NOTES);
	    if ($self->cdata) {
		$notes->appendChild (XML::LibXML::CDATASection->new ($self->serviceNotes));
	    } else {
		$notes->appendText ($self->serviceNotes);
	    }
	    $sNotes->appendChild ($notes);
	}
	if ($self->exceptions) {
	    foreach my $exception (@{ $self->exceptions }) {
		$sNotes->appendChild ($exception->toXML);
	    }
	}
	$elemContent->appendChild ($sNotes);
    }
    
    if ($self->jobs) {
	foreach my $job (@{ $self->jobs }) {
	    $elemContent->appendChild ($job->toXML);
	}
    }

    $root->appendChild ($elemContent);
    return $self->closeXML ($root);
}


#-----------------------------------------------------------------
#
# MOSES::MOBY::DataElement
#
#-----------------------------------------------------------------
package MOSES::MOBY::DataElement;
use base qw( MOSES::MOBY::Base );
use XML::LibXML;
use MOSES::MOBY::Tags;
use strict;

#-----------------------------------------------------------------
# A list of allowed attribute names. See MOSES::MOBY::Base for details.
#-----------------------------------------------------------------
{
    my %_allowed =
	(
	 name => undef,
	 );

    sub _accessible {
	my ($self, $attr) = @_;
	exists $_allowed{$attr} or $self->SUPER::_accessible ($attr);
    }
    sub _attr_prop {
	my ($self, $attr_name, $prop_name) = @_;
	my $attr = $_allowed {$attr_name};
	return ref ($attr) ? $attr->{$prop_name} : $attr if $attr;
	return $self->SUPER::_attr_prop ($attr_name, $prop_name);
    }
}

sub init {
    my ($self) = shift;
    $self->SUPER::init();
    $self->name ('');
}

#-----------------------------------------------------------------
# toXML
#    return a LibXML::Element called _dummy_ (it will be later
#    changed either to Simple or to Collection)
#-----------------------------------------------------------------
sub toXML {
    my $self = shift;
    $self->increaseXMLCounter;

    my $root = XML::LibXML::Element->new ('_dummy_');
    $root->setNamespace (MOBY_XML_NS, MOBY_XML_NS_PREFIX);
    $root->setAttributeNS (MOBY_XML_NS, ARTICLENAME, $self->name)
	if $self->name;
    
    return $self->closeXML ($root);
}

#-----------------------------------------------------------------
#
# MOSES::MOBY::Simple
#
#-----------------------------------------------------------------
package MOSES::MOBY::Simple;
use base qw( MOSES::MOBY::DataElement );
use XML::LibXML;
use MOSES::MOBY::Tags;
use strict;

#-----------------------------------------------------------------
# A list of allowed attribute names. See MOSES::MOBY::Base for details.
#-----------------------------------------------------------------
{
    my %_allowed =
	(
	 data  => {type => 'MOSES::MOBY::Data::Object'},
	 );

    sub _accessible {
	my ($self, $attr) = @_;
	exists $_allowed{$attr} or $self->SUPER::_accessible ($attr);
    }
    sub _attr_prop {
	my ($self, $attr_name, $prop_name) = @_;
	my $attr = $_allowed {$attr_name};
	return ref ($attr) ? $attr->{$prop_name} : $attr if $attr;
	return $self->SUPER::_attr_prop ($attr_name, $prop_name);
    }
}

#-----------------------------------------------------------------
# toXML
#-----------------------------------------------------------------
sub toXML {
    my $self = shift;
    $self->increaseXMLCounter;
    my $root = $self->SUPER::toXML;
    $root->setNodeName (MOBY_XML_NS_PREFIX . ':' . SIMPLE);
    $root->appendChild ($self->data->toXML)
	if $self->data;
    return $self->closeXML ($root);
}


#-----------------------------------------------------------------
#
# MOSES::MOBY::Collection
#
#-----------------------------------------------------------------
package MOSES::MOBY::Collection;
use base qw( MOSES::MOBY::DataElement );
use XML::LibXML;
use MOSES::MOBY::Tags;
use strict;

#-----------------------------------------------------------------
# A list of allowed attribute names. See MOSES::MOBY::Base for details.
#-----------------------------------------------------------------
{
    my %_allowed =
	(
	 data  => {type => 'MOSES::MOBY::Simple', is_array => 1},
	 );

    sub _accessible {
	my ($self, $attr) = @_;
	exists $_allowed{$attr} or $self->SUPER::_accessible ($attr);
    }
    sub _attr_prop {
	my ($self, $attr_name, $prop_name) = @_;
	my $attr = $_allowed {$attr_name};
	return ref ($attr) ? $attr->{$prop_name} : $attr if $attr;
	return $self->SUPER::_attr_prop ($attr_name, $prop_name);
    }
}

#-----------------------------------------------------------------
# toXML
#-----------------------------------------------------------------
sub toXML {
    my $self = shift;
    $self->increaseXMLCounter;
    my $root = $self->SUPER::toXML;
    $root->setNodeName (MOBY_XML_NS_PREFIX . ':' . COLLECTION);
    if ($self->data) {
	foreach my $simple (@{ $self->data }) {
	    $root->appendChild ($simple->toXML);
	}
    }
    return $self->closeXML ($root);
}


#-----------------------------------------------------------------
#
# MOSES::MOBY::Parameter
#
#-----------------------------------------------------------------
package MOSES::MOBY::Parameter;
use base qw( MOSES::MOBY::DataElement );
use XML::LibXML;
use MOSES::MOBY::Tags;
use strict;

#-----------------------------------------------------------------
# A list of allowed attribute names. See MOSES::MOBY::Base for details.
#-----------------------------------------------------------------
{
    my %_allowed =
	(
	 value  => undef,
	 );

    sub _accessible {
	my ($self, $attr) = @_;
	exists $_allowed{$attr} or $self->SUPER::_accessible ($attr);
    }
    sub _attr_prop {
	my ($self, $attr_name, $prop_name) = @_;
	my $attr = $_allowed {$attr_name};
	return ref ($attr) ? $attr->{$prop_name} : $attr if $attr;
	return $self->SUPER::_attr_prop ($attr_name, $prop_name);
    }
}

#-----------------------------------------------------------------
# toXML
#-----------------------------------------------------------------
sub toXML {
    my $self = shift;
    $self->increaseXMLCounter;

    my $root = XML::LibXML::Element->new (PARAMETER);
    $root->setNamespace (MOBY_XML_NS, MOBY_XML_NS_PREFIX);
    $root->setAttributeNS (MOBY_XML_NS, ARTICLENAME, $self->name)
	if $self->name;
    if ($self->value)  {
	my $val = XML::LibXML::Element->new (VALUE);
	$val->setNamespace (MOBY_XML_NS, MOBY_XML_NS_PREFIX);
	$val->appendText ($self->value);
	$root->appendChild ($val);
    }
    return $self->closeXML ($root);
}

#-----------------------------------------------------------------
#
# MOSES::MOBY::Job
#
#-----------------------------------------------------------------
package MOSES::MOBY::Job;
use base qw( MOSES::MOBY::Base );
use XML::LibXML;
use MOSES::MOBY::Tags;
use strict;

#-----------------------------------------------------------------
# A list of allowed attribute names. See MOSES::MOBY::Base for details.
#-----------------------------------------------------------------
{
    my %_allowed =
	(
	 jid           => undef,
	 dataElements  => {type => 'MOSES::MOBY::DataElement', is_array => 1},

	 # used internally (which context this job belongs to)
         _context      => {type => 'MOSES::MOBY::Package'},
	 );

    sub _accessible {
	my ($self, $attr) = @_;
	exists $_allowed{$attr} or $self->SUPER::_accessible ($attr);
    }
    sub _attr_prop {
	my ($self, $attr_name, $prop_name) = @_;
	my $attr = $_allowed {$attr_name};
	return ref ($attr) ? $attr->{$prop_name} : $attr if $attr;
	return $self->SUPER::_attr_prop ($attr_name, $prop_name);
    }
}

#-----------------------------------------------------------------
# record things into this job's context
#-----------------------------------------------------------------
sub record_info {
    shift->_record (MOSES::MOBY::ServiceException->info (@_));
}

sub record_warning {
    shift->_record (MOSES::MOBY::ServiceException->warning (@_));
}

sub record_error {
    shift->_record (MOSES::MOBY::ServiceException->error (@_));
}

sub _record {
    my ($self, $exception) = @_;
    return unless defined $self->_context;
    $exception->jobId ($self->jid);
    $self->_context->add_exceptions ($exception);
}

#-----------------------------------------------------------------
# toXML
#-----------------------------------------------------------------
sub toXML {
    my $self = shift;
    $self->increaseXMLCounter;
    my $root = XML::LibXML::Element->new (MOBYDATA);
    $root->setNamespace (MOBY_XML_NS, MOBY_XML_NS_PREFIX);
    $root->setAttributeNS (MOBY_XML_NS, QUERYID, $self->jid)
	if $self->jid;
    if ($self->dataElements) {
	foreach my $element (@{ $self->dataElements }) {
	    $root->appendChild ($element->toXML);
	}
    }
    return $self->closeXML ($root);
}

#-----------------------------------------------------------------
# getData
#    return a data element by its name (an element can be a Simple or
#    a Collection); or the first data element if no $element_name was
#    given; or throw an exception if there is no such element
#-----------------------------------------------------------------
sub getData {
    my ($self, $element_name) = @_;
    $self->throw ('Job does not have any data, at all.')
	unless $self->dataElements;
    return ${ $self->dataElements }[0] unless ($element_name);
    foreach my $elem (@{ $self->dataElements }) {
	next unless $elem->name;
	return $elem if $elem->name eq $element_name;
    }
    $self->throw ("Job does not have data element '$element_name'.");
}

#-----------------------------------------------------------------
# setData
#
#    Set $value as a new data element in this job. If an element of
#    the same name already exists it is replaced.
#
#    The name is either from $element_name, or, if $element_name is
#    not given, from the element itself ($value->name). The $value
#    should always have a name (playing the role of an 'article
#    name'). If none has a name, the $value is added at the end - but
#    that should not happen (BioMoby API requires data to be
#    named). Note, however, that adding at the end can be done only
#    with a value that is already a MOSES::MOBY::DataElement (see below).
#
#    Note that except of the ability to replace an existing element,
#    it is equivalent to the add_dataElements ($new_element).
#
#    The $value must be of type MOSES::MOBY::DataElement (actually, of one of
#    its sub-classes: Simple, Collection, or Parameter), or a Moby
#    data object (in which case, it is wrapped in a Simple), or a
#    primitive value (in which case it is first wrapped in a Moby
#    primitive type - its type is chosen according the given
#    $element_type - and then into a Simple).
#
#-----------------------------------------------------------------
sub setData {
    my ($self, $value, $element_name, $element_type) = @_;

    my $value_ref = ref ($value);

    # value may already be a container - in which case just store it
    if ($value_ref and $value->isa ('MOSES::MOBY::DataElement')) {
	$self->_place_value ($value, $element_name);
	return;
    }

    # from now we definitely need an element name
    # (because $value needs to be wrapped in something named)
    $self->throw ("An element name must be given when setting data to a job.")
	unless $element_name;

    # now $value is a moby data type or a primitive value
    $element_type ||= MOSES::MOBY::Base->STRING;
    $self->_place_value ($value_ref ?
			 new MOSES::MOBY::Simple ( data => $value,
					    name => $element_name ) :
			 new MOSES::MOBY::Simple ( data => $self->check_type ($element_name, $element_type, $value),
					    name => $element_name ),
			 $element_name);
}

sub _place_value {
    my ($self, $value, $element_name) = @_;

    # make sure that we have a space where to put value
    $self->dataElements ([])
	unless $self->dataElements;

    my $name = $element_name || $value->name;
    if ($name) {
	foreach my $elem (@{ $self->dataElements }) {
	    if ($name eq $elem->name) {
		$elem = $value;
		return;
	    }
	}
    }
    push (@{ $self->dataElements }, $value);
}

# $value is an ARRAY or a single value; if $value is not already a
# Simple, it will be wrapped in Simple (or Simples). If $value is not
# even a reference (which means that it represents just a value of a
# primitive type) we use $element_type to create first a primitive
# type, and then wrap it into a Simple.

# $element_name is here mandatory (no magic with a potential name in
# $value as in 'setData').

# This is used for adding data to a collection. Therefore, if there is
# no collection named $element_name found, a new collection is
# created.

sub addData {
    my ($self, $element_name, $element_type, @values) = @_;
    return unless @values;
    my $value_ref = ref ($values[0]);

    # just in case somebody sends here a ready collection
    return $self->setData ($values[0], $element_name)
	if $value_ref eq 'MOSES::MOBY::Collection';

    # from now we definitely need an element name
    $self->throw ("An element name must be given when adding things to a job.")
	unless $element_name;

    # make sure that values are Simples
    $element_type ||= MOSES::MOBY::Base->STRING;
#    my @values = $value_ref eq 'ARRAY' ? @$value : $value;
    foreach my $val (@values) {
	next if ref ($val) eq 'MOSES::MOBY::Simple';
	$val = (ref ($val) ?
		new MOSES::MOBY::Simple ( data => $val ) :
		new MOSES::MOBY::Simple ( data => $self->check_type ($element_name, $element_type, $val) ) );
    }

    # find a place where values should be added
    $self->dataElements ([])
	unless $self->dataElements;
    foreach my $elem (@{ $self->dataElements }) {
	if ($element_name eq $elem->name) {
	    $self->throw ("In a job, a collection was expected but found this:\n" .
			  $elem->toString)
		unless ref($elem) eq 'MOSES::MOBY::Collection';
	    push (@{ $elem->{data} }, @values);
	    return;
	}
    }
    $self->setData (new MOSES::MOBY::Collection ( name => $element_name,
					   data => [@values] ),
		    $element_name);
}

#-----------------------------------------------------------------
# getParameter
#    return a value of a secondary parameter given by its name;
#    throw an exception if there is no such parameter
#-----------------------------------------------------------------
sub getParameter {
    my ($self, $element_name) = @_;
    $self->throw ('Job does not have any parameters, at all.')
	unless $self->dataElements;
    foreach my $elem (@{ $self->dataElements }) {
	next unless $elem->name;
	return $elem->value
	    if $elem->name eq $element_name and ref ($elem) eq 'MOSES::MOBY::Parameter';
    }
    $self->throw ("Job does not have parameter '$element_name'.");
}



1;
__END__