from PIL import Image
import numpy as np
from typing import Union
from world_maker.data_analysis import handle_import_image
from random import randint


class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height


class Bin:
    def __init__(self, grid):
        self.grid = grid
        self.rectangles = []

    def place_rectangle(self, rectangle):
        best_spot = None
        best_spot_empty_area = float('inf')

        for i in range(len(self.grid[0]) - rectangle.width + 1):
            for j in range(len(self.grid) - rectangle.height + 1):
                if self.can_place(rectangle, i, j):
                    empty_area = self.calculate_empty_area(rectangle, i, j)
                    if empty_area < best_spot_empty_area:
                        best_spot = (i, j)
                        best_spot_empty_area = empty_area

        if best_spot is not None:
            self.rectangles.append(
                (best_spot, (best_spot[0] + rectangle.width, best_spot[1] + rectangle.height)))
            self.update_grid(rectangle, *best_spot)
            return True

        return False

    def calculate_empty_area(self, rectangle, x, y):
        empty_area = 0
        for rect_x in range(x, x + rectangle.width):
            for rect_y in range(y, y + rectangle.height):
                if self.grid[rect_y][rect_x]:
                    empty_area += 1
        return empty_area

    def can_place(self, rectangle, x, y):
        for rect_x in range(x, x + rectangle.width):
            for rect_y in range(y, y + rectangle.height):
                if not self.grid[rect_y][rect_x]:
                    return False
        return True

    def update_grid(self, rectangle, x, y):
        for rect_x in range(x, x + rectangle.width):
            for rect_y in range(y, y + rectangle.height):
                self.grid[rect_y][rect_x] = False


def generate_rectangle(min_width: int = 10, max_width: int = 25):
    width = randint(min_width, max_width)
    height = randint(min_width, max_width)
    return Rectangle(width, height)


def pack_rectangles(grid, min_width: int = 10, max_width: int = 25):
    bin = Bin(grid)
    while True:
        rectangle = generate_rectangle(min_width, max_width)
        print(f"[Pack rectangles] Number of rectangles: {len(bin.rectangles)}")
        if not bin.place_rectangle(rectangle) or len(bin.rectangles) >= 3000:
            break
    return bin.rectangles


def draw_rectangles(rectangles, grid, heightmap):
    heightmap = handle_import_image(heightmap).convert('L')
    image = Image.new('L', (len(grid[0]), len(grid)), 0)
    for rectangle in rectangles:
        start, end = rectangle
        height = []
        for x in range(start[0], end[0]):
            for y in range(start[1], end[1]):
                height.append(heightmap.getpixel((x, y)))
        height_average = sum(height)/len(height)
        for x in range(start[0], end[0]):
            for y in range(start[1], end[1]):
                image.putpixel((x, y), round(height_average))
    return image


def area_of_rectangles(rectangles):
    area = 0
    for rectangle in rectangles:
        start, end = rectangle
        area += abs((end[0] - start[0]) * (end[1] - start[1]))
    return area


def generate_building(image: str | Image.Image, heightmap: str | Image.Image, output: str = './world_maker/data/building.png',
                      number_of_try: int = 1, min_width: int = 10, max_width: int = 25):
    print("[Building] Start generating building position...")
    image = handle_import_image(image).convert('L')
    rectangles_output = []
    for n in range(number_of_try):
        print("[Building] Try", n+1)
        grid = np.array(image)
        rectangles = pack_rectangles(grid, min_width, max_width)
        print("[Building] Number of building:", len(rectangles))
        print("[Building] Area of building:", area_of_rectangles(rectangles))
        if area_of_rectangles(rectangles) > area_of_rectangles(rectangles_output):
            rectangles_output = rectangles
    draw_rectangles(rectangles_output, grid, heightmap).save(output)
    return rectangles_output
