ref_align - Method for doing targeted alignments on partial annotations

        :param found_seqs: The input sequence record.
        :type found_seqs: Seq
        :param sequence: The input sequence record.
        :type sequence: Seq
        :param locus: The gene locus associated with the sequence.
        :type locus: ``str``
        :param annotation: The incomplete annotation from a previous iteration.
        :type annotation: :ref:`ann`
        :param partial_ann: The partial annotation after looping through all of the blast sequences.
        :type partial_ann: :ref:`ann`
        :rtype: :ref:`ann`

def ref_align(self, found_seqs, sequence: Seq=None,
                  locus: str=None, annotation: Annotation=None,
                  partial_ann: Annotation=None,
                  run: int=0,
                  cutoff: float=.90) -> Annotation:
        """
        ref_align - Method for doing targeted alignments on partial annotations

        :param found_seqs: The input sequence record.
        :type found_seqs: Seq
        :param sequence: The input sequence record.
        :type sequence: Seq
        :param locus: The gene locus associated with the sequence.
        :type locus: ``str``
        :param annotation: The incomplete annotation from a previous iteration.
        :type annotation: :ref:`ann`
        :param partial_ann: The partial annotation after looping through all of the blast sequences.
        :type partial_ann: :ref:`ann`
        :rtype: :ref:`ann`

        """
        if annotation and isinstance(annotation, Annotation):
            if 0 in annotation.mapping \
                    and not isinstance(annotation.mapping[0], int):
                ft = annotation.mapping[0]
                start_order = self.refdata.structures[locus][ft]
            else:
                start_order = 0

            # Check whether it's exons only
            exon_only = True
            if hasattr(annotation, 'annotation') and annotation.annotation:
                for f in annotation.annotation:
                    if re.search("intron", f) or re.search("UTR", f):
                        exon_only = False
            elif(len(sequence.seq) > 900):
                exon_only = False

            annoated = []
            if hasattr(annotation, 'annotation') and annotation.annotation:
                annoated = list(annotation.annotation.keys())

            # Extract the missing blocks and
            # only align those blocks to the known
            # missing features
            # Start with all blocks missing
            # and then delete block if it is found
            tmp_missing = []
            missing_blocks = annotation.blocks
            for b in sorted(annotation.blocks):
                # **** Check if block equals full input sequence *** #
                # - If it does, then just align the ful

                start = b[0]-1 if b[0] != 0 else 0
                seq_feat = \
                    SeqFeature(
                        FeatureLocation(
                            ExactPosition(start),
                            ExactPosition(b[len(b)-1]),
                            strand=1),
                        type="unmapped")

                feat = seq_feat.extract(annotation.seq)
                combosrecs, exons, fullrec = self._refseqs(locus,
                                                           start,
                                                           annotation,
                                                           feat,
                                                           b)

                # Print out different sequence types being align
                if self.verbose and self.verbosity > 3:
                    for combseqr in combosrecs:
                        self.logger.info(self.logname + " " + combseqr.id)

                ic = 0

                # Looping through the generated combos
                # from the reference sequence. (ex. intron1-exon1)
                for combseqr in combosrecs:
                    if self.verbose:
                        self.logger.info(self.logname + " aligning ->" + combseqr.id)
                    
                    # Running the alignment
                    an, ins, dels = align_seqs(combseqr, feat, locus,
                                               start,
                                               annotation.missing,
                                               len(annoated),
                                               cutoff=cutoff,
                                               verbose=self.align_verbose,
                                               verbosity=self.align_verbosity)

                    # Checking if any features were mapped
                    mapped_feat = list(an.annotation.keys())
                    if len(mapped_feat) >= 1:

                        # loop through the annotated features
                        for f in an.annotation:
                            f_order = self.refdata.structures[locus][f]

                            # Only map features if they are in the correct order
                            if f in annotation.missing \
                                    and f_order >= start_order:

                                # * HARD CODED LOGIC *
                                # Only accept the alignments if they are
                                # somewhat similar to what's been observed already
                                length, lengthsd = 0, 0
                                length = float(self.refdata.feature_lengths[locus][f][0])
                                lengthsd = float(self.refdata.feature_lengths[locus][f][1])

                                incr = 3 if not is_classII(locus) else 4

                                max_length = length + (lengthsd*incr) + ins
                                min_length = length - (lengthsd*incr) - dels

                                if f == "exon_8" and not is_classII(f):
                                    max_length = 10

                                # Check ordering when it's only exon sequences
                                if exon_only:
                                    f_order = self.refdata.structures[locus][f]
                                    endp = an.features[f].location.end + 1

                                    #Make sure order of alignment make sense
                                    if an.features[f].location.start == 0 \
                                            and f != "five_prime_UTR" \
                                            and not isexon(f):
                                        del an.features[f]
                                        continue

                                    if endp in annotation.mapping and not isinstance(annotation.mapping[endp], int):
                                        mf = annotation.mapping[endp]
                                        expected_order = f_order + 1
                                        expected_order2 = f_order + 2
                                        if expected_order != self.refdata.structures[locus][mf] and expected_order2 != self.refdata.structures[locus][mf]:
                                            self.logger.info(self.logname + " out of order1 -> " + mf + " " + f)                                        
                                            del an.features[f]
                                            continue

                                    startp = an.features[f].location.start - 1
                                    if startp in annotation.mapping and not isinstance(annotation.mapping[startp], int):
                                        mf = annotation.mapping[startp]
                                        expected_order = f_order - 1
                                        expected_order2 = f_order - 2
                                        if expected_order != self.refdata.structures[locus][mf] and expected_order2 != self.refdata.structures[locus][mf]:
                                            self.logger.info(self.logname + " out of order2 -> " + mf + " " + f)
                                            del an.features[f]
                                            continue
                                else:

                                    ##Make sure order of alignment make sense
                                    if an.features[f].location.start == 0 \
                                            and f != "five_prime_UTR" \
                                            and 'three_prime_UTR' in annotation.annotation:
                                        del an.features[f]
                                        continue

                                    f_order = self.refdata.structures[locus][f]
                                    endp = an.features[f].location.end + 1
                                    if endp in annotation.mapping and not isinstance(annotation.mapping[endp], int):
                                        mf = annotation.mapping[endp]
                                        expected_order = f_order + 1
                                        if expected_order != self.refdata.structures[locus][mf]:
                                            self.logger.info(self.logname + " out of order12 -> " + mf + " " + f)
                                            del an.features[f]
                                            continue

                                    startp = an.features[f].location.start - 1
                                    if startp in annotation.mapping and not isinstance(annotation.mapping[startp], int):
                                        mf = annotation.mapping[startp]
                                        expected_order = f_order - 1
                                        if expected_order != self.refdata.structures[locus][mf]:
                                            self.logger.info(self.logname + " out of order22 -> " + mf + " " + f)
                                            del an.features[f]
                                            continue

                                if self.verbose and self.verbosity > 0:
                                    sl = str(len(an.annotation[f]))

                                    self.logger.info(self.logname + " " + locus
                                                     + " " + f
                                                     + " len = " + sl
                                                     + " | max = "
                                                     + str(max_length)
                                                     + " | min = "
                                                     + str(min_length))

                                    if len(an.annotation[f]) <= max_length:
                                        self.logger.info(self.logname
                                                         + " " + locus
                                                         + " " + f
                                                         + " " + sl + " <= "
                                                         + str(max_length))
                                    else:
                                        self.logger.info(self.logname
                                                         + " " + locus
                                                         + " " + f
                                                         + " " + sl + " ! <= !"
                                                         + str(max_length))

                                    if len(an.annotation[f]) >= min_length:
                                        self.logger.info(self.logname
                                                         + " " + locus
                                                         + " " + f
                                                         + " " + sl + " >= "
                                                         + str(min_length))
                                    else:
                                        self.logger.info(self.logname + " "
                                                         + locus
                                                         + " " + f
                                                         + " " + sl + " ! >= !"
                                                         + str(min_length))

                                # Update the original annotation
                                # with the returned annotation
                                if(len(an.annotation[f]) <= max_length and
                                        len(an.annotation[f]) >= min_length):
                                    if self.verbose and self.verbosity > 0:
                                        self.logger.info(self.logname
                                                         + " Annotated " + f
                                                         + " with clustalo using " +
                                                         combseqr.id)
                                        self.logger.info(self.logname
                                                         + " Coordinates for " + f
                                                         + str(an.features[f].location.start)
                                                         + " - " + str(an.features[f].location.end)
                                                         )
                                    if annotation.annotation:
                                        annotation.annotation.update({f:
                                                                    an.annotation[f]
                                                                  })
                                        annotation.features.update({f:
                                                                    an.features[f]
                                                                  })  
                                    else:
                                        annotation.annotation = {}
                                        annotation.annotation.update({f:
                                                                    an.annotation[f]
                                                                  })
                                        annotation.features.update({f:
                                                                    an.features[f]
                                                                  })
                                    if f in annotation.refmissing:
                                        i = annotation.refmissing.index(f)
                                        del annotation.refmissing[i]

                                    if f in annotation.missing:
                                        del annotation.missing[f]

                                    #if b in annotation.blocks:
                                    #    del annotation.blocks[annotation.blocks.index(b)]
                                    #     print(annotation.blocks)
                                    # if an.blocks:
                                    #     #print("PARTIAL BLOCK")
                                    #     #print(an.blocks)
                                    #     if b in missing_blocks:
                                    #         del missing_blocks[missing_blocks.index(b)]

                                    #     if self.verbose and self.verbosity > 0:
                                    #         self.logger.info(self.logname
                                    #                          + " Part of block mapped")

                                    # else:
                                    #     if self.verbose and self.verbosity > 0:
                                    #         self.logger.info(self.logname
                                    #                          + " All blocks mapped")
                                        
                                    #     del annotation.blocks[annotation.blocks.index(b)]
                                    #     if b in missing_blocks:
                                    #         del missing_blocks[missing_blocks.index(b)]
                                else:
                                    self.logger.info(self.logname + " FAILED HERE!!!!!!!!!")
                                #elif b not in mbtmp and b in missing_blocks:
                                #    mbtmp.append(b)
                            else:
                                self.logger.info(self.logname + " OUT OF ORDER !!!!!!!!!!!!!!!!!!!")

                    # Update the coordinates
                    coordinates = dict(map(lambda x: [x, 1], [i for i in range(0, len(sequence.seq)+1)]))
                    for f in annotation.features:
                        s = annotation.features[f].location.start
                        e = annotation.features[f].location.end
                        if s != 0:
                            s += 1
                            e += 1
                        else:
                            e += 1

                        for i in range(s, e):
                            annotation.mapping[i] = f
                            if i in coordinates:
                                del coordinates[i]

                    # Get any remaining blocks after updated
                    # annotation
                    blocks = getblocks(coordinates)
                    annotation.blocks = blocks

                    # Check to see if that annotation is complete
                    annotation.check_annotation()
                    if annotation.complete_annotation:
                        if self.verbose:
                            self.logger.info(self.logname
                                             + " Completed annotation"
                                             + " with targeted ref_align")
                        return annotation
                    else:

                        if an.features:
                            # for f in an.features:
                            #     f_order = self.refdata.structures[locus][f]
                            #     # Only add features if they are after the
                            #     # first feature mapped
                            #     if f_order >= start_order and f not in annotation.features \
                            #             and f in annotation.annotation:
                            #         annotation.features[f] = an.features[f]

                            # Rerunning seqsearch with
                            # new annotation from alignment
                            tmpann = self.seqsearch.search_seqs(found_seqs,
                                                                sequence,
                                                                locus,
                                                                partial_ann=annotation,
                                                                run=run)

                            if tmpann.complete_annotation:
                                for f in tmpann.annotation:
                                    if f not in annotation.annotation:
                                        annotation.annotation[f] = tmpann.annotation[f]
                                if self.verbose:
                                    self.logger.info(self.logname
                                                     + " Completed annotation"
                                                     + " with targeted ref_align and seqsearch!")

                                return tmpann
                            annotation = tmpann

                    ic += 1

                # Has to be missing exons
                exons_n = 0
                for f in annotation.missing:
                    if re.search("intron", f) or re.search("UTR", f):
                        exons_n += 1

                # Run exon only alignment
                if len(exons.seq) >= 4 and exons_n > 0:
                    exonan, ins, dels = align_seqs(exons, feat, locus, start,
                                                   annotation.missing,
                                                   len(annoated),
                                                   cutoff=cutoff,
                                                   verbose=self.align_verbose,
                                                   verbosity=self.align_verbosity)
                    mapped_exons = list(exonan.annotation.keys())
                    if len(mapped_exons) >= 1:
                        if self.verbose:
                            self.logger.info(self.logname
                                             + " Annotated exons with align")
                        for f in exonan.annotation:
                            if self.verbose and self.verbosity > 0:
                                self.logger.info(self.logname
                                                 + " Annotated "
                                                 + f + " len = "
                                                 + str(len(exonan
                                                           .annotation[f])))
                            annotation.annotation.update({f: exonan.annotation[f]})
                            annotation.features.update({f: exonan.features[f]})

                        coordinates = dict(map(lambda x: [x, 1], [i for i in range(0, len(sequence.seq)+1)]))
                        for f in annotation.features:
                            s = annotation.features[f].location.start
                            e = annotation.features[f].location.end
                            if s != 0:
                                s += 1
                                e += 1
                            else:
                                e += 1
                            for i in range(s, e):
                                annotation.mapping[i] = f
                                if i in coordinates:
                                    del coordinates[i]

                        blocks = getblocks(coordinates)
                        annotation.blocks = blocks
                        annotation.check_annotation()
                        if annotation.complete_annotation:
                            if self.verbose:
                                self.logger.info(self.logname + " Completed annotation with targeted exons ref_align")
                            return annotation

            return annotation
        elif partial_ann:

            annoated = []
            if hasattr(partial_ann, 'annotation') and partial_ann.annotation:
                annoated = list(partial_ann.annotation.keys())

            # Do full sequence alignments
            # any only extract out the part
            # that couldn't be explained from above
            if 0 in partial_ann.mapping \
                    and not isinstance(partial_ann.mapping[0], int):
                ft = partial_ann.mapping[0]
                start_order = self.refdata.structures[locus][ft]
            else:
                start_order = 0

            # Extract the missing blocks and
            # only align those blocks to the known
            # missing features
            # Start with all blocks missing
            # and then delete block if it is found
            tmp_missing = []
            missing_blocks = partial_ann.blocks
            for b in sorted(partial_ann.blocks):

                # **** Check if block equals full input sequence *** #
                # - If it does, then just align the ful
                start = b[0]-1 if b[0] != 0 else 0
                seq_feat = \
                    SeqFeature(
                        FeatureLocation(
                            ExactPosition(start),
                            ExactPosition(b[len(b)-1]),
                            strand=1),
                        type="unmapped")

                feat = seq_feat.extract(partial_ann.seq)
                combosrecs, exons, fullrec = self._refseqs(locus,
                                                           start,
                                                           partial_ann,
                                                           feat,
                                                           b)
                if len(fullrec.seq) >= 4:

                    fullref, ins, dels = align_seqs(fullrec, feat,
                                                    locus, start,
                                                    partial_ann.missing,
                                                    len(annoated),
                                                    cutoff=cutoff,
                                                    verbose=self.align_verbose,
                                                    verbosity=self.align_verbosity)

                    if hasattr(fullref, 'features') and fullref.features:
                        mapped_full = list(fullref.annotation.keys())
                        if len(mapped_full) >= 1:

                            if self.verbose:
                                self.logger.info(self.logname
                                                 + " Annotated fullrec"
                                                 + " with clustalo")

                            # If it wasn't found
                            del missing_blocks[missing_blocks.index(b)]

                            for f in fullref.annotation:
                                if self.verbose and self.verbosity > 0:
                                    self.logger.info(self.logname + " Annotated " + f + " len = " + str(len(fullref.annotation[f])))
                                partial_ann.annotation.update({f: fullref.annotation[f]})

                        if b in missing_blocks:
                            del missing_blocks[missing_blocks.index(b)]
                        else:
                            for bm in tmp_missing:
                                if bm in missing_blocks:
                                    del missing_blocks[missing_blocks.index(bm)]

                        for f in fullref.features:
                            f_order = self.refdata.structures[locus][f]
                            # Only add features if they are after the
                            # first feature mapped
                            if f_order >= start_order and f not in partial_ann.features \
                                    and f in partial_ann.annotation:
                                partial_ann.features[f] = fullref.features[f]

                        coordinates = dict(map(lambda x: [x, 1], [i for i in range(0, len(sequence.seq)+1)]))
                        for f in partial_ann.features:
                            s = partial_ann.features[f].location.start
                            e = partial_ann.features[f].location.end
                            if s != 0:
                                s += 1
                                e += 1
                            else:
                                e += 1
                            for i in range(s, e):
                                partial_ann.mapping[i] = f
                                if i in coordinates:
                                    del coordinates[i]

                        blocks = getblocks(coordinates)
                        partial_ann.check_annotation()
                        if partial_ann.complete_annotation:
                            if self.verbose:
                                self.logger.info(self.logname + " Annotated all features with clustalo")
                        
                    return partial_ann

            if self.verbose:
                self.logger.info(self.logname
                                 + " Failed to annotate features")
            return ''