# -*- coding: utf-8 -*-
"""
Updated on Wed Jul 29 07:00:30 2020

@author: Qaisar TanvirS
"""
import time
import os
import operator
from cellular_automaton import automaton


"""
Copyright 2019 Richard Feistenauer

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""


class _Rect:
    def __init__(self, left=0, top=0, width=0, height=0, rect=None, pos=None, size=None):
        if rect is not None and (pos is not None or size is not None):
            raise ValueError("define either rect OR position and size OR left, top, width and height")
        if not (left == top == width == height == 0) and not(rect == pos == size is None):
            raise ValueError("define either rect OR position and size OR left, top, width and height")

        self.__direct_initialisation(height, left, top, width)
        self.__pos_and_size_initialisation(pos, size)
        self.__rect_initialisation(rect)

    def __rect_initialisation(self, rect):
        if rect is not None:
            self.__direct_initialisation(rect[1][1], rect[0][0], rect[0][1], rect[1][0])

    def __pos_and_size_initialisation(self, pos, size):
        if pos is not None:
            self.left = pos[0]
            self.top = pos[1]

        if size is not None:
            self.width = size[0]
            self.height = size[1]

    def __direct_initialisation(self, height, left, top, width):
        self.left = left
        self.top = top
        self.width = width
        self.height = height

    def get_rect_tuple(self):
        return (self.left, self.top), (self.width, self.height)


class DrawEngine(object):
    def __init__(self, window_size=None, *args, **kwargs):
        super().__init__(*args, **kwargs)
        global pygame
        import pygame
        pygame.init()
        pygame.display.set_caption("Cellular Automaton")
        self.__screen = pygame.display.set_mode(window_size)
        self.__font = pygame.font.SysFont("monospace", 15)

        self._width = window_size[0]
        self._height = window_size[1]

    def write_text(self, pos, text, color=(0, 255, 0)):
        label = self.__font.render(text, 1, color)
        update_rect = self.__screen.blit(label, pos)
        DrawEngine.update_rectangles(update_rect)

    def fill_surface_with_color(self, rect, color=(0, 0, 0)):
        return self.__screen.fill(color, rect)

    @staticmethod
    def update_rectangles(rectangles):
        pygame.display.update(rectangles)

    
    def get_screen_state(self):
        return self.__screen
    
    @staticmethod
    def is_active():
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                return False
        return True


class _CASurface:
    def __init__(self,
                 grid_rect,
                 cellular_automaton: automaton.CellularAutomatonProcessor,
                 draw_engine,
                 *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._cellular_automaton = cellular_automaton
        self.__rect = grid_rect
        self.__cell_size = self._calculate_cell_display_size()
        self.__draw_engine = draw_engine

    def _calculate_cell_display_size(self):
        grid_dimension = self._cellular_automaton.get_dimension()
        return [self.__rect.width / grid_dimension[0], self.__rect.height / grid_dimension[1]]

    def redraw_cellular_automaton(self):
        """ Redraws those cells which changed their state since last redraw. """
        self.__draw_engine.update_rectangles(list(self.__redraw_dirty_cells()))
        pygame.image.save(self.__draw_engine.get_screen_state(), f"{os.getcwd()}\Images\{time.time()}.jpg")

    def __redraw_dirty_cells(self):
        for coordinate, cell in self._cellular_automaton.get_cells().items():
            if cell.is_set_for_redraw():
                yield from self.__redraw_cell(cell, coordinate)

    def __redraw_cell(self, cell, coordinate):
        cell_color = self.__get_cell_color(cell)
        cell_pos = self._calculate_cell_position_in_the_grid(coordinate)
        surface_pos = self._calculate_cell_position_on_screen(cell_pos)
        cell.was_redrawn()
        yield self._draw_cell_surface(surface_pos, cell_color)

    def __get_cell_color(self, cell):
        return self._cellular_automaton.get_current_rule().get_state_draw_color(
            cell.get_current_state(self._cellular_automaton.get_current_evolution_step()))

    def _calculate_cell_position_in_the_grid(self, coordinate):
        return list(map(operator.mul, self.__cell_size, coordinate))

    def _calculate_cell_position_on_screen(self, cell_pos):
        return [self.__rect.left + cell_pos[0], self.__rect.top + cell_pos[1]]

    def _draw_cell_surface(self, surface_pos, cell_color):
        return self.__draw_engine.fill_surface_with_color((surface_pos, self.__cell_size), cell_color)



class CAWindow(DrawEngine):
    def __init__(self, cellular_automaton: automaton.CellularAutomatonProcessor,
                 evolution_steps_per_draw=1,
                 window_size=(1000, 800),
                 *args, **kwargs):
        super().__init__(window_size=window_size, *args, **kwargs)
        self._ca = cellular_automaton
        self.ca_display = _CASurface(_Rect(pos=(0, 30), size=(window_size[0], window_size[1] - 30)),
                                     self._ca,
                                     self)

        self.__loop_evolution_and_redraw_of_automaton(evolution_steps_per_draw=evolution_steps_per_draw)

    def __loop_evolution_and_redraw_of_automaton(self, evolution_steps_per_draw):
        while super().is_active():
            time_ca_start = time.time()
            self._ca.evolve_x_times(evolution_steps_per_draw)
            time_ca_end = time.time()
            self.ca_display.redraw_cellular_automaton()
            time_ds_end = time.time()
            self.__print_process_duration(time_ca_end, time_ca_start, time_ds_end)

    def __print_process_duration(self, time_ca_end, time_ca_start, time_ds_end):
        super().fill_surface_with_color(_Rect(size=(self._width, 30)).get_rect_tuple())
        super().write_text((10, 5), "CA: " + "{0:.4f}".format(time_ca_end - time_ca_start) + "s")
        super().write_text((310, 5), "Display: " + "{0:.4f}".format(time_ds_end - time_ca_end) + "s")
        super().write_text((660, 5), "Step: " + str(self._ca.get_current_evolution_step()))
