from typing import List, Tuple, Callable, Dict

from tunepy2.interfaces import AbstractGenomeFactory, AbstractRandomNumberGenerator
from tunepy2.internal import Genome
from copy import deepcopy


class RandomNeighborGenomeFactory(AbstractGenomeFactory):
    def __init__(
            self,
            dimensions: Tuple,
            rng: AbstractRandomNumberGenerator,
            n: int,
            fitness_func: Callable[..., float],
            *args,
            **kwargs):
        """
        Creates a new RandomNumberGenomeFactory

        :param dimensions: dimensions of bitstrings of Genome objects generated by the factory
        :param rng: random number generator
        :param n: Manhattan distance between neighbors
        :param fitness_func: fitness function passed into new Genome objects that accepts a bitstring
        :param args: will be passed into fitness_func
        :param kwargs: will be passed into fitness_func
        """
        self._n = n
        self._dimensions = dimensions
        self._rng = rng
        self._fitness_func = fitness_func
        self._args = args
        self._kwargs = kwargs

    def build(self, prior_genomes: List[Genome]) -> Genome:
        """ Builds a new Genome from a list of priors

        :param prior_genomes: list of prior Genomes
        :return: a new Genome object
        """
        def bit_flip(bit_value):
            if bit_value == 0:
                return 1
            else:
                return 0

        def recursive_bit_flip(bitstring, dimensions):
            random_index = self._rng.random_int(0, dimensions[0])
            if len(dimensions) == 1:
                bitstring[random_index] = bit_flip(bitstring[random_index])
            else:
                recursive_bit_flip(bitstring[random_index], dimensions[1:])

        selected_genome = prior_genomes[self._rng.random_int(0, len(prior_genomes))]
        candidate_genome_bitstring = deepcopy(selected_genome.bitstring)

        for _ in range(self._n):
            recursive_bit_flip(candidate_genome_bitstring, self._dimensions)

        return Genome(self._fitness_func, candidate_genome_bitstring, *self._args, **self._kwargs)

    @property
    def dimensions(self) -> Tuple:
        """

        :return: the dimensions of Genome objects returned by this factory
        """
        return self._dimensions
