import six
from unittest import TestCase

from dark.mutations import getAPOBECFrequencies, mutateString


class TestGetAPOBECFrequencies(TestCase):
    """
    Tests the getAPOBECFrequencies function
    """
    def testCorrectResult(self):
        """
        The function should return the right bases.
        s:  AGTCTAAAGTCATGACTGGTCCCCTTG
        q:      ..AA..T....G....G...
        """
        result = []
        subjectTitle = '333031'
        subjectSequence = 'AGTCTAAAGTCATGACTGGTCCCCTTG'
        subject = '%s \t \t \t %s' % (subjectTitle, subjectSequence)
        result.append(subject)

        queryTitle = 'H9L0NJQ01DXGOQ'
        querySequence = '    ..AA..T....G....G...   '
        query = '%s \t %s' % (queryTitle, querySequence)
        result.append(query)

        bases = getAPOBECFrequencies(result, 'C', 'G', 'cPattern')

        self.assertEqual({'ACA': 0, 'ACC': 0, 'ACG': 0, 'ACT': 1, 'CCA': 0,
                          'CCC': 0, 'CCG': 0, 'CCT': 0, 'GCA': 0, 'GCC': 0,
                          'GCG': 0, 'GCT': 0, 'TCA': 0, 'TCC': 1, 'TCG': 0,
                          'TCT': 0}, bases)


class TestMutateString(TestCase):
    """
    Tests the mutateString function.
    """

    def testZeroLength(self):
        """
        The function should raise when given an empty string
        """
        error = 'Empty original string passed.'
        with six.assertRaisesRegex(self, ValueError, error):
            mutateString('', 1)

    def testTooManyMutationsRequested(self):
        """
        The function should raise when asked to perform more mutations than
        there are in the original string.
        """
        error = 'Cannot make 2 mutations in a string of length 1'
        with six.assertRaisesRegex(self, ValueError, error):
            mutateString('x', 2)

    def testDuplicateReplacementLetter(self):
        """
        The function should raise when given a replacement string that contains
        a duplicate letter.
        """
        error = 'Replacement string contains duplicates'
        with six.assertRaisesRegex(self, ValueError, error):
            mutateString('x', 1, 'aa')

    def testReplacementLengthOneAppearsInOriginal(self):
        """
        The function should raise when given a replacement string that contains
        just one letter if that letter also appears in the original.
        """
        error = 'Impossible replacement'
        with six.assertRaisesRegex(self, ValueError, error):
            mutateString('x', 1, 'x')

    def testZeroReplacements(self):
        """
        The function should return the original string when zero mutations
        are requested.
        """
        self.assertEqual('acgt', mutateString('acgt', 0, 'x'))

    def testOneDeterministicReplacement(self):
        """
        The function should return the correct result when one mutation is
        requested and only one mutation is possible.
        """
        self.assertEqual('c', mutateString('a', 1, 'ac'))

    def testFiveDeterminsticReplacements(self):
        """
        The function should return the correct result when five mutations are
        requested and only one outcome is possible.
        """
        self.assertEqual('ccccc', mutateString('aaaaa', 5, 'ac'))

    def testOneReplacement(self):
        """
        The function should return all possible correct results (and only those
        results) when one mutation is requested and two outcomes are possible.

        NOTE: this test is a bit bad because it's non-deterministic.
        """
        possible = set(['ab', 'ba'])
        seen = set()

        for _ in range(100):
            result = mutateString('aa', 1, 'ab')
            self.assertTrue(result in possible)
            seen.add(result)
        self.assertEqual(seen, possible)

    def testTwoReplacements(self):
        """
        The function should return all possible correct results (and only those
        results) when two mutations are requested and four outcomes are
        possible.

        NOTE: this test is a bit bad because it's non-deterministic.
        """
        possible = set(['ab', 'ba', 'ac', 'ca'])
        seen = set()

        for _ in range(100):
            result = mutateString('aa', 1, 'abc')
            self.assertTrue(result in possible,
                            '%s is not in %r' % (result, possible))
            seen.add(result)
        self.assertEqual(seen, possible)
