# Copyright 2003-2008 by Leighton Pritchard.  All rights reserved.
# This code is part of the Biopython distribution and governed by its
# license.  Please see the LICENSE file that should have been included
# as part of this package.
#
# Contact:       Leighton Pritchard, Scottish Crop Research Institute,
#                Invergowrie, Dundee, Scotland, DD2 5DA, UK
#                L.Pritchard@scri.ac.uk
################################################################################

""" Feature module

    Provides:

    o Feature - class to wrap Bio.SeqFeature objects with drawing information

    For drawing capabilities, this module uses reportlab to define colors:

    http://www.reportlab.com

    For dealing with biological information, the package uses BioPython:

    http://www.biopython.org
"""

# ReportLab imports
from reportlab.lib import colors

# GenomeDiagram imports
from ._Colors import ColorTranslator


class Feature(object):
    """ Class to wrap Bio.SeqFeature objects for GenomeDiagram

        Provides:

        Methods:

        o __init__(self, parent=None, feature_id=None, feature=None,
                 color=colors.lightgreen) Called when the feature is
                 instantiated

        o set_feature(self, feature) Wrap the passed feature

        o get_feature(self) Return the unwrapped Bio.SeqFeature object

        o set_color(self, color) Set the color in which the feature will
                be drawn (accepts multiple formats: reportlab color.Color()
                tuple and color.name, or integer representing Artemis color

        o get_color(self) Returns color.Color tuple of the feature's color

        o __getattr__(self, name) Catches attribute requests and passes them to
                the wrapped Bio.SeqFeature object

        Attributes:

        o parent    FeatureSet, container for the object

        o id        Unique id

        o color    color.Color, color to draw the feature

        o hide      Boolean for whether the feature will be drawn or not

        o sigil     String denoting the type of sigil to use for the feature.
                    Currently either "BOX" or "ARROW" are supported.

        o arrowhead_length  Float denoting length of the arrow head to be drawn,
                            relative to the bounding box height.  The arrow shaft
                            takes up the remainder of the bounding box's length.

        o arrowshaft_height  Float denoting length of the representative arrow
                             shaft to be drawn, relative to the bounding box height.
                             The arrow head takes the full height of the bound box.

        o name_qualifiers   List of Strings, describes the qualifiers that may
                    contain feature names in the wrapped Bio.SeqFeature object

        o label     Boolean, 1 if the label should be shown

        o label_font    String describing the font to use for the feature label

        o label_size    Int describing the feature label font size

        o label_color  color.Color describing the feature label color

        o label_angle   Float describing the angle through which to rotate the
                    feature label in degrees (default = 45, linear only)

        o label_position    String, 'start', 'end' or 'middle' denoting where
                    to place the feature label. Leave as None for the default
                    which is 'start' for linear diagrams, and at the bottom of
                    the feature as drawn on circular diagrams.

        o label_strand  Integer -1 or +1 to explicitly place the label on the
                    forward or reverse strand. Default (None) follows th
                    feature's strand. Use -1 to put labels under (linear) or
                    inside (circular) the track, +1 to put them above (linear)
                    or outside (circular) the track.

        o locations     List of tuples of (start, end) ints describing where the
                    feature and any subfeatures start and end

        o type      String denoting the feature type

        o name      String denoting the feature name

        o strand    Int describing the strand on which the feature is found

    """
    def __init__(self, parent=None, feature_id=None, feature=None,
                 color=colors.lightgreen, label=0, border=None, colour=None):
        """ __init__(self, parent=None, feature_id=None, feature=None,
                 color=colors.lightgreen, label=0)

            o parent    FeatureSet containing the feature

            o feature_id    Unique id for the feature

            o feature   Bio.SeqFeature object to be wrapped

            o color    color.Color Color to draw the feature (overridden
                       by backwards compatible argument with UK spelling,
                       colour).  Either argument is overridden if 'color'
                       is found in feature qualifiers

            o border   color.Color Color to draw the feature border, use
                       None for the same as the fill color, False for no border.

            o label     Boolean, 1 if the label should be shown
        """
        # Let the UK spelling (colour) override the USA spelling (color)
        if colour is not None:
            color = colour

        self._colortranslator = ColorTranslator()

        # Initialise attributes
        self.parent = parent
        self.id = feature_id
        self.color = color            # default color to draw the feature
        self.border = border
        self._feature = None            # Bio.SeqFeature object to wrap
        self.hide = 0                   # show by default
        self.sigil = 'BOX'
        self.arrowhead_length = 0.5  # 50% of the box height
        self.arrowshaft_height = 0.4  # 40% of the box height
        self.name_qualifiers = ['gene', 'label', 'name', 'locus_tag', 'product']
        self.label = label
        self.label_font = 'Helvetica'
        self.label_size = 6
        self.label_color = colors.black
        self.label_angle = 45
        self.label_position = None  # Expect 'start', 'middle', or 'end' (plus aliases)
        self.label_strand = None  # Expect +1 or -1 if overriding this

        if feature is not None:
            self.set_feature(feature)

    def set_feature(self, feature):
        """ set_feature(self, feature)

            o feature   Bio.SeqFeature object to be wrapped

            Defines the Bio.SeqFeature object to be wrapped
        """
        self._feature = feature
        self.__process_feature()

    def __process_feature(self):
        """ __process_feature(self)

            Examine the feature to be wrapped, and set some of the Feature's
            properties accordingly
        """
        self.locations = []
        bounds = []
        # This will be a list of length one for simple FeatureLocation:
        for location in self._feature.location.parts:
            start = location.nofuzzy_start
            end = location.nofuzzy_end
            # if start > end and self.strand == -1:
            #    start, end = end, start
            self.locations.append((start, end))
            bounds += [start, end]
        self.type = str(self._feature.type)                     # Feature type
        # TODO - Strand can vary with subfeatures (e.g. mixed strand tRNA)
        if self._feature.strand is None:
            # This is the SeqFeature default (None), but the drawing code
            # only expects 0, +1 or -1.
            self.strand = 0
        else:
            self.strand = int(self._feature.strand)                 # Feature strand
        if 'color' in self._feature.qualifiers:                # Artemis color (if present)
            self.color = self._colortranslator.artemis_color(
                                         self._feature.qualifiers['color'][0])
        self.name = self.type
        for qualifier in self.name_qualifiers:
            if qualifier in self._feature.qualifiers:
                self.name = self._feature.qualifiers[qualifier][0]
                break
        # Note will be 0 to N for origin wrapping feature on genome of length N
        self.start, self.end = min(bounds), max(bounds)

    def get_feature(self):
        """ get_feature(self) -> Bio.SeqFeature

            Returns the unwrapped Bio.SeqFeature object
        """
        return self._feature

    def set_colour(self, colour):
        """Backwards compatible variant of set_color(self, color) using UK spelling."""
        color = self._colortranslator.translate(colour)
        self.color = color

    def set_color(self, color):
        """ set_color(self, color)

            o color    The color to draw the feature - either a colors.Color
                       object, an RGB tuple of floats, or an integer
                       corresponding to colors in colors.txt

            Set the color in which the feature will be drawn
        """
        # TODO - Make this into the set method for a color property?
        color = self._colortranslator.translate(color)
        self.color = color

    def __getattr__(self, name):
        """ __getattr__(self, name) -> various

            If the Feature class doesn't have the attribute called for,
            check in self._feature for it
        """
        return getattr(self._feature, name)  # try to get the attribute from the feature


################################################################################
# RUN AS SCRIPT
################################################################################

if __name__ == '__main__':

    # Test code
    gdf = Feature()
