from __future__ import annotations

from collections.abc import Iterable, Sequence

from radiate.radiate import PyChromosome
from radiate._bridge.wrapper import RsObject
from radiate._typing import RdDataType
from radiate.dtype import Int64, Float64

from .gene import Gene, GeneType


class Chromosome[T](RsObject):
    """
    Represents a chromosome in a genome.
    """

    @staticmethod
    def int(
        length: int,
        init_range: tuple[int, int] | None = None,
        bounds: tuple[int, int] | None = None,
        dtype: RdDataType | None = None,
    ) -> Chromosome[int]:
        """
        Create an integer chromosome with specified length and optional parameters.
        :param length: Length of the chromosome.
        :param allele: Initial value of the gene.
        :param init_range: Minimum and maximum value for the gene.
        :param bounds: Minimum and maximum bound for the gene.
        :return: A new Chromosome instance configured as an integer chromosome.

        Example
        --------
        >>> rd.Chromosome.int(length=3, init_range=(0, 10), bounds=(-5, 15))
        Chromosome(genes=[0, 5, 10])
        """
        dtype = dtype or Int64
        genes = [
            Gene.int(init_range=init_range, bounds=bounds, dtype=dtype)
            for _ in range(length)
        ]
        return Chromosome(genes=genes)

    @staticmethod
    def bit(length: int) -> Chromosome[bool]:
        """
        Create a bit chromosome with specified length and optional allele.
        :param length: Length of the chromosome.
        :param allele: Initial value of the gene.
        :return: A new Chromosome instance configured as a bit chromosome.

        Example
        --------
        >>> rd.chromosome.bit(length=4)
        Chromosome(genes=[True, False, True, False])
        """
        genes = [Gene.bit() for _ in range(length)]
        return Chromosome(genes=genes)

    @staticmethod
    def char(length: int, char_set: set[str] | None = None) -> Chromosome[str]:
        """
        Create a character chromosome with specified length and optional character set.
        :param length: Length of the chromosome.
        :param char_set: Set of characters to choose from.
        :return: A new Chromosome instance configured as a character chromosome.

        Example
        --------
        >>> rd.chromosome.char(length=5, char_set={"a", "b", "c"})
        Chromosome(genes=[a, b, c, a, b])
        """
        genes = [Gene.char(char_set=char_set) for _ in range(length)]
        return Chromosome(genes=genes)

    @staticmethod
    def float(
        length: int = 1,
        init_range: tuple[float, float] | None = None,
        bounds: tuple[float, float] | None = None,
        genes: Iterable[Gene[float]]
        | Sequence[Gene[float]]
        | Gene[float]
        | None = None,
        dtype: RdDataType | None = None,
    ) -> Chromosome[float]:
        """
        Create a float chromosome with specified length and optional parameters.
        :param length: Length of the chromosome.
        :param allele: Initial value of the gene.
        :param init_range: Minimum and maximum value for the gene.
        :param bounds: Minimum and maximum bound for the gene.
        :return: A new Chromosome instance configured as a float chromosome.

        Example
        --------
        >>> rd.chromosome.float(length=5, init_range=(0.0, 10.0), bounds=(-5.0, 15.0))
        Chromosome(genes=[0.0, 2.5, 5.0, 7.5, 10.0])
        """
        if genes is not None:
            if isinstance(genes, (Iterable, Sequence)):
                return Chromosome(genes=list(genes))
            if isinstance(genes, Gene):
                return Chromosome(genes=[genes])
        else:
            genes = [
                Gene.float(init_range=init_range, bounds=bounds, dtype=dtype or Float64)
                for _ in range(length)
            ]
            return Chromosome(genes=genes)

    def __init__(
        self,
        genes: Iterable[Gene[T]] | Gene[T] | None = None,
    ):
        super().__init__()

        if genes is None:
            return

        if isinstance(genes, Gene):
            self._pyobj = PyChromosome([genes.__backend__()])
        if isinstance(genes, Iterable):
            self._pyobj = PyChromosome(list(map(lambda g: g.__backend__(), genes)))
        else:
            raise TypeError("genes must be a Gene instance or a list of Gene instances")

    def __repr__(self):
        return self.__backend__().__repr__()

    def __len__(self):
        """
        Returns the length of the chromosome.
        :return: Length of the chromosome.
        """
        return self.__backend__().__len__()

    def __getitem__(self, index: int) -> Gene[T]:
        """
        Returns the gene at the specified index.
        :param index: Index of the gene to retrieve.
        :return: Gene instance at the specified index.
        """
        return self.genes()[index]

    def __iter__(self):
        """
        Creates an iterator over a View of the chromosome. This means that
        each gene returned by the iterator is a view into the original chromosome and
        is not a copy, so changes to the gene will affect the original chromosome.

        :return: An iterator over the genes in the chromosome.
        """
        for i in self.genes():
            yield i

    def gene_type(self) -> "GeneType":
        from . import GeneType

        return GeneType.from_str(self.__backend__().gene_type())

    def genes(self) -> list[Gene[T]]:
        """
        Get the genes of the chromosome.
        :return: The genes of the chromosome.
        """
        return self.try_get_cache(
            "genes_cache",
            lambda: [Gene.from_rust(g) for g in self.__backend__()],
        )
