#!/usr/bin/env python
"""Tests for an Organism in a Genetic Algorithm population.
"""
# standard library
import sys

# Biopython
from Bio import Alphabet
from Bio.Seq import MutableSeq

# PyUnit
import unittest

# local stuff
from Bio.GA import Organism

def run_tests(argv):
    test_suite = testing_suite()
    runner = unittest.TextTestRunner(sys.stdout, verbosity = 2)
    runner.run(test_suite)
    
def testing_suite():
    """Generate the set of tests.
    """
    test_suite = unittest.TestSuite()

    test_loader = unittest.TestLoader()
    test_loader.testMethodPrefix = 't_'
    tests = [CreatePopulationTest, OrganismTest]

    for test in tests:
        cur_suite = test_loader.loadTestsFromTestCase(test)
        test_suite.addTest(cur_suite)

    return test_suite

# -- utility functions
class TestAlphabet(Alphabet.Alphabet):
    """Simple alphabet for test purposes.
    """
    letters = ["1", "2", "3", "4"]

def genome_generator():
    """Generate a genome for testing purposes.
    """
    return MutableSeq("1234", TestAlphabet())

def fitness_calculator(genome):
    """Calculate fitness for testing purposes.
    """
    assert isinstance(genome, MutableSeq), "Expected MutableSeq for a genome."

    regular_seq = genome.toseq()
    return int(regular_seq.data)

class CreatePopulationTest(unittest.TestCase):
    """Tests for utility functions for creating populations.
    """
    def setUp(self):
        self.alphabet = TestAlphabet()

    def t_function_population(self):
        """Create a population using a function to generate genomes.
        """
        num_orgs = 10
        new_pop = Organism.function_population(genome_generator,
                                               num_orgs, fitness_calculator)

        assert len(new_pop) == num_orgs, "Expected %s organisms, got %s" \
               % (num_orgs, len(new_pops))

        for org in new_pop:
            assert isinstance(org, Organism.Organism), \
                   "Expected to get an organism, got %r" % org

            exp_fit = fitness_calculator(org.genome)
            assert org.fitness == exp_fit, \
                   "Expected fitness of %s, got %s" % (org.fitness, exp_fit)

    def t_random_population(self):
        """Create a population randomly from a alphabet.
        """
        num_orgs = 10
        genome_size = 5
        new_pop = Organism.random_population(self.alphabet, genome_size,
                                             num_orgs, fitness_calculator)

        assert len(new_pop) == num_orgs, "Expected %s organisms, got %s" \
               % (num_orgs, len(new_pops))

        for org in new_pop:
            assert isinstance(org, Organism.Organism), \
                   "Expected to get an organism, got %r" % org

            exp_fit = fitness_calculator(org.genome)
            assert org.fitness == exp_fit, \
                   "Expected fitness of %s, got %s" % (org.fitness, exp_fit)

            assert len(org.genome) == genome_size, \
                   "Expected genome size of %s, got %s" % (len(org.genome),
                                                           genome_size)

    def t_random_population_types(self):
        """Creating a random population with different types of alphabets.
        """
        class DoubleAlphabet:
            letters = [1.0, 2.0]

        class CharacterAlphabet:
            letters = ["a", "b"]

        class IntegerAlphabet:
            letters = [1, 2]

        def test_fitness(genome):
            return 2

        all_alphabets = [DoubleAlphabet(), CharacterAlphabet(),
                         IntegerAlphabet()]

        for alphabet in all_alphabets:
            new_pop = Organism.random_population(alphabet, 5, 10,
                                                 test_fitness)

class OrganismTest(unittest.TestCase):
    """Tests for an organism in a GA population.
    """
    def setUp(self):
        self.alphabet = TestAlphabet()
        self.genome = MutableSeq("1234", self.alphabet)
        self.organism = Organism.Organism(self.genome, fitness_calculator)

    def t_organism_basic(self):
        """Exercise basic organism functionality.
        """
        same_genome = MutableSeq("1234", self.alphabet)
        same_organism = Organism.Organism(same_genome, fitness_calculator)

        dif_genome = MutableSeq("1111", self.alphabet)
        dif_organism = Organism.Organism(dif_genome, fitness_calculator)

        assert self.organism == same_organism, \
               "Comparison doesn't work for identical organisms."

        assert self.organism != dif_organism, \
               "Comparison doesn't work for different organism."

    def t_organism_fitness(self):
        """Test the ability to deal with the fitness of the genome.
        """
        assert self.organism.fitness == 1234, \
               "Unexpected fitness %s" % self.organism.fitness
        
        new_genome = MutableSeq("1111", self.alphabet)
        self.organism.genome = new_genome
        self.organism.recalculate_fitness()

        assert self.organism.fitness == 1111, \
               "Unexpected fitness %s" % self.organism.fitness

    def t_organism_copy(self):
        """Test copying of organisms.
        """
        new_organism = self.organism.copy()

        new_organism.genome.append("1")

        assert new_organism.genome != self.organism.genome, \
               "Did not provide a copy of the organism."

    def t_provide_fitness(self):
        """Test that providing a pre-calculated fitness works.
        """
        def fitness_calc(genome):
            raise ValueError("Should not have been executed.")

        genome = self.organism.genome

        # make sure not supplying fitness works
        try:
            new_org = Organism.Organism(genome, fitness_calc)
            raise AssertionError("Did not calculate fitness when expected.")
        except ValueError:
            pass

        # make sure supplying fitness works
        new_org = Organism.Organism(genome, fitness_calc, 50)

if __name__ == "__main__":
    sys.exit(run_tests(sys.argv))
