=head1 NAME

iPE::Model::DurationSubmodel - The container for a composite state duration distribution for a specified isochore level.

=head1 DESCRIPTION

This class contains all the specific state duration distributions for a particular region type and a particular isochore level.  Each submodel may have one or more distributions, as defined by the base class L<iPE::Model::DurationDistribution>.

=over 8

=cut

package iPE::Model::DurationSubmodel;

use iPE;
use iPE::Globals;
use iPE::Util::Overlap;
use iPE::Util::Overlap::Node;
use iPE::Model::DurationDistribution::CONSTANT;
use iPE::Model::DurationDistribution::DEFINED;

use base ("iPE::Model");
use strict;

=head1 FUNCTIONS

=item new(tag, att)

Creates a new duration submodel.  Should not be called directly except by L<iPE::gHMM>.

=cut
sub new {
    my $class = shift;
    my $this = $class->SUPER::new(@_);
    my ($tag, $att, $data, $element) = @_;

    $this->{region_} = $att->{region};
    $this->{isochore_} = $att->{isochore};
    $this->{distributions_} = [];
    $this->{overlap_} = new iPE::Util::Overlap;
    $this->{totalSamples_} = 0;
    $this->{countedSamples_} = 0;

    $this->handle_submodels($element);

    return $this;
}

=item region ()

The region which the parent model belongs to.

=cut
sub region      { shift->{region_} }

=item isochore ()

The main thing that a DurationSubmodel provides is an isochore level bucket for features to be counted into.  The level is kept here.

=cut
sub isochore      { shift->{isochore_}      }
=item distributions ()

The array reference to the actual DurationDistribution objects.

=cut
sub distributions { shift->{distributions_} }
sub overlap       { shift->{overlap_}       }
=item totalSamples (), countedSamples ()

Another feature that the duration submodel tracks is the prior probability of a smaple being in any of its durations.  Here the total samples are tracked, and the number of samples are increased with each count.  totalSamples () represents the total number of samples submitted, countedSamples represent the number that fit into one of the specific duration distributions.

=cut
sub totalSamples  { shift->{totalSamples_}  }
sub countedSamples  { shift->{countedSamples_}  }

sub init {
    my ($this) = @_;

    $this->{overlap_} = undef;
    for my $dur_dist (@{$this->distributions}) {
        $dur_dist->init;
    }
    $this->{distributions_} = 
        [ sort {$a->min <=> $b->min} @{$this->distributions} ] ;
}

sub outputPrepare {
    my ($this, $out, $mode) = @_;

    for my $dur_dist(@{$this->distributions}) {
        next if $dur_dist->type == iPE::Model::DurationDistribution::FIXED;
        $dur_dist->outputPrepare($out, $mode);
        if($mode ne "score") {
            my $pstring = $dur_dist->getParamString();
            my $header = $out->indent."Total Samples: ".$dur_dist->samples."\n";
            if($mode eq "prob") {
                $header .= $out->indent."Density: ".$dur_dist->density."\n";
            }
            $pstring = $header.$pstring;
            $dur_dist->setParamString($pstring);
        }
    }
}

sub outputZoe {
    my ($this, $out, $mode) = @_;

    $out->print (scalar(@{$this->distributions}."\n"));
    $out->increaseIndent;
    for my $dist (@{$this->distributions}) {
        $dist->outputZoe($out, $mode);
    }
    $out->decreaseIndent;
}

sub handle_submodel {
    my $this = shift;
    my ($tag, $att, $data, $element) = @_;
    my $dist;

    my $classname = 
        iPE::Model::DurationDistribution::getClassname($att->{model});

    # pass the region name along to the actual distribution
    $att->{region} = $this->region;
    $dist = $classname->new(@_);
    push @{$this->distributions}, $dist;

    my $node = new iPE::Util::Overlap::Node( { low  => $dist->min, 
                                               high => $dist->max,
                                               letter => "L"} );
    my $overlap_node = $this->overlap->find_overlap($node);
    die "Region (".$node->low->coord.", ".$node->high->coord.
        ") overlaps region (".$overlap_node->low->coord.", ".
        $overlap_node->high->coord.") for duration distribution on line ".
        $element->line_number."\n" if($overlap_node);

    $this->overlap->insert($node);
}

sub countFeature {
    my ($this, $feature, $wt) = @_;

    my $length = $feature->end-$feature->start+1;
    $this->{totalSamples_} += $wt;
    #the distributions should be sorted, so the "L" distribution should be last.
    for my $dist (@{$this->distributions}) {
        next if $dist->type == iPE::Model::DurationDistribution::FIXED ();
        #my $corrLength = int($length/$dist->lengthUnit);
        #while the lengthUnit defines how small a sample unit to consider
        #it is not shown in the parameter file as such.  The length is
        #the length
        if($dist->min < $length && ($dist->max eq "L" || $dist->max > $length)){
            $dist->incSamples($wt);
            $dist->countFeature($feature, $wt);
            $this->{countedSamples_} += $wt;
            last;
        }
    }
}

sub smooth {
    my ($this) = @_;
    my $cumDensity = 0;

    for my $dist (@{$this->distributions}) {
        next if $dist->type == iPE::Model::DurationDistribution::FIXED ();

        if($this->totalSamples == 0) {
            Warn("0 samples in duration distribution $this->{region_}\n".
                 "for isochore $this->{isochore_}.  There is probably no\n".
                 "sequence in the $this->{isochore_} isochore group\n");
            $dist->setDensity(0);
            next;
        }
        else { $dist->setDensity($dist->samples/$this->totalSamples); }
        $dist->setCumDensity($cumDensity);
        $dist->smooth;
        $cumDensity += $dist->density;
    }
}

sub normalize {
    my ($this) = @_;
    msg("real total counts: ".$this->totalSamples."\n");
    my $lastEndProb = -1;
    # if an internal distribution (one that ends before "L") fails to produce
    # a final probability, then we have to remove the rest of the distributions
    # and set this one's end point to "L".
    #my $removingDists = 0;

    for my $dist (sort {$a->min <=> $b->min } @{$this->distributions}) {
        if($dist->type == iPE::Model::DurationDistribution::FIXED ()) {
            $lastEndProb = -1; next; }
        $dist->initialProb($lastEndProb);
        &{$dist->normalizer}($dist);
        $lastEndProb = $dist->finalProb;
        if($lastEndProb < 0. && $dist->max ne "L") {
            $dist->max("L");
            while($this->distributions->[scalar(@{$this->distributions}) - 1] 
                    != $dist) {
                my $cur_dist = pop @{$this->distributions};
                $this->element->removeChild($cur_dist->element);
            }
            last;
        }
    }
}

sub score {
    my ($this) = @_;

    for my $dist (@{$this->distributions}) {
        next if $dist->type == iPE::Model::DurationDistribution::FIXED ();
        $dist->score;
    }
}

=back

=head1 SEE ALSO

L<iPE::Model>

=head1 AUTHOR

Bob Zimmermann (rpz@cse.wustl.edu)

=cut
1;
