"""The Genome class represents a shape sequence and its various properties, from which an image can be built."""

import pickle

import numpy as np

from shapevolve.utils import unadjust


class MismatchedGenomeError(Exception):
    """An error that occurs when two genomes do not share the same properties with one another."""
    pass


class Genome:
    """This class represents a shape sequence and its various properties, from which an image can be built."""

    def __init__(self, sequence, ratio, height, width, background_color, adjusters, palette, draw):
        """Constructs a genome based on certain properties.

        :param sequence: A sequence of Gene objects that represent the shapes in the image.
        :param ratio: The ratio between the evolution image dimensions and the original image dimensions.
        :param height: The height of the image.
        :param width: The width of the image.
        :param background_color: The background color of the image.
        :param adjusters: Reversible image adjusters that can be applied and unapplied to the image.
        :param palette: A list of RGB color tuples that colors are referenced from.
        :param draw: A function that is used to draw an image according to a Gene object.
        :type sequence: List[Gene]
        :type ratio: float
        :type height: int
        :type width: int
        :type background_color: Tuple[int, int, int]
        :type adjusters: List[Dict[str, Callable]]
        :type palette: List[Tuple[int, int, int]]
        :type draw: Callable
        """
        self.sequence = sequence
        self.ratio = ratio
        self.height = height
        self.width = width
        self.background_color = background_color
        self.adjusters = adjusters
        self.palette = palette
        self.draw = draw

    def render_scaled_image(self, after_unadjustment=False, fill_gaps=True):
        """Renders a scaled version of an image according to the genome's ratio field.

        :param after_unadjustment: Whether the image has already been un-adjusted.
        :param fill_gaps: Whether gaps should be filled in the scaled image.
        :type after_unadjustment: bool
        :type fill_gaps: bool
        :return: The scaled image.
        :rtype: ndarray
        """
        scaled_height = round(self.height / self.ratio)
        scaled_width = round(self.width / self.ratio)
        scaled_sequence = self.scale_sequence(fill_gaps)
        scaled_image = self.render_image(scaled_height, scaled_width, scaled_sequence, after_unadjustment)

        return scaled_image

    def scale_sequence(self, fill_gaps):
        """Generates a gene sequence with each gene scaled by the genome's ratio field.

        :param fill_gaps: Whether the gene's radius should be adjusted to fill new gaps in the scaled image.
        :type fill_gaps: bool
        :return: The new Gene sequence.
        :rtype: List[Gene]
        """
        return [gene.get_scaled_version(self.ratio, fill_gaps) for gene in self.sequence]

    def render_raw_image(self, after_unadjustment=False):
        """Renders the image without any scaling.

        :param after_unadjustment: Whether the image has already been un-adjusted.
        :type after_unadjustment: bool
        :return: The rendered image.
        :rtype: ndarray
        """
        return self.render_image(self.height, self.width, self.sequence, after_unadjustment)

    def render_image(self, height, width, sequence, after_unadjustment):
        """Renders an image according to certain parameters.

        :param height: The height of the desired image.
        :param width: The width of the desired image.
        :param sequence: The gene sequence to draw on the image.
        :param after_unadjustment: Whether the image has already been un-adjusted.
        :type height: int
        :type width: int
        :type sequence: List[Gene]
        :type after_unadjustment: bool
        :return: The rendered image.
        :rtype: ndarray
        """
        image = np.zeros((height, width, 3), np.uint8)
        image[:] = self.background_color

        for gene in sequence:
            self.draw(image, gene, self.palette)
        if not after_unadjustment:
            image = unadjust(image, self.adjusters)
        return image

    def save_genome(self, filename):
        """Saves the genome to a file using pickle.

        :param filename: The filename to save the genome to.
        :type filename: str
        """
        with open(filename, 'wb') as genomeFile:
            pickle.dump(self, genomeFile)


def is_compatible(genome1, genome2):
    """Check whether two genomes could describe the same base image.

    :param genome1: Genome 1, to be compared.
    :param genome2: Genome 2, to be compared.
    :type genome1: Genome
    :type genome2: Genome
    :return: Whether they are matched.
    :rtype: bool
    :raises: MismatchedGenomeError: If the genomes do not match.
    """
    gene_checks = ['max_radius', 'min_radius', 'height', 'width', 'num_colors']
    genome_checks = ['ratio', 'height', 'width', 'background_color', 'adjusters', 'palette', 'draw']

    for gene1, gene2 in zip(genome1.sequence, genome2.sequence):
        gene1_dict = vars(gene1)
        gene2_dict = vars(gene2)
        for check in gene_checks:
            if gene1_dict[check] != gene2_dict[check]:
                raise MismatchedGenomeError(f"Genome match was inconsistent: {check} field was incorrect. "
                                            f"Please make sure the source image matches the genome you are "
                                            f"trying to load.")

    if len(genome1.sequence) != len(genome2.sequence):
        raise MismatchedGenomeError(f"Genome match was inconsistent: gene sequences had different lengths. "
                                    f"Please make sure the source image matches the genome you are "
                                    f"trying to load.")

    genome1dict = vars(genome1)
    genome2dict = vars(genome2)
    for check in genome_checks:
        if genome1dict[check] != genome2dict[check]:
            raise MismatchedGenomeError(f"Genome match was inconsistent: {check} field was incorrect. "
                                        f"Please make sure the source image matches the genome you are "
                                        f"trying to load.")

    return True


def load_genome(filename):
    """Loads a pickled Genome file.

    :param filename: The filename of the genome to be loaded.
    :type filename: str
    :return: The loaded genome.
    :rtype: Genome
    """
    with open(filename, 'rb') as genomeFile:
        genome = pickle.load(genomeFile)
    return genome

"""The Genome class represents a shape sequence and its various properties, from which an image can be built."""

import pickle

import numpy as np

from shapevolve.utils import unadjust


class MismatchedGenomeError(Exception):
    """An error that occurs when two genomes do not share the same properties with one another."""
    pass


class Genome:
    """This class represents a shape sequence and its various properties, from which an image can be built."""

    def __init__(self, sequence, ratio, height, width, background_color, adjusters, palette, draw):
        """Constructs a genome based on certain properties.

        :param sequence: A sequence of Gene objects that represent the shapes in the image.
        :param ratio: The ratio between the evolution image dimensions and the original image dimensions.
        :param height: The height of the image.
        :param width: The width of the image.
        :param background_color: The background color of the image.
        :param adjusters: Reversible image adjusters that can be applied and unapplied to the image.
        :param palette: A list of RGB color tuples that colors are referenced from.
        :param draw: A function that is used to draw an image according to a Gene object.
        :type sequence: List[Gene]
        :type ratio: float
        :type height: int
        :type width: int
        :type background_color: Tuple[int, int, int]
        :type adjusters: List[Dict[str, Callable]]
        :type palette: List[Tuple[int, int, int]]
        :type draw: Callable
        """
        self.sequence = sequence
        self.ratio = ratio
        self.height = height
        self.width = width
        self.background_color = background_color
        self.adjusters = adjusters
        self.palette = palette
        self.draw = draw

    def render_scaled_image(self, after_unadjustment=False, fill_gaps=True):
        """Renders a scaled version of an image according to the genome's ratio field.

        :param after_unadjustment: Whether the image has already been un-adjusted.
        :param fill_gaps: Whether gaps should be filled in the scaled image.
        :type after_unadjustment: bool
        :type fill_gaps: bool
        :return: The scaled image.
        :rtype: ndarray
        """
        scaled_height = round(self.height / self.ratio)
        scaled_width = round(self.width / self.ratio)
        scaled_sequence = self.scale_sequence(fill_gaps)
        scaled_image = self.render_image(scaled_height, scaled_width, scaled_sequence, after_unadjustment)

        return scaled_image

    def scale_sequence(self, fill_gaps):
        """Generates a gene sequence with each gene scaled by the genome's ratio field.

        :param fill_gaps: Whether the gene's radius should be adjusted to fill new gaps in the scaled image.
        :type fill_gaps: bool
        :return: The new Gene sequence.
        :rtype: List[Gene]
        """
        return [gene.get_scaled_version(self.ratio, fill_gaps) for gene in self.sequence]

    def render_raw_image(self, after_unadjustment=False):
        """Renders the image without any scaling.

        :param after_unadjustment: Whether the image has already been un-adjusted.
        :type after_unadjustment: bool
        :return: The rendered image.
        :rtype: ndarray
        """
        return self.render_image(self.height, self.width, self.sequence, after_unadjustment)

    def render_image(self, height, width, sequence, after_unadjustment):
        """Renders an image according to certain parameters.

        :param height: The height of the desired image.
        :param width: The width of the desired image.
        :param sequence: The gene sequence to draw on the image.
        :param after_unadjustment: Whether the image has already been un-adjusted.
        :type height: int
        :type width: int
        :type sequence: List[Gene]
        :type after_unadjustment: bool
        :return: The rendered image.
        :rtype: ndarray
        """
        image = np.zeros((height, width, 3), np.uint8)
        image[:] = self.background_color

        for gene in sequence:
            self.draw(image, gene, self.palette)
        if not after_unadjustment:
            image = unadjust(image, self.adjusters)
        return image

    def save_genome(self, filename):
        """Saves the genome to a file using pickle.

        :param filename: The filename to save the genome to.
        :type filename: str
        """
        with open(filename, 'wb') as genomeFile:
            pickle.dump(self, genomeFile)


def is_compatible(genome1, genome2):
    """Check whether two genomes could describe the same base image.

    :param genome1: Genome 1, to be compared.
    :param genome2: Genome 2, to be compared.
    :type genome1: Genome
    :type genome2: Genome
    :return: Whether they are matched.
    :rtype: bool
    :raises: MismatchedGenomeError: If the genomes do not match.
    """
    gene_checks = ['max_radius', 'min_radius', 'height', 'width', 'num_colors']
    genome_checks = ['ratio', 'height', 'width', 'background_color', 'adjusters', 'palette', 'draw']

    for gene1, gene2 in zip(genome1.sequence, genome2.sequence):
        gene1_dict = vars(gene1)
        gene2_dict = vars(gene2)
        for check in gene_checks:
            if gene1_dict[check] != gene2_dict[check]:
                raise MismatchedGenomeError(f"Genome match was inconsistent: {check} field was incorrect. "
                                            f"Please make sure the source image matches the genome you are "
                                            f"trying to load.")

    if len(genome1.sequence) != len(genome2.sequence):
        raise MismatchedGenomeError(f"Genome match was inconsistent: gene sequences had different lengths. "
                                    f"Please make sure the source image matches the genome you are "
                                    f"trying to load.")

    genome1dict = vars(genome1)
    genome2dict = vars(genome2)
    for check in genome_checks:
        if genome1dict[check] != genome2dict[check]:
            raise MismatchedGenomeError(f"Genome match was inconsistent: {check} field was incorrect. "
                                        f"Please make sure the source image matches the genome you are "
                                        f"trying to load.")

    return True


def load_genome(filename):
    """Loads a pickled Genome file.

    :param filename: The filename of the genome to be loaded.
    :type filename: str
    :return: The loaded genome.
    :rtype: Genome
    """
    with open(filename, 'rb') as genomeFile:
        genome = pickle.load(genomeFile)
    return genome

The Genome class represents a shape sequence and its various properties, from which an image can be built. An error that occurs when two genomes do not share the same properties with one another. This class represents a shape sequence and its various properties, from which an image can be built. Constructs a genome based on certain properties.          :param sequence: A sequence of Gene objects that represent the shapes in the image.         :param ratio: The ratio between the evolution image dimensions and the original image dimensions.         :param height: The height of the image.         :param width: The width of the image.         :param background_color: The background color of the image.         :param adjusters: Reversible image adjusters that can be applied and unapplied to the image.         :param palette: A list of RGB color tuples that colors are referenced from.         :param draw: A function that is used to draw an image according to a Gene object.         :type sequence: List[Gene]         :type ratio: float         :type height: int         :type width: int         :type background_color: Tuple[int, int, int]         :type adjusters: List[Dict[str, Callable]]         :type palette: List[Tuple[int, int, int]]         :type draw: Callable Renders a scaled version of an image according to the genome's ratio field.          :param after_unadjustment: Whether the image has already been un-adjusted.         :param fill_gaps: Whether gaps should be filled in the scaled image.         :type after_unadjustment: bool         :type fill_gaps: bool         :return: The scaled image.         :rtype: ndarray Generates a gene sequence with each gene scaled by the genome's ratio field.          :param fill_gaps: Whether the gene's radius should be adjusted to fill new gaps in the scaled image.         :type fill_gaps: bool         :return: The new Gene sequence.         :rtype: List[Gene] Renders the image without any scaling.          :param after_unadjustment: Whether the image has already been un-adjusted.         :type after_unadjustment: bool         :return: The rendered image.         :rtype: ndarray Renders an image according to certain parameters.          :param height: The height of the desired image.         :param width: The width of the desired image.         :param sequence: The gene sequence to draw on the image.         :param after_unadjustment: Whether the image has already been un-adjusted.         :type height: int         :type width: int         :type sequence: List[Gene]         :type after_unadjustment: bool         :return: The rendered image.         :rtype: ndarray Saves the genome to a file using pickle.          :param filename: The filename to save the genome to.         :type filename: str Check whether two genomes could describe the same base image.      :param genome1: Genome 1, to be compared.     :param genome2: Genome 2, to be compared.     :type genome1: Genome     :type genome2: Genome     :return: Whether they are matched.     :rtype: bool     :raises: MismatchedGenomeError: If the genomes do not match. Loads a pickled Genome file.      :param filename: The filename of the genome to be loaded.     :type filename: str     :return: The loaded genome.     :rtype: Genome