=head1 NAME

iPE::Model::Emission::LUT - Markov chain model (Look-Up Table)

=head1 DESCRIPTION

=head1 FUNCTIONS

=cut

package iPE::Model::Emission::LUT;
use iPE;
use iPE::Globals;
use base("iPE::Model::Emission");
use strict;

sub init {
    my ($this) = @_;
    $this->{posCounts_}  = {};
    $this->{nullCounts_} = {};
    $this->{scores_}     = {};

    unless($this->hasSettings()) {
        #XXX deprecated.  remove when moving to settings format
        my ($orderstr) = split(' ', $this->{data_});
        if(defined($orderstr)) {
            if($orderstr =~ m/\//) {
                ($this->settings->{order}, $this->settings->{targetOrder}) = 
                    split(/\//, $orderstr);
            }
            else {
                ($this->settings->{order}, $this->settings->{targetOrder}) = 
                    ($orderstr, $orderstr);
            }
        }
    }

    die("LUT requires an order setting in the data attribute.\n".
        "Error found in ".$this->name."\n")
        if(!defined($this->order));

    $this->settings->{targetOrder} ||= $this->order;
    if($this->order =~ m/[^\d]/ || $this->targetOrder =~ m/[^\d]/) {
        die(__PACKAGE__.": The order and targetOrder settings for LUT must\n".
            "only contain digits.\nError found in ".$this->name."\n");
    }
}

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

    my @nmers = 
        $this->seqClass->getAllSequences($this->order+1, $this->ambiguate);
    for my $nmer (@nmers) {
        $this->{posCounts_}->{$nmer} = 0.;
        $this->{nullCounts_}->{$nmer} = 0.;
        $this->{scores_}->{$nmer} = 0.;
    }
}

sub posCounts   { shift->{posCounts_}               }
sub nullCounts  { shift->{nullCounts_}              }
sub scores      { shift->{scores_}                  }
sub order       { shift->settings->{order}         }
sub targetOrder { shift->settings->{targetOrder}   }

sub countRegion     { 
    my ($this, $region) = @_;
    if($region->seq->loaded) { _count(@_, 0)         }
    else                     { _countUnloaded(@_, 0) }
}
sub countNullRegion { 
    my ($this, $region) = @_;
    if($region->seq->loaded) { _count(@_, 1)         }
    else                     { _countUnloaded(@_, 1) }
}

sub _count {
    my ($this, $region, $null) = @_;

    my $buck;
    if($null)   { $buck = $this->nullCounts }
    else        { $buck = $this->posCounts  }

    #optimization
    my $order = $this->order;
    my $weight = $region->weight;
    my $end = $region->end;
    my $strRef = $region->strRef;

    my $str;
    for (my $pos = $region->start+$order; $pos <= $end; $pos++) {
        $str = substr($$strRef, $pos-$order, $order+1);
        $buck->{$str} += $weight;
    }
}

sub _countUnloaded {
    my ($this, $region, $null) = @_;

    my $buck;
    if($null)   { $buck = $this->nullCounts }
    else        { $buck = $this->posCounts  }

    #optimization
    my $order = $this->order;
    my $targetOrder = $this->targetOrder;
    my $weight = $region->weight;
    my $end = $region->end;
    my $strRef = $region->strRef;

    my $strand = $region->strand;
    my $seq = $region->seq;
    my $str;
    for (my $pos = $region->start+$order; $pos <= $end; $pos++) {
        $str = $seq->getContext($strand, $pos, $order, $targetOrder);
        $buck->{$str} += $weight;
    }
}

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

    if($this->ambiguate &&
            $this->wildcard == iPE::Model::Emission::LEXICAL()) { 
        $this->lexicalAmbiguateMarkovChain($this->posCounts, $this->order);
        $this->lexicalAmbiguateMarkovChain($this->nullCounts, $this->order) 
            if ($this->nullModel);
    }

    $this->pseudocountSmoother->smoothHref($this->posCounts);
    $this->pseudocountSmoother->smoothHref($this->nullCounts) 
        if($this->nullModel);
    $this->smoother->smoothHref($this->posCounts);
    $this->smoother->smoothHref($this->nullCounts) if($this->nullModel);
}

sub normalize {
    my ($this) = @_;
    
    my @nmers = $this->seqClass->getAllSequences($this->order,$this->ambiguate);
    #whether or not we have a null model.
    my $nullModel = $this->nullModel;
    my $wildCard = $this->seqClass->getWildCard();
    my @alphabet = @{$this->seqClass->getAlphabet};
    push @alphabet, $this->seqClass->getWildCard 
        if($this->ambiguate && $this->wildcard == iPE::Model::Emission::LITERAL());
    for my $nmer (@nmers) {
        my $totCounts = 0;
        my $totNullCounts = 0;
        for my $l (@alphabet) {
            $totCounts += $this->posCounts->{$nmer.$l};
            $totNullCounts += $this->nullCounts->{$nmer.$l} if($nullModel);
        }
        for my $l (@alphabet) {
            $this->posCounts->{$nmer.$l} /= $totCounts
                if($totCounts);
            $this->nullCounts->{$nmer.$l} /= $totNullCounts 
                if($totNullCounts && $nullModel);
        }
        if($this->ambiguate && 
                    $this->wildcard == iPE::Model::Emission::LEXICAL()) { 
            $this->posCounts->{$nmer.$wildCard} = 1;
            $this->nullCounts->{$nmer.$wildCard} = 1;
        }
    }
}

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

    my $g = new iPE::Globals();
    my @nmers = 
        $this->seqClass->getAllSequences($this->order+1, $this->ambiguate);
    my $scale = $g->options->scaleFactor;
    my $negInf = $g->options->sequenceNegInf;
    my $nullModel = $this->nullModel;

    for my $nmer (@nmers) {
        if($nullModel) {
            $this->{scores_}->{$nmer} = 
                $this->logScore($this->{posCounts_}->{$nmer},
                    $this->{nullCounts_}->{$nmer});
        }
        else {
            $this->{scores_}->{$nmer} = 
                $this->logScore($this->{posCounts_}->{$nmer});
        }
    }
    if($this->ambiguate && 
            $this->wildcard == iPE::Model::Emission::PENALTY()) {
        $this->penalizeAmbiguousNmers($this->{scores_}, $this->order);
    }
}

sub outputPrepare {
    my ($this, $out, $mode) = @_;
    my $pstring = "";
    my $word_type = ref($this).$this->name;
    my @alphabet = @{$this->seqClass->getAlphabet};
    push @alphabet, $this->seqClass->getWildCard if($this->ambiguate);

    for my $nmer 
            ($this->seqClass->getAllSequences($this->order, $this->ambiguate)) {
        $pstring .= $nmer.$out->tab if($mode ne "score"); 
        for my $l (@alphabet) {
            if($mode eq "count" || $mode eq "prob") {
                $pstring .= 
                    $out->floatf($this->posCounts->{$nmer.$l}); 
                if($this->nullModel) {
                    $pstring .= " | ". 
                        $out->floatf($this->nullCounts->{$nmer.$l}); 
                }
                $pstring .= $out->tab;
            }
            elsif($mode eq "score") {
                $pstring .= $out->intf($this->scores->{$nmer.$l}).$out->tab;
            }
        }
        $pstring .= "\n";
    }

    $this->setParamString($pstring);
}

=head1 SEE ALSO

L<iPE::Model::Emission>

=head1 AUTHOR

Bob Zimmermann (rpz@cse.wustl.edu)

=cut

1;
