from coopgame.gameTemplate import GameTemplate
from cooptools.colors import Color
import pygame
from coopstructs.vectors import Vector2
from noise import snoise2, pnoise3
import math
from coopbugger.monitoredclass import MonitoredClass
import logging
from coopstructs.toggles import BooleanToggleable
import numpy as np
import cooptools.marchingSquares as msqrs
from coopgame.tilemap import TileSet, Tilemap


REZ_X = 50
REZ_Y = 50
MAX_SIZE = 20


class Particle():
    def __init__(self, pos: Vector2, value, size, color = None):
        self.pos = pos
        self.size = size
        self.thickness = 1
        self.value = value
        self.color = color

    def display(self, screen: pygame.Surface, color = None):
        if color is None:
            color = self.color
        pygame.draw.circle(screen, color, (self.pos.x, self.pos.y), self.size, self.thickness)

class MarchingSquaresGame(GameTemplate, MonitoredClass):
    def __init__(self):
        GameTemplate.__init__(self, fullscreen=False)
        MonitoredClass.__init__(self)
        self.cols = REZ_X
        self.rows = REZ_Y

        self.xoff = 0
        self.yoff = 0
        self.zoff = 0
        self.xyincrement = .002
        self.zincrement = 0

        self.noise_scale = max(self.rows, self.cols)/5
        self.particles = []

        self._blitables = {}
        self.show_overlap = False
        self.show_lines = BooleanToggleable(default=False)
        self._init_blitables()


        self.create_particles()

        self.line_surface_layers = []
        self.lines_surface = None
        self.lines_surface_offset = Vector2(0, 0)

        self._outline_tileset = None
        self.init_tileset()
        self._outline_tilemap = None

    def _init_blitables(self):
        for ii in range(1, MAX_SIZE):
            blitable = pygame.Surface(size=(ii * 2, ii*2))
            if self.show_overlap:
                blitable.set_colorkey(Color.BLACK.value)
            color = (0, 0, int(255 * (ii / MAX_SIZE)))
            pygame.draw.circle(blitable, color, (ii, ii), ii, 1)
            self._blitables[ii] = blitable

    @MonitoredClass.timer
    def draw(self, frames:int, debug_mode: bool = False):
        self.xoff += self.xyincrement
        self.yoff += self.xyincrement
        self.zoff += self.zincrement

        self.update_particles()

        for row in self.particles:
            for particle, color in row:
                self.screen.blit(self._blitables[particle.size], (particle.focal_point.x, particle.focal_point.y))

        if self.show_lines.value:
            self.draw_lines()

        if frames % 100 == 0:
            print(self.tracked_time)

    @MonitoredClass.timer
    def build_line_surfaces(self):
        if self.zincrement != 0:
            return None

        surfaces = []

        # Move existing surfaces
        for surface, offset in self.line_surface_layers:
            offset += Vector2(-1, -1) / self.noise_scale * ((REZ_Y + REZ_X) / 2)
            if offset.x > -self.screen.get_width() and offset.y > -self.screen.get_height():
                surfaces.append((surface, offset))

        # create new surface
        lines_surface = pygame.Surface((self.screen.get_width(), self.screen.get_height()), pygame.SRCALPHA)
        lines_surface = lines_surface.convert_alpha()
        if len(self.line_surface_layers) == 0:
            self.draw_lines_to_surface(lines_surface)
            surfaces.append((lines_surface, Vector2(0, 0)))
        elif self.line_surface_layers[-1][1].length() >= (Vector2(REZ_X, REZ_Y).length()):
            self.draw_lines_to_surface(lines_surface, only_last=True)
            surfaces.append((lines_surface, Vector2(0, 0)))

        return surfaces


    def handle_left_mouse_down(self, pressed_keys, mouse_pos: Vector2):
        self.show_overlap = not self.show_overlap
        self._init_blitables()

    def handle_right_mouse_up(self, pressed_keys, mouse_pos_as_vector: Vector2):
        self.show_lines.toggle()

    @MonitoredClass.timer
    def update_particles(self):

        if self.particles is None or len(self.particles) == 0:
            self.create_particles()

        octaves = 6
        persistence = .5
        lacunarity = 2


        for ii in range(0, self.cols):
            # self.particles.append([])
            for jj in range(0, self.rows):
                value = pnoise3(
                    (ii) / self.noise_scale + self.xoff,
                    (jj) / self.noise_scale + self.yoff,
                    self.zoff,
                    octaves=octaves,
                    persistence=persistence,
                    lacunarity=lacunarity,
                    repeatx=1024,
                    repeaty=1024,
                    base=0)
                color = (0, 0, int(255 * (value + 1 / 2)))

                self.particles[ii][jj][0].value = value
                self.particles[ii][jj][0].size = max(int((value + 1 / 2) * MAX_SIZE), 1)
                self.particles[ii][jj][0].color = color



    @MonitoredClass.timer
    def create_particles(self):
        self.particles.clear()

        for ii in range(0, self.cols):
            self.particles.append([])
            for jj in range(0, self.rows):
                self.particles[ii].append(
                    (Particle(Vector2(ii * self.sq_width, jj * self.sq_height), 0, 1), Color.WHITE))

    @MonitoredClass.timer
    def get_marching_squares_state_array(self):
        # get an array of the value of particles
        part_value_arr = np.array(
            [[particle[0].value for particle in row] for row in self.particles]
        ).transpose()

        # get the state of each square
        line_defs = msqrs.marching_squares(part_value_arr, qualifier=lambda x: math.ceil(x) > 0, ret_type=msqrs.MarchingSquaresReturnType.STATE)

        # convert the dict to an np array containing states that map to tileset
        max_0 = max(x[0] for x in line_defs.keys())
        max_1 = max(x[1] for x in line_defs.keys())
        new_arr = np.zeros((max_0 + 1, max_1 + 1), dtype=int)
        for key, val in line_defs.items():
            new_arr[key[0]][key[1]] = val

        return new_arr

    @MonitoredClass.timer
    def draw_lines_to_surface(self, surface: pygame.Surface, only_last=False):
        counter = 0

        new_arr = self.get_marching_squares_state_array()

        # create a tilemap
        self._outline_tilemap = Tilemap(self._outline_tileset, map=new_arr, surface_size=(self.screen.get_width(), self.screen.get_height()))

        # blit tilemap to the screen
        self.screen.blit(self._outline_tilemap.image, self._outline_tilemap.rect)

        logging.debug(f"{counter} lines drawn")

    @MonitoredClass.timer
    def draw_lines(self):
        if self.line_surface_layers is not None and len(self.line_surface_layers) > 0:
            print(len(self.line_surface_layers))
            for surface, offset in self.line_surface_layers:
                self.screen.blit(surface, (offset.x, offset.y))
        else:
            self.draw_lines_to_surface(self.screen)

    def init_tileset(self):

        tiles = []

        mdpts = msqrs.square_midpoints(0, 0, self.sq_width, self.sq_height)

        for state, condition in msqrs.state_mapper.items():
            new_surf = pygame.Surface((self.sq_width, self.sq_height))
            new_surf.fill(Color.HOT_PINK.value)
            new_surf.set_colorkey(Color.HOT_PINK.value)
            new_surf.convert_alpha()
            for line in condition:
                pygame.draw.line(new_surf, Color.WHITE.value, mdpts[line[0]], mdpts[line[1]])
            tiles.append(new_surf)

        self._outline_tileset = TileSet(tiles=tiles)

    @property
    def sq_width(self):
        return self.screen_width / REZ_X

    @property
    def sq_height(self):
        return self.screen_height / REZ_Y

if __name__ == "__main__":
    game = MarchingSquaresGame()
    game.main()