"""
    accessibility_functions
    ~~~~~~~~~~~~~~~~~~~~~~~

    Exposes functions for accessibility, neighbor abundance, and diversity index computation.

    :author: Fahad Khalid
    :license: MIT, see LICENSE for more details.
"""


class AccessibilityAnalyzer:
    # Constructor
    def __init__(self, repertoire, network, repToGiantDict, dataDict, netBuilder, bitManip):
        # Repertoire name
        self.repertoire = repertoire

        # Reference to the network on which to perform this
        # analysis
        self.network = network

        # Reference to dict: key=repertoire, value=network
        # FIXME: perhaps this dict should be replaced by the genonet
        #		 method getGraphFor() ...
        self.repToGiantDict = repToGiantDict

        # Reference to dict: key=rep, value=dict{seq: score}
        self.dataDict = dataDict

        # Get a reference to the NetworkBuilder object
        self.netBuilder = netBuilder

        # Reference to the bit manipulator object
        self.bitManip = bitManip

    # Computes the accessibility value for the given repertoire.
    # Ref: Cowperthwaite et al. (2008) PLoS Comp. Biol.
    def getAccessibility(self):
        # Value to be calculated and returned
        accessibility = 0

        # Get the list of targets
        targets = self.dataDict.keys()

        # Remove the repertoire under consideration from the list of targets
        targets.remove(self.repertoire)

        # Get all sequences (in bit format) for the given repertoire
        sequences = [self.bitManip.seqToBits(seq) for seq in \
                     self.network.vs["sequences"]]

        # For each target, compute F(j,i)
        for target in targets:
            # Get the genotype network for this target
            targetNet = self.repToGiantDict[target]

            # Get all external neighbors for this target
            extNeighbors = self.netBuilder.getAllExtNeighbors(targetNet)

            # Find sequences that exist in both extNeighbors and
            # sequences, i.e., intersection of both sets
            commonSeqs = set(extNeighbors) & set(sequences)

            # Calculate what fraction of sequences in extNeighbors
            # is in commonSeqs
            try:
                fraction = float(len(commonSeqs)) / float(len(extNeighbors))
            except ZeroDivisionError:
                fraction = 0

            # Add the fraction to the total accessibility value
            accessibility += fraction

        return accessibility

    # Computes the neighbor abundance for the given repertoire.
    # Ref: Cowperthwaite et al. (2008) PLoS Comp. Biol.
    def getNeighborAbundance(self):
        # Value to be calculated and returned
        abundance = 0

        # Get all external neighbors for the given network
        extNeighbors = self.netBuilder.getAllExtNeighbors(self.network)

        # Get the list of targets
        targets = self.dataDict.keys()

        # Remove the repertoire under consideration from the list of targets
        targets.remove(self.repertoire)

        # For each target, compute F(i, j)
        for target in targets:
            # Get target sequences in bit format
            targetSeqs = [self.bitManip.seqToBits(seq) for seq in \
                          self.repToGiantDict[target].vs["sequences"]]

            # Find sequences that exist in both extNeighbors and
            # targetSeqs, i.e., intersection of both sets
            commonSeqs = set(extNeighbors) & set(targetSeqs)

            # Calculate what fraction of targetSeqs in extNeighbors
            # is in commonSeqs
            try:
                fraction = float(len(commonSeqs)) / float(len(extNeighbors))
            except ZeroDivisionError:
                fraction = 0

            # Muliply the fraction with the target's abundance, i.e., the size
            # of the target genotype network
            abundanceFrac = float(fraction) * float(len(targetSeqs))

            # Add the fraction to the total abundance value
            abundance += abundanceFrac

        return abundance

    # Computes the E-statistic value for the given repertoire.
    # Ref: Cowperthwaite et al. (2008) PLoS Comp. Biol.
    def getPhenotypicDivesity(self):
        # Value to be calculated and returned
        diversity = 0

        # Get all external neighbors for the given network
        extNeighbors = self.netBuilder.getAllExtNeighbors(self.network)

        # Get the list of targets
        targets = self.dataDict.keys()

        # Remove the repertoire under consideration from the list of targets
        targets.remove(self.repertoire)

        # For each target, compute F(i, j)
        for target in targets:
            # Get target sequences in bit format
            targetSeqs = [self.bitManip.seqToBits(seq) for seq in \
                          self.repToGiantDict[target].vs["sequences"]]

            # Find sequences that exist in both extNeighbors and
            # targetSeqs, i.e., intersection of both sets
            commonSeqs = set(extNeighbors) & set(targetSeqs)

            # Calculate what fraction of targetSeqs in extNeighbors
            # is in commonSeqs
            try:
                fraction = float(len(commonSeqs)) / float(len(extNeighbors))
            except ZeroDivisionError:
                fraction = 0

            # Add the fraction to the total diversity value
            diversity += (fraction * fraction)

        return diversity
