import numpy as np
from PIL import Image, ImageDraw
from typing import Tuple, Optional, List, Union
import svgwrite
import colorsys


class PatternGenerator:
    AVAILABLE_PATTERNS = [
        "yin_yang", "flowers", "pseudocalligraphy",
        "cross_stitch", "mandala", "geometric_tiles", "waves"
    ]

    def __init__(self, width: int = 800, height: int = 600, seed: Optional[int] = None, format: str = "png"):
        self.width = width
        self.height = height
        self.format = format
        self.seed = seed if seed is not None else np.random.randint(0, 2**31)
        self.rng = np.random.default_rng(self.seed)

        self.img: Optional[Image.Image] = None
        self.draw: Optional[ImageDraw.ImageDraw] = None
        self.dwg: Optional[svgwrite.Drawing] = None


    def _random_palette(self, n: int = 5) -> List[Tuple[int, int, int, int]]:
        base_hue = self.rng.random()
        colors = []
        for i in range(n):
            hue = (base_hue + i / n + self.rng.uniform(-0.1, 0.1)) % 1.0
            sat = self.rng.uniform(0.4, 0.9)
            val = self.rng.uniform(0.5, 1.0)
            r, g, b = colorsys.hsv_to_rgb(hue, sat, val)
            colors.append((int(r * 255), int(g * 255), int(b * 255), 255))
        return colors

    def _svg_color(self, color: Tuple[int, int, int, int]) -> str:
        return f"rgb({color[0]},{color[1]},{color[2]})"


    def _setup_canvas(self, bg_color: Tuple[int, int, int, int]):
        if self.format == "svg":
            self.dwg = svgwrite.Drawing(size=(self.width, self.height))
            self.dwg.add(self.dwg.rect(insert=(0, 0), size=(self.width, self.height), fill=self._svg_color(bg_color)))
        else:
            self.img = Image.new('RGBA', (self.width, self.height), bg_color)
            self.draw = ImageDraw.Draw(self.img)

    def _draw_ellipse(self, bbox: List[float], fill: Tuple[int, int, int, int], outline=None, width=1):
        if self.format == "svg":
            x0, y0, x1, y1 = bbox
            cx, cy = (x0 + x1) / 2, (y0 + y1) / 2
            rx, ry = abs(x1 - x0) / 2, abs(y1 - y0) / 2
            kwargs = {'center': (cx, cy), 'r': (rx, ry), 'fill': self._svg_color(fill)}
            if outline:
                kwargs['stroke'] = self._svg_color(outline)
                kwargs['stroke_width'] = width
            self.dwg.add(self.dwg.ellipse(**kwargs))
        else:
            self.draw.ellipse(bbox, fill=fill, outline=outline, width=width)

    def _draw_rectangle(self, bbox: List[float], fill: Tuple[int, int, int, int]):
        if self.format == "svg":
            x0, y0, x1, y1 = bbox
            self.dwg.add(self.dwg.rect(insert=(x0, y0), size=(abs(x1 - x0), abs(y1 - y0)), fill=self._svg_color(fill)))
        else:
            self.draw.rectangle(bbox, fill=fill)

    def _draw_line(self, xy: List[float], fill: Tuple[int, int, int, int], width=1):
        if self.format == "svg":
            x0, y0, x1, y1 = xy
            self.dwg.add(self.dwg.line(start=(x0, y0), end=(x1, y1), stroke=self._svg_color(fill), stroke_width=width))
        else:
            self.draw.line(xy, fill=fill, width=width)

    def _draw_polygon(self, points: List[Tuple[float, float]], fill: Tuple[int, int, int, int]):
        if self.format == "svg":
            self.dwg.add(self.dwg.polygon(points=points, fill=self._svg_color(fill)))
        else:
            self.draw.polygon(points, fill=fill)

    def _draw_halfcircles_yinyang(self, bbox: List[float], fill_left, fill_right):
        if self.format == "svg":
            x0, y0, x1, y1 = bbox
            cx, cy = (x0 + x1) / 2, (y0 + y1) / 2
            r = abs(x1 - x0) / 2
            # Левая половина (через SVG Path arc)
            path_left = f"M {cx},{cy - r} A {r},{r} 0 0,0 {cx},{cy + r} Z"
            self.dwg.add(self.dwg.path(d=path_left, fill=self._svg_color(fill_left)))
            # Правая половина
            path_right = f"M {cx},{cy - r} A {r},{r} 0 0,1 {cx},{cy + r} Z"
            self.dwg.add(self.dwg.path(d=path_right, fill=self._svg_color(fill_right)))
        else:
            self.draw.pieslice(bbox, 90, 270, fill=fill_left)
            self.draw.pieslice(bbox, 270, 90, fill=fill_right)

    def _get_result(self) -> Union[Image.Image, svgwrite.Drawing]:
        return self.dwg if self.format == "svg" else self.img


    def yin_yang(self, radius: int = 150):
        self._setup_canvas((240, 240, 240, 255))
        cx, cy = self.width // 2, self.height // 2

        # Background outline
        self._draw_ellipse([cx - radius, cy - radius, cx + radius, cy + radius], fill=(0,0,0,0), outline=(0, 0, 0, 255), width=2)

        self._draw_halfcircles_yinyang([cx - radius, cy - radius, cx + radius, cy + radius], (0, 0, 0, 255), (255, 255, 255, 255))

        # Left (Black) and Right (White) halves
        self._draw_ellipse([cx - radius//2, cy - radius, cx + radius//2, cy], fill=(255, 255, 255, 255))
        self._draw_ellipse([cx - radius//2, cy, cx + radius//2, cy + radius], fill=(0, 0, 0, 255))

        dot_r = radius // 6
        self._draw_ellipse([cx - dot_r, cy - radius//2 - dot_r, cx + dot_r, cy - radius//2 + dot_r], fill=(0, 0, 0, 255))
        self._draw_ellipse([cx - dot_r, cy + radius//2 - dot_r, cx + dot_r, cy + radius//2 + dot_r], fill=(255, 255, 255, 255))

        return self._get_result()

    def flowers(self, num_flowers: int = 20, petal_count: int = 6):
        self._setup_canvas((255, 255, 255, 255))
        palette = self._random_palette(4)

        for _ in range(num_flowers):
            cx = self.rng.integers(50, self.width - 50)
            cy = self.rng.integers(50, self.height - 50)
            size = self.rng.integers(20, 80)
            color = palette[self.rng.integers(0, len(palette))]

            for i in range(petal_count):
                angle = (i / petal_count) * 2 * np.pi
                px = cx + np.cos(angle) * size
                py = cy + np.sin(angle) * size
                self._draw_ellipse([px - size//2, py - size//2, px + size//2, py + size//2], fill=color)

            self._draw_ellipse([cx - size//4, cy - size//4, cx + size//4, cy + size//4], fill=(255, 220, 0, 255))
        return self._get_result()

    def pseudocalligraphy(self, stroke_count: int = 50):
        self._setup_canvas((245, 245, 240, 255))
        palette = self._random_palette(3)

        for _ in range(stroke_count):
            x1 = self.rng.integers(0, self.width)
            y1 = self.rng.integers(0, self.height)
            x2 = x1 + self.rng.integers(-100, 100)
            y2 = y1 + self.rng.integers(-200, 200)
            color = palette[self.rng.integers(0, len(palette))]
            width = int(self.rng.integers(2, 12))

            self._draw_line([x1, y1, x2, y2], fill=color, width=width)

            if self.rng.random() > 0.5:
                cx = (x1 + x2) // 2 + self.rng.integers(-30, 30)
                cy = (y1 + y2) // 2 + self.rng.integers(-30, 30)
                r = width * 2
                self._draw_ellipse([cx - r, cy - r, cx + r, cy + r], fill=color)
        return self._get_result()

    def cross_stitch(self, grid_size: int = 20):
        cols = self.width // grid_size
        rows = self.height // grid_size
        self.width = cols * grid_size
        self.height = rows * grid_size
        self._setup_canvas((255, 255, 255, 255))

        palette = self._random_palette(6)

        for row in range(rows):
            for col in range(cols):
                if self.rng.random() > 0.3:
                    color = palette[self.rng.integers(0, len(palette))]
                    cx = col * grid_size + grid_size // 2
                    cy = row * grid_size + grid_size // 2
                    size = grid_size // 3

                    for dx, dy in [(-1, -1), (1, -1), (-1, 1), (1, 1)]:
                        x1, y1 = cx + dx * size, cy + dy * size
                        x2, y2 = cx - dx * size, cy - dy * size
                        self._draw_line([x1, y1, x2, y2], fill=color, width=2)
        return self._get_result()

    def mandala(self, rings: int = 5):
        self._setup_canvas((255, 255, 255, 255))
        cx, cy = self.width // 2, self.height // 2
        palette = self._random_palette(5)

        for ring in range(rings):
            radius = (ring + 1) * 40
            petals = (ring + 1) * 4 + 4
            color = palette[ring % len(palette)]

            for i in range(petals):
                angle = (i / petals) * 2 * np.pi
                for r in range(radius - 20, radius + 1, 5):
                    x = cx + np.cos(angle) * r
                    y = cy + np.sin(angle) * r
                    size = 3 + ring
                    self._draw_ellipse([x - size, y - size, x + size, y + size], fill=color)
        return self._get_result()

    def geometric_tiles(self, tile_size: int = 40):
        cols = self.width // tile_size
        rows = self.height // tile_size
        self.width = cols * tile_size
        self.height = rows * tile_size
        self._setup_canvas((255, 255, 255, 255))

        palette = self._random_palette(4)

        for row in range(rows):
            for col in range(cols):
                color = palette[self.rng.integers(0, len(palette))]
                x, y = col * tile_size, row * tile_size
                shape = self.rng.integers(0, 4)

                if shape == 0:
                    self._draw_rectangle([x, y, x + tile_size, y + tile_size], fill=color)
                elif shape == 1:
                    self._draw_ellipse([x, y, x + tile_size, y + tile_size], fill=color)
                elif shape == 2:
                    self._draw_polygon([(x, y + tile_size), (x + tile_size, y + tile_size), (x + tile_size // 2, y)], fill=color)
                else:
                    self._draw_polygon([(x, y), (x + tile_size, y), (x + tile_size, y + tile_size), (x, y + tile_size)], fill=color)
        return self._get_result()

    def waves(self, amplitude: int = 50, frequency: float = 0.02):
        self._setup_canvas((240, 248, 255, 255))
        palette = self._random_palette(5)

        for layer in range(5):
            color = palette[layer]
            y_offset = layer * self.height // 5
            amp = amplitude * (1 + layer * 0.5)
            phase = self.rng.uniform(0, 2 * np.pi)

            for x in range(0, self.width, 2):
                y = y_offset + int(amp * np.sin(frequency * x + phase))
                self._draw_ellipse([x - 2, y - 2, x + 2, y + 2], fill=color)
        return self._get_result()


def generate(pattern: str = "flowers", output: str = "output.png",
             width: int = 800, height: int = 600, seed: Optional[int] = None,
             format: str = "png"):

    if pattern not in PatternGenerator.AVAILABLE_PATTERNS:
        raise ValueError(f"Unknown pattern: {pattern}. Available: {PatternGenerator.AVAILABLE_PATTERNS}")

    gen = PatternGenerator(width, height, seed, format)

    generate_method = getattr(gen, pattern)
    result = generate_method()

    if format == "svg":
        result.saveas(output)
    else:
        result.save(output)

    return result
