################################################################################
#   Gene prediction pipeline 
#
#   $Id: Prediction.py 2781 2009-09-10 11:33:14Z andreas $
#
#   Copyright (C) 2004 Andreas Heger
#
#   This program is free software; you can redistribute it and/or
#   modify it under the terms of the GNU General Public License
#   as published by the Free Software Foundation; either version 2
#   of the License, or (at your option) any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with this program; if not, write to the Free Software
#   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#################################################################################
"""
Prediction.py - Gene prediction with exonerate/genewise
========================================================

:Author: 
:Release: $Id$
:Date: |today|
:Tags: Python

"""
import os, sys, string, re, getopt, tempfile, copy

import Genomics
import alignlib

## number of nucleotides to ignore for counting stop-codons
## This used to be three, but is now set to 0 in order to keep
## accounting for stop codons consistent with the disruptions
## table.
GlobalBorderStopCodons = 0
GlobalStopCodons = ("TAG", "TAA", "TGA")

class Error(Exception):
    """Base class for exceptions in this module."""
    def __str__(self):
        return str(self.message)
    def _get_message(self, message): return self._message
    def _set_message(self, message): self._message = message
    message = property(_get_message, _set_message)

class InputError(Error):
    """Exception raised for errors in the input.

    Attributes:
        expression -- input expression in which the error occurred
        message -- explanation of the error
    """

    def __init__(self, message):
        self.message = message

class AlignmentError(Error):
    """Exception raised by inconsistent alignments.

    Attributes:
        expression -- input expression in which the error occurred
        message -- explanation of the error
    """

    def __init__(self, message):
        self.message = message


class Prediction:
    
    def __init__(self, expand = 1):

        self.mExpand = expand
        
        self.mPredictionId = 0
        self.mQueryToken = 0
        self.mQueryFrom = 0
        self.mQueryTo = 0
        self.mSbjctToken = 0
        self.mSbjctStrand = 0
        self.mSbjctFrom = 0
        self.mSbjctTo = 0
        self.mRank = 0
        self.score = 0
        self.mQueryLength = 0
        self.mQueryCoverage = 0
        self.mNGaps = 0
        self.mNFrameShifts = 0
        self.mNIntrons = 0
        self.mNSplits = 0
        self.mNStopCodons = 0
        self.mPercentIdentity = 0
        self.mPercentSimilarity = 0
        self.mTranslation = ""
        self.mSbjctGenomeFrom = 0
        self.mSbjctGenomeTo = 0
        self.mAlignmentString = ""
        self.mQueryAli = ""
        self.mSbjctAli = ""
        
        if self.mExpand:
            self.mMapPeptide2Translation = alignlib.makeAlignmentVector()
            self.mMapPeptide2Genome = []
        else:
            self.mMapPeptide2Translation = None
            self.mMapPeptide2Genome = None
        self.mNAssembled = 0

    def __del__(self):
        if self.mExpand:
            self.mMapPeptide2Translation.clear()
            self.mMapPeptide2Genome = []
        self.mTranslation = ""
        self.mAlignmentString = ""
        
    def __eq__(self, other ):

        return self.mQueryToken == other.mQueryToken and \
               self.mSbjctToken == other.mSbjctToken and \
               self.mSbjctStrand == other.mSbjctStrand and \
               self.score == other.score and \
               self.mQueryFrom == other.mQueryFrom and \
               self.mQueryTo == other.mQueryTo and \
               self.mSbjctGenomeFrom == other.mSbjctGenomeFrom and \
               self.mSbjctGenomeTo == other.mSbjctGenomeTo and \
               self.mAlignmentString == other.mAlignmentString 

    def cleanAlignmentInfo( self ):
        """remove alignment information (to save space)."""
        self.mTranslation, self.mAlignmentString, self.mQueryAli, self.mSbjctAli = None, None, None, None
        
    def getCopy( self ):
        """return a new copy.
        """

        new_entry = Prediction()

        new_entry.mExpand = self.mExpand 
        
        new_entry.mPredictionId = self.mPredictionId 
        new_entry.mQueryToken = self.mQueryToken 
        new_entry.mQueryFrom = self.mQueryFrom 
        new_entry.mQueryTo = self.mQueryTo 
        new_entry.mSbjctToken = self.mSbjctToken 
        new_entry.mSbjctStrand = self.mSbjctStrand 
        new_entry.mSbjctFrom = self.mSbjctFrom 
        new_entry.mSbjctTo = self.mSbjctTo 
        new_entry.mRank = self.mRank 
        new_entry.score = self.score 
        new_entry.mQueryLength = self.mQueryLength 
        new_entry.mQueryCoverage = self.mQueryCoverage 
        new_entry.mNGaps = self.mNGaps 
        new_entry.mNFrameShifts = self.mNFrameShifts 
        new_entry.mNIntrons = self.mNIntrons 
        new_entry.mNSplits = self.mNSplits 
        new_entry.mNStopCodons = self.mNStopCodons 
        new_entry.mPercentIdentity = self.mPercentIdentity 
        new_entry.mPercentSimilarity = self.mPercentSimilarity 
        new_entry.mTranslation = self.mTranslation 
        new_entry.mSbjctGenomeFrom = self.mSbjctGenomeFrom 
        new_entry.mSbjctGenomeTo = self.mSbjctGenomeTo 
        new_entry.mAlignmentString = self.mAlignmentString 
        new_entry.mQueryAli = self.mQueryAli 
        new_entry.mSbjctAli = self.mSbjctAli 

        if self.mExpand:
            new_entry.mMapPeptide2Translation = alignlib.makeAlignmentVector()
            alignlib.copyAlignment( new_entry.mMapPeptide2Translation, self.mMapPeptide2Translation)
            new_entry.mMapPeptide2Genome = Genomics.String2Alignment( new_entry.mAlignmentString) 
        else:
            new_entry.mMapPeptide2Translation = self.mMapPeptide2Translation = None
            new_entry.mMapPeptide2Genome = self.mMapPeptide2Genome = None

        return new_entry

    def Expand( self ):
        self.mExpand = True
        
        if self.mMapPeptide2Translation.getLength() > 0:
            f = alignlib.AlignmentFormatEmissions( self.mMapPeptide2Translation )
            self.mQueryAli, self.mSbjctAli = f.mRowAlignment, f.mColAlignment
            self.mQueryFrom = self.mMapPeptide2Translation.getRowFrom()
            self.mQueryTo = self.mMapPeptide2Translation.getRowTo()
            self.mSbjctFrom = self.mMapPeptide2Translation.getColFrom()
            self.mSbjctTo = self.mMapPeptide2Translation.getColTo()

        self.mMapPeptide2Genome = Genomics.String2Alignment( self.mAlignmentString )

    def Add( self, const_other,
             combine_contig = False,
             allow_overlap = False,
             contig_size = 0,
             combine_queries = False,
             as_intron = False ):
        """add one entry to another.

        This procedure allows to add
        
        - predictions on different contigs if combine_contig = True
        - overlapping predictions on the same query if allow_overlap = True
        - results from different queries if combine_queries = True

        - if as_intron is set to true, the new fragment is added as an intron.
        
        """

        ## create working copies of each prediction
        other = const_other.getCopy()
        this  = self.getCopy()

        other.Expand()
        this.Expand()

        if as_intron:
            code = "I"
        else:
            code = "P"

        ## check for query overlaps
        if this.mQueryToken == other.mQueryToken:

            query_overlap = max( 0, min(this.mQueryTo, other.mQueryTo) -\
                                 max(this.mQueryFrom, other.mQueryFrom) + 1)

            if query_overlap > 0:

                if allow_overlap:
                    overlap = query_overlap
                    ## if queries overlap, truncate this before adding the other
                    this.mMapPeptide2Translation.removeRowRegion( this.mQueryTo - overlap + 1, this.mQueryTo )
                    other.mMapPeptide2Translation.moveAlignment( 0, -overlap )
                    this.mQueryTo -= overlap
                    this.mTranslation = this.mTranslation[:-overlap]

                    ## remove aligned residues from the back
                    for x in range(len(this.mMapPeptide2Genome) - 1, 0, -1):
                        if this.mMapPeptide2Genome[x][1] <= overlap:
                            overlap -= this.mMapPeptide2Genome[x][1]
                            del this.mMapPeptide2Genome[x]
                        else:
                            break
                    this.mMapPeptide2Genome[-1] = (this.mMapPeptide2Genome[-1][0],
                                                   this.mMapPeptide2Genome[-1][1] - overlap,
                                                   this.mMapPeptide2Genome[-1][2] - overlap * 3)
                else:
                    raise ValueError, "refusing to add overlapping entries: overlap = %i, queries:\n%s\n%s\n, set allow_overlap = True " % (query_overlap, str(this), str(other))


        else:
            if not combine_queries:
                raise ValueError, "refusing to add different queries - set combine_queries = True."

        if this.mSbjctToken != other.mSbjctToken or \
               this.mSbjctStrand != other.mSbjctStrand :
            if combine_contig:
                this.mSbjctToken += "-" + other.mSbjctToken
                this.mSbjctStrand += other.mSbjctStrand
            else:
                raise ValueError, "can not add different sbjct."                

        sbjct_overlap = max(0, min(this.mSbjctGenomeTo, other.mSbjctGenomeTo) -\
                            max(this.mSbjctGenomeFrom, other.mSbjctGenomeFrom), 0)

        if sbjct_overlap > 0:
            if not combine_contig:
                raise ValueError, "refusing to add overlapping entries: overlap = %i, sbjct:\n%s\n%s\n" % (sbjct_overlap, str(this), str(other))

        if this.mSbjctToken == other.mSbjctToken:

            ## set precedence
            if this.mSbjctGenomeFrom < other.mSbjctGenomeFrom:
                first = this
                second = other
            else:
                first = other
                second = this

            ## get length of gap
            d_na = second.mSbjctGenomeFrom - first.mSbjctGenomeTo

            if this.mQueryToken != other.mQueryToken:
                d_aa = first.mQueryLength - first.mQueryTo                 
                # create a new virtual query by concatenating
                # the two queries
                this.mQueryToken += "-" + other.mQueryToken

                # sort out the alignment
                second.mMapPeptide2Translation.moveAlignment( first.mQueryLength, 0 )

                this.mQueryLength = first.mQueryLength + second.mQueryLength

            else:
                d_aa = second.mQueryFrom - first.mQueryTo - 1
            
            this.mSbjctGenomeFrom = min(this.mSbjctGenomeFrom, other.mSbjctGenomeFrom )
            this.mSbjctGenomeTo = max(this.mSbjctGenomeTo, other.mSbjctGenomeTo )        

            this.mMapPeptide2Genome = first.mMapPeptide2Genome + [(code, d_aa, d_na)] + second.mMapPeptide2Genome
            this.mTranslation = first.mTranslation + second.mTranslation

            second.mMapPeptide2Translation.moveAlignment( 0, first.mSbjctTo - 1 )
            
        else:
            ## join on different contigs
            d_na = contig_size - this.mSbjctGenomeTo + other.mSbjctGenomeFrom + query_overlap * 3
            d_aa = other.mQueryFrom - this.mQueryTo - 1
            this.mMapPeptide2Genome += [(code, d_aa, d_na),] + other.mMapPeptide2Genome
            this.mTranslation += other.mTranslation 
            other.mMapPeptide2Translation.moveAlignment( 0, this.mSbjctTo - 1 )

            this.mSbjctGenomeFrom = this.mSbjctGenomeFrom
            this.mSbjctGenomeTo = contig_size + other.mSbjctGenomeTo

        ## now fill self from first and this
        self.mQueryToken = first.mQueryToken
        self.mQueryLength = this.mQueryLength
        
        nthis  = this.mMapPeptide2Translation.getLength() - this.mMapPeptide2Translation.getNumGaps()
        nother = other.mMapPeptide2Translation.getLength() - other.mMapPeptide2Translation.getNumGaps()

        self.mMapPeptide2Genome = first.mMapPeptide2Genome
        self.mSbjctGenomeFrom = this.mSbjctGenomeFrom
        self.mSbjctGenomeTo= this.mSbjctGenomeTo
        
        ## there might be some reference counting issues, thus
        ## do it the explicit way.
        alignlib.addAlignment2Alignment( this.mMapPeptide2Translation, other.mMapPeptide2Translation)
        self.mMapPeptide2Translation = alignlib.makeAlignmentVector()
        alignlib.addAlignment2Alignment( self.mMapPeptide2Translation, this.mMapPeptide2Translation )
        
        self.mTranslation = this.mTranslation
        
        self.mQueryFrom = self.mMapPeptide2Translation.getRowFrom()
        self.mQueryTo = self.mMapPeptide2Translation.getRowTo()
        self.mSbjctFrom = self.mMapPeptide2Translation.getColFrom()
        self.mSbjctTo = self.mMapPeptide2Translation.getColTo()
        
        self.mQueryCoverage = 100.0 * (self.mQueryTo - self.mQueryFrom + 1) / float(self.mQueryLength)

        self.mAlignmentString = string.join( map( \
                                      lambda x: string.join(map(str, x), " "),
                                      self.mMapPeptide2Genome), " ")

        f = alignlib.AlignmentFormatEmssions( self.mMapPeptide2Translation )
        self.mQueryAli, self.mSbjctAli = f.mRowAlignment, f.mColAlignment

        ## summary parameters
        self.mRank = max( this.mRank, other.mRank)
        self.score += other.score
        self.mNGaps += other.mNGaps
        self.mNFrameShifts += other.mNFrameShifts
        self.mNIntrons += other.mNIntrons + 1
        self.mNStopCodons += other.mNStopCodons
        
        nnew = self.mMapPeptide2Translation.getLength() - self.mMapPeptide2Translation.getNumGaps()
        
        self.mPercentIdentity = min( 100.0, (self.mPercentIdentity * nthis + other.mPercentIdentity * nother) / nnew )
        self.mPercentSimilarity = min( 100.0, (self.mPercentSimilarity * nthis + other.mPercentSimilarity * nother) / nnew )

        self.mNAssembled += 1 + other.mNAssembled

    def InvertGenomicCoordinates( self, lgenome ):
        """invert genomic alignment on sequence.

        Negative strand is calculated from the other end.
        """
        if self.mSbjctStrand == "-":
            x = self.mSbjctGenomeFrom 
            self.mSbjctGenomeFrom = lgenome - self.mSbjctGenomeTo
            self.mSbjctGenomeTo   = lgenome - x
        
    def shiftGenomicRegion( self, forward_offset, reverse_offset = 0 ):
        """shift aligned genomic region by offset.

        if reverse offset is given, negative strand predictions
        are shifted by this amount.
        """
        if reverse_offset:
            if self.mSbjctStrand == "+":
                self.mSbjctGenomeFrom += forward_offset
                self.mSbjctGenomeTo += forward_offset
            else:
                self.mSbjctGenomeFrom += reverse_offset                
                self.mSbjctGenomeTo += reverse_offset
        else:
            self.mSbjctGenomeFrom += forward_offset
            self.mSbjctGenomeTo += forward_offset

    def shiftQueryRegion( self, offset ):
        """shift aligned genomic region by offset."""
        self.mQueryFrom += offset
        self.mQueryTo += offset
        if self.mExpand:
            self.mMapPeptide2Translation.moveAlignment( offset, 0)

    def setStrand( self, strand ):
        """set strand."""
        self.mSbjctStrand = strand 

    def getHeader(self):
        """return header."""
        return "\t".join( ("id",
                          "query_token",
                          "sbjct_token",
                          "sbjct_strand",
                          "rank",
                          "score",
                          "query_from",
                          "query_to",
                          "query_ali",
                          "sbjct_from",
                          "sbjct_to",
                          "sbjct_ali",
                          "query_length",
                          "query_coverage",
                          "ngaps",
                          "nframeshifts",
                          "nintrons",
                          "nsplits",
                          "nstops",
                          "pidentity",
                          "psimilarity",
                          "translation",
                          "sbjct_genome_from",
                          "sbjct_genome_to",
                          "map_peptide2genome",
                          "nassembled" ) )
    def __str__( self ):
        """get a string representation of results."""

            
        if self.mExpand:
            if self.mMapPeptide2Translation.getLength() > 0:
                f = alignlib.AlignmentFormatEmissions( self.mMapPeptide2Translation )
                row_ali, col_ali = f.mRowAlignment, f.mColAlignment
                self.mQueryFrom = self.mMapPeptide2Translation.getRowFrom()
                self.mQueryTo = self.mMapPeptide2Translation.getRowTo()
                self.mSbjctFrom = self.mMapPeptide2Translation.getColFrom()
                self.mSbjctTo = self.mMapPeptide2Translation.getColTo()
            else:
                row_ali, col_ali = "", ""
        else:
            row_ali = self.mQueryAli
            col_ali = self.mSbjctAli
            
        if self.mPredictionId:
            return string.join( map(str, (\
                self.mPredictionId,
                self.mQueryToken,
                self.mSbjctToken,
                self.mSbjctStrand,
                self.mRank,
                self.score,
                self.mQueryFrom,
                self.mQueryTo,
                row_ali,
                self.mSbjctFrom,
                self.mSbjctTo,
                col_ali,
                self.mQueryLength,
                self.mQueryCoverage, 
                self.mNGaps,
                self.mNFrameShifts,
                self.mNIntrons,
                self.mNSplits,
                self.mNStopCodons,
                "%5.2f" % self.mPercentIdentity,
                "%5.2f" % self.mPercentSimilarity,
                self.mTranslation,
                self.mSbjctGenomeFrom,
                self.mSbjctGenomeTo,
                self.mAlignmentString,
                self.mNAssembled,
                )), "\t")
        else:
            return string.join( map(str, (\
              self.mQueryToken,
              self.mSbjctToken,
              self.mSbjctStrand,
              self.mRank,
              self.score,
              self.mQueryFrom,
              self.mQueryTo,
              row_ali,
              self.mSbjctFrom,
              self.mSbjctTo,
              col_ali,
              self.mQueryLength,
              self.mQueryCoverage, 
              self.mNGaps,
              self.mNFrameShifts,
              self.mNIntrons,
              self.mNSplits,
              self.mNStopCodons,
                "%5.2f" % self.mPercentIdentity,
                "%5.2f" % self.mPercentSimilarity,
              self.mTranslation,
              self.mSbjctGenomeFrom,
              self.mSbjctGenomeTo,
              self.mAlignmentString,
              self.mNAssembled,
              )), "\t")

    def fillFromTable( self, table_row ):

        if len(table_row) == 25:
            ( self.mPredictionId,
              self.mQueryToken, self.mSbjctToken, self.mSbjctStrand,
              self.mRank, self.score,
              self.mQueryFrom, self.mQueryTo, self.mQueryAli,
              self.mSbjctFrom, self.mSbjctTo, self.mSbjctAli,
              self.mQueryLength, self.mQueryCoverage, 
              self.mNGaps, self.mNFrameShifts, self.mNIntrons,
              self.mNSplits, self.mNStopCodons,
              self.mPercentIdentity, self.mPercentSimilarity,
              self.mTranslation,
              self.mSbjctGenomeFrom, self.mSbjctGenomeTo,
              self.mAlignmentString) = table_row
        elif len(table_row) == 26:
            ( self.mPredictionId,
              self.mQueryToken, self.mSbjctToken, self.mSbjctStrand,
              self.mRank, self.score,
              self.mQueryFrom, self.mQueryTo, self.mQueryAli,
              self.mSbjctFrom, self.mSbjctTo, self.mSbjctAli,
              self.mQueryLength, self.mQueryCoverage, 
              self.mNGaps, self.mNFrameShifts, self.mNIntrons,
              self.mNSplits, self.mNStopCodons,
              self.mPercentIdentity, self.mPercentSimilarity,
              self.mTranslation,
              self.mSbjctGenomeFrom, self.mSbjctGenomeTo,
              self.mAlignmentString,
              self.mNAssembled) = table_row[:26]
        elif len(table_row) > 26:
            ( self.mPredictionId,
              self.mQueryToken, self.mSbjctToken, self.mSbjctStrand,
              self.mRank, self.score,
              self.mQueryFrom, self.mQueryTo, self.mQueryAli,
              self.mSbjctFrom, self.mSbjctTo, self.mSbjctAli,
              self.mQueryLength, self.mQueryCoverage, 
              self.mNGaps, self.mNFrameShifts, self.mNIntrons,
              self.mNSplits, self.mNStopCodons,
              self.mPercentIdentity, self.mPercentSimilarity,
              self.mTranslation,
              self.mSbjctGenomeFrom, self.mSbjctGenomeTo,
              self.mAlignmentString,
              self.mNAssembled) = table_row[:26]
        else:
            raise ValueError, "unknown format: %i fields" % len(data)
            sys.exit(0)
            
        if self.mExpand:
            self.mMapPeptide2Translation = alignlib.makeAlignmentVector()

            if self.mQueryAli != "" and self.mSbjctAli != "":
                alignlib.AlignmentFormatEmissions( self.mQueryFrom, self.mQueryAli,
                                                   self.mSbjctFrom, self.mSbjctAli ).copy( self.mMapPeptide2Translation )

            self.mMapPeptide2Genome = Genomics.String2Alignment( self.mAlignmentString )

    def setTranslation( self, genomic_sequence ):
        """set translation from genomic sequence."""
        self.mMapPeptide2Translation, self.mTranslation = Genomics.Alignment2PeptideAlignment( \
                self.mMapPeptide2Genome, self.mQueryFrom, self.mSbjctGenomeFrom, genomic_sequence )
        
    def getExonBoundariesOnGenome( self ):
        """return a list of exons for this prediction.
        """
        return map( lambda x: (x[3], x[4]),
                    Genomics.Alignment2ExonBoundaries( self.mMapPeptide2Genome,
                                                       self.mQueryFrom,
                                                       self.mSbjctGenomeFrom ))
        
    def write( self ):
        """write a tab separated list of results.
        """
        print str(self)
        
    def read( self, line ):

        data = string.split( line[:-1], "\t")
        
        if len(data) == 26:
            ( self.mPredictionId,
              self.mQueryToken, self.mSbjctToken, self.mSbjctStrand,
              self.mRank, self.score,
              self.mQueryFrom, self.mQueryTo, self.mQueryAli,
              self.mSbjctFrom, self.mSbjctTo, self.mSbjctAli,
              self.mQueryLength, self.mQueryCoverage, 
              self.mNGaps, self.mNFrameShifts, self.mNIntrons,
              self.mNSplits, self.mNStopCodons,
              self.mPercentIdentity, self.mPercentSimilarity,
              self.mTranslation,
              self.mSbjctGenomeFrom, self.mSbjctGenomeTo,
              self.mAlignmentString, self.mNAssembled,
              ) = data
        elif len(data) == 25:
            ( self.mPredictionId,
              self.mQueryToken, self.mSbjctToken, self.mSbjctStrand,
              self.mRank, self.score,
              self.mQueryFrom, self.mQueryTo, self.mQueryAli,
              self.mSbjctFrom, self.mSbjctTo, self.mSbjctAli,
              self.mQueryLength, self.mQueryCoverage, 
              self.mNGaps, self.mNFrameShifts, self.mNIntrons,
              self.mNSplits, self.mNStopCodons,
              self.mPercentIdentity, self.mPercentSimilarity,
              self.mTranslation,
              self.mSbjctGenomeFrom, self.mSbjctGenomeTo,
              self.mAlignmentString,
              ) = data
        elif len(data) == 24:            
            ( self.mQueryToken, self.mSbjctToken, self.mSbjctStrand,
              self.mRank, self.score,
              self.mQueryFrom, self.mQueryTo, self.mQueryAli,
              self.mSbjctFrom, self.mSbjctTo, self.mSbjctAli,
              self.mQueryLength, self.mQueryCoverage, 
              self.mNGaps, self.mNFrameShifts, self.mNIntrons,
              self.mNSplits, self.mNStopCodons,
              self.mPercentIdentity, self.mPercentSimilarity,
              self.mTranslation,
              self.mSbjctGenomeFrom, self.mSbjctGenomeTo,
              self.mAlignmentString,
              ) = data
        elif len(data) == 23:
            ( self.mQueryToken, self.mSbjctToken, self.mSbjctStrand,
              self.mRank, self.score,
              self.mQueryFrom, self.mQueryTo, self.mQueryAli,
              self.mSbjctFrom, self.mSbjctTo, self.mSbjctAli,
              self.mQueryLength, self.mQueryCoverage, 
              self.mNGaps, self.mNFrameShifts, self.mNIntrons,
              self.mNSplits, self.mNStopCodons,
              self.mPercentIdentity, self.mPercentSimilarity,
              self.mTranslation,
              self.mSbjctGenomeFrom, self.mSbjctGenomeTo,
              ) = data
            self.mAlignmentString = ""
        else:
            raise ValueError, "unknown format: %i fields in line %s" % (len(data), line[:-1])

        (self.score, self.mQueryCoverage, self.mPercentIdentity, self.mPercentSimilarity) = map (\
            float, (self.score, self.mQueryCoverage, self.mPercentIdentity, self.mPercentSimilarity))

        (self.mPredictionId, 
         self.mQueryFrom, self.mQueryTo, self.mQueryLength,
         self.mSbjctFrom, self.mSbjctTo,
         self.mSbjctGenomeFrom, self.mSbjctGenomeTo,
         self.mNGaps, self.mNIntrons, self.mNSplits, self.mNStopCodons,
         self.mNFrameShifts, self.mNAssembled) = map (\
            int, ( self.mPredictionId,
                   self.mQueryFrom, self.mQueryTo, self.mQueryLength,
                   self.mSbjctFrom, self.mSbjctTo,
                   self.mSbjctGenomeFrom, self.mSbjctGenomeTo,
                   self.mNGaps, self.mNIntrons, self.mNSplits, self.mNStopCodons,
                   self.mNFrameShifts, self.mNAssembled))

        if self.mExpand:        
            self.mMapPeptide2Translation = alignlib.makeAlignmentVector()

            if self.mQueryAli != "" and self.mSbjctAli != "":
                
                alignlib.AlignmentFormatExplicit(
                    self.mQueryFrom, self.mQueryAli,
                    self.mSbjctFrom, self.mSbjctAli).copy( self.mMapPeptide2Translation )

            self.mMapPeptide2Genome = Genomics.String2Alignment( self.mAlignmentString )

##--------------------------------------------------------------------------
class Predictions :
    mLogLevel = 0

    def __init__( self ):
        self.mMatches = []

    def __eq__(self, other):
        if not other: return 0        

        if not other.mMatches: return 0
        if not self.mMatches: return 0
        return self.mMatches[0] == other.mMatches[0]

    def __getitem__(self, key ):
        return self.mMatches[key]

    def __setitem__(self, key, value ):
        self.mMatches[key] = value

    def __len__(self):
        return len(self.mMatches)

    def __str__(self):
        return string.join( map(str, self.mMatches), "\n")

    def append( self, entry ):
        return self.mMatches.append( entry )
    
    def clear( self ):
        for x in self.mMatches:
            x.__del__()
        self.mMatches = []
    
    def combine(self, other):
        self.mMatches += other.mMatches

    def add( self, prediction ):
        """prediction to predictions."""
        self.mMatches.append(prediction)

    def reRank( self, offset = 1 ):
        """rerank predictions based on current sorting order."""
        
        for x in range(len(self.mMatches)):
            self.mMatches[x].mRank = offset + x

    def getBestMatch( self ):
        self.sort( lambda x,y: cmp(-x.score, -y.score)  )
        return self.mMatches[0]
        
    def getNumMatches( self ):
        """return number of matches."""
        return len(self.mMatches)

    def shiftGenomicRegion( self, forward_offset, reverse_offset = 0):
        """shift aligned genomic region by offset for all entries.

        if reverse offset is given, negative strand predictions
        are shifted by this amount.
        """
        for x in self.mMatches:
            x.shiftGenomicRegion( forward_offset, reverse_offset)

    def shiftQueryRegion( self, offset ):
        """shift aligned genomic region by offset for all entries."""
        for x in self.mMatches:
            x.shiftQueryRegion( offset )

    def setStrand( self, strand ):
        """set strand."""
        for x in self.mMatches:
            x.setStrand( strand )
    
    def write( self ):
        """write a tab separated list of results.
        """
        for x in self.mMatches:
            x.write()

    def sort( self, cmp_function = None ):
        """sort predictions.
        """
        self.mMatches.sort( cmp_function )

    def getRange( self ):
        """return a prediction covering all predictions."""

        p = self.mMatches[0].getCopy()

        p.mExpand = False
        
        if len(self.mMatches) == 1: return p
        
        for pp in self.mMatches[1:]:

            p.mQueryFrom       = min(p.mQueryFrom      , pp.mQueryFrom )
            p.mQueryTo         = max(p.mQueryTo        , pp.mQueryTo )
            p.mSbjctGenomeFrom = min(p.mSbjctGenomeFrom, pp.mSbjctGenomeFrom )
            p.mSbjctGenomeTo   = max(p.mSbjctGenomeTo  , pp.mSbjctGenomeTo )
            p.score += pp.score

        return p


def iterate( infile ):
    '''iterate over predicions in infile.'''

    for line in infile:
        if line.startswith("#"): continue
        if line.startswith("id\tquery_token"): continue
        p = Prediction()
        p.read( line )
        yield p
        

    
