package GTx::APE::AtomicProcess;

use base 'GTx::APE::Process';

use strict;
use warnings;
use Carp;

use constant TRUE  => 1;
use constant FALSE => 0;

{
    my %_attributes = (
		       inConcreteIOId    => undef,
		       inConcreteEntity  => undef,
		       inEntityTypeId    => undef,
		       inEntityRemark    => undef,
		       inPrefix          => undef,
		       inDescription     => undef,
		       inFormat          => undef,
		       outConcreteIOId   => undef,
		       outConcreteEntity => undef,
		       outEntityTypeId   => undef,
		       outEntityRemark   => undef,
		       outPrefix         => undef,
		       outDescription    => undef,
		       outFormat         => undef,

		       inDataId          => undef,

		       arrayPatternHash  => {},
		       );

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


    #################################################################
    ## Initialise the objects private attributes. The argument is
    ## deleted as it is found, initialised and set.
    #################################################################
    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);
    }

    
    #################################################################
    ## Method to push a new ArrayPattern object to the
    ## arrayPatternHash attribute.
    #################################################################
    sub addArrayPattern {
	my ($self,$arrayPattern) = @_;
	croak "Error: must be an ArrayPattern object: $arrayPattern\n" 
	    unless $arrayPattern->isa('GTx::APE::ArrayPattern');
	push(@{$object{ $self }->{arrayPatternHash}->{$arrayPattern->getInMapId()}}, 
	     $arrayPattern);
    }


    #################################################################
    ## Method to return the number of ArrayPattern objects.
    #################################################################
    sub getArrayPatternCount {
	my $self = shift;
	my $count= 0;
	foreach my $inMapId (keys(%{$object{ $self }->{arrayPatternHash}})) {
	    $count += scalar(@{$object{ $self }->{arrayPatternHash}->{$inMapId}});
	}
	return $count;
    }


    #################################################################
    ## Method to delete the arrayPatternHash attribute. Returns the
    ## hash before deleting it.
    #################################################################
    sub deleteArrayPatternHash {
    	my $self = shift;
    	return delete $object{ $self }->{arrayPatternHash};
    }


    #################################################################
    ## Method to array the data using the array pattern. The array
    ## pattern reflects a 1:1 mapping between two object. This method
    ## sets the arraying data in the ArrayPattern object.  Sets this
    ## objects inDataId, this is the input id used to get the arraying
    ## data.
    #################################################################
    sub setArrayingData {
	my ($self,$inId,$data) = @_;
	$object{ $self }->{inDataId} = $inId;
	foreach(@{$data}) {
	    my $format = $_->[0];
	    my $mapId  = $_->[1];
	    if(exists($object{ $self }->{arrayPatternHash}->{$mapId})) {
		foreach my $arrayPattern (@{$object{ $self }->{arrayPatternHash}->{$mapId}}) {
		    $arrayPattern->setOutData($_);
		}
	    }
	}
    }


    #################################################################
    ## Method to clear the arraying data held in the ArrayPattern  
    ## objects.
    #################################################################
    sub clearArrayingData {
	my $self = shift;
	$object{ $self }->{inDataId} = undef;
	foreach my $mapId (keys %{$object{ $self }->{arrayPatternHash}}) {
	    foreach my $arrayPattern (@{$object{ $self }->{arrayPatternHash}->{$mapId}}) {
		$arrayPattern->clearOutData();
	    }
	}
    }
	

    #################################################################
    ## Method to check that the content count of each ArrayPattern
    ## objects outData attribute is consistent. An inconsistency is a
    ## fatal error.
    #################################################################
    sub isArrayedDataInconsistent {
	my $self = shift;
	my $count;
	foreach my $mapId (keys(%{$object{ $self }->{arrayPatternHash}})) {
	    foreach my $arrayPattern (@{$object{ $self }->{arrayPatternHash}->{$mapId}}) {
		my $thisCount = scalar(@{$arrayPattern->getOutData()});
		$count = $thisCount unless $count;
		if($count != $thisCount) {
		    return $arrayPattern;
		}
	    }
	}
	return FALSE;
    }


    #################################################################
    ## Method that returns boolean if this objects ArrayPattern objects
    ## have arraying data. 
    #################################################################
    sub hasArrayingData {
	my $self = shift;
	## Return false if there are no keys in the arrayPatternHash
	## attribute.
	return FALSE unless scalar %{$object{ $self }->{arrayPatternHash}};
	## Check each ArrayPattern held in the arrayPatternHash
	## attribute.
	foreach my $mapId (keys(%{$object{ $self }->{arrayPatternHash}})) {
	    foreach my $arrayPattern (@{$object{ $self }->{arrayPatternHash}->{$mapId}}) {
		unless($arrayPattern->hasOutData()) {
		    print STDERR "ArrayPattern from AtomicProcess (prefix '",
		    $self->getInPrefix(), "' with id_map '", $arrayPattern->getInMapId(),
		    "' contains no arraying data.\n";
		    return FALSE;
		}
	    }
	}
	return TRUE;
    }


    #################################################################
    ## Method to find the smallest inMapId in all of the ArrayPattern
    ## objects in the arrayPatternHash.
    #################################################################
    sub findMinInMapId {
	my $self = shift;
	my $apH  = $object{ $self }->{arrayPatternHash};
	my $minInMapId;
	foreach my $mapId (keys(%{$apH})) {
	    $minInMapId = $mapId unless $minInMapId;
	    if($mapId < $minInMapId) { $minInMapId = $mapId; }
	}
	return $minInMapId;
    }


    #################################################################
    ## Method to find the largest inMapId in all of the ArrayPattern
    ## objects in the arrayPatternHash.
    #################################################################
    sub findMaxInMapId {
	my $self = shift;
	my $apH  = $object{ $self }->{arrayPatternHash};
	my $maxInMapId;
	foreach my $mapId (keys(%{$apH})) {
	    $maxInMapId = $mapId unless $maxInMapId;
	    if($mapId > $maxInMapId) { $maxInMapId = $mapId; }
	}
	return $maxInMapId;
    }
    
    
    #################################################################
    ## Method to find the smallest outMapId in all of the ArrayPattern
    ## objects in the arrayPatternHash.
    #################################################################
    sub findMinOutMapId {
	my $self = shift;
	my $apH  = $object{ $self }->{arrayPatternHash};
	my $minOutMapId;
	foreach my $mapId (keys(%{$apH})) {
	    foreach my $ap (@{$apH->{$mapId}}) {
		$minOutMapId = $ap->getOutMapId() unless $minOutMapId;
		if($ap->getOutMapId() < $minOutMapId) { $minOutMapId = $ap->getOutMapId(); }
	    }
	}
	return $minOutMapId;
    }

    
    #################################################################
    ## Method to find the largest outMapId in all of the ArrayPattern
    ## objects in the arrayPatternHash.
    #################################################################
    sub findMaxOutMapId {
	my $self = shift;
	my $apH  = $object{ $self }->{arrayPatternHash};
	my $maxOutMapId;
	foreach my $mapId (keys(%{$apH})) {
	    foreach my $ap (@{$apH->{$mapId}}) {
		$maxOutMapId = $ap->getOutMapId() unless $maxOutMapId;
		if($mapId > $maxOutMapId) { $maxOutMapId = $mapId; }
	    }
	}
	return $maxOutMapId;
    }


    #################################################################
    ## Method to destroy this object.
    #################################################################
    sub DESTROY {
	my $self = shift;
	delete $object{$self};
	$self->SUPER::DESTROY;
    }
}

1;
