#!~/.pyenv/versions/3.11.6/bin/python
#
# Copyright (c) 2024 Cutieguwu | Olivia Brooks
#
# -*- coding:utf-8 -*-
# @Title: Sections of layers in a game.
# @Author: Cutieguwu | Olivia Brooks
# @Email: owen.brooks77@gmail.com | obroo2@ocdsb.ca
# @Description: Classes descibing different sections in a pygame game.
#
# @Script: section.py
# @Date Created: 16 Apr, 2024
# @Last Modified: 19 Jun, 2024
# @Last Modified by: Cutieguwu | Olivia Brooks
# ----------------------------------------------------------

from icecream import ic
from random import randint
from pygame.image import load
from pygame.transform import scale
from lib import effect
from lib.system import DIRWORKING

class Section():
    """
    A 16x16 area of the layer.
    If this section can be interacted with by a player, `Section` will store the function.
    """

    def __init__(self, game):
        self.GAME = game                                                                # Cannot shortcut self.LEVEL due to Section befing used as part the init
        self.SCALE = game.SCALE
        self.WINDOW = game.WINDOW
    
    def draw(self, layout):
        """
        Draw the section-built layout on the screen.
        """

        blocksInView = {}
        blocksDrawn = 0
        is_sorted = False
        layers = []
        currentLayerIndex = 0

        # Improve processing time, only blocks on display will be drawn.
        # Improve engine to support drawing areas with a single block, and not blitting blocks without alpha over blocks of lower layers.
        for coords, block in layout.items():
            try:                                                                        # Does this block define a range fill.
                for y in range(coords[1], coords[3] + 1):
                    if y in range(self.GAME.LEVEL.dy, self.GAME.LEVEL.dy + self.SCALE.gameY + 1):   # Only try to blit blocks on a displayed row.
                        for x in range(coords[0], coords[2] + 1):
                            if x in range(self.GAME.LEVEL.dx, self.GAME.LEVEL.dx + self.SCALE.gameX + 1):
                                blocksInView[(x, y, block.layer)] = block               # Add block layer to prevent overwriting blocks underneath.

                                if block.layer not in layers:                           # Get all the layers present of displayed blocks
                                    layers.append(block.layer)

            except IndexError:
                if coords[0] in range(self.GAME.LEVEL.dx, self.GAME.LEVEL.dx + self.SCALE.gameX + 1) and coords[1] in range(self.GAME.LEVEL.dy, self.GAME.LEVEL.dy + self.SCALE.gameY + 1): # If block is in display area.

                    blocksInView[coords] = block

                    if block.layer not in layers:                                       # Get all the layers present of displayed blocks
                        layers.append(block.layer)

        while not is_sorted:                                                            # Sort layers to draw their blocks at the correct heights.
            is_sorted = True
            for i in range(0, len(layers) - 1):
                if layers[i] > layers[i + 1]:
                    is_sorted = False

                    temp = layers[i]
                    layers[i] = layers[i + 1]
                    layers[i + 1] = temp

        while blocksDrawn < len(blocksInView):                                          # While there are blocks still not drawn
            for location, block in blocksInView.items():                                # Go through each block and draw it if it is on the layer currently being drawn.
                if block.layer == layers[currentLayerIndex]:                            # Use a list of layers so that if no blocks in view are on a layer, no time is wasted parsing blocks on a layer that doesn't show.

                    x = (location[0] - self.GAME.LEVEL.dx) * self.SCALE.unitBlockPx     # Get its x, y location in pixels.
                    y = (location[1] - self.GAME.LEVEL.dy) * self.SCALE.unitBlockPx
                    if block.TEXTURE[block.state]["IS_ANIMATED"]:                       # Get the current texture frame for animated textures.
                        block.animationClock = block.animationClock + 1

                        if block.animationClock >= (self.GAME.clock.get_fps() * block.TEXTURE[block.state]["TIME_PER_FRAME"]):  # If the time in seconds passed is equal or greater than the time per frame of the animated texture

                            block.animationClock = 0
                            textures = len(block.TEXTURE[block.state]["texture"])
                            currentTexture = block.textureIndex                                                                 # Fetch value to marginally reduce time to reach value repeatedly from dict.

                            if (currentTexture + 1) > (textures - 1):
                                block.textureIndex = 0

                                if block.TEXTURE[block.state]["ANIMATION_REPEATS"] != -1:                                       # If a limited animation
                                    block.animationRepeats = block.animationRepeats + 1
                                    if block.animationRepeats == block.TEXTURE[block.state]["ANIMATION_REPEATS"]:               # Block has animated for its state's full time.
                                        Section.update_state(block)                                                             # Cannot call self.update_state for some reason.
                                                                                                                                # Causes two positional arugments by witchcraft.

                            else:
                                block.textureIndex = currentTexture + 1

                        texture = block.TEXTURE[block.state]["texture"][block.textureIndex]

                    else:                                                               # Otherwise print its static texture for its state.
                        texture = block.TEXTURE[block.state]["texture"][0]
                    
                    if texture.get_width() != self.SCALE.unitBlockPx:
                        texture = scale(texture, (self.SCALE.unitBlockPx, self.SCALE.unitBlockPx))

                    self.WINDOW.window.blit(texture, (x, y))

                    blocksDrawn = blocksDrawn + 1

                    #ic(f"Printed blocks {blocksDrawn}/{len(blocksInView)}")

            currentLayerIndex = currentLayerIndex + 1

    def update_state(section):
        """
        Updates the state of the section.
        """
        
        section.state = section.TEXTURE[section.state]["FOLLOW_UP_STATE"]
        section.textureIndex = 0

class Textures():
    """
    Contains all textures used by the game.
    Not the most memory-efficient. May want to rework this simple memory hog.
    """

    def __init__(self, unitBlockPx):

        self.load_textures()

        self.update_scale(unitBlockPx)

    def load_textures(self):
        """
        Loads textures from file.
        """

        BLOCKSDIR = DIRWORKING + "/data/image/game/blocks/"

        self.TEXTURES = {
        "BLOCK_BLUE": load(BLOCKSDIR + "wool_colored_blue.png").convert(),
        "BLOCK_BROWN": load(BLOCKSDIR + "wool_colored_brown.png").convert(),
        "BLOCK_CYAN": load(BLOCKSDIR + "wool_colored_cyan.png").convert(),
        "BLOCK_GRAY": load(BLOCKSDIR + "wool_colored_gray.png").convert(),
        "BLOCK_GREEN": load(BLOCKSDIR + "wool_colored_green.png").convert(),
        "BLOCK_LIGHT_BLUE": load(BLOCKSDIR + "wool_colored_light_blue.png").convert(),
        "BLOCK_LIME": load(BLOCKSDIR + "wool_colored_lime.png").convert(),
        "BLOCK_MAGENTA": load(BLOCKSDIR + "wool_colored_magenta.png").convert(),
        "BLOCK_ORANGE": load(BLOCKSDIR + "wool_colored_orange.png").convert(),
        "BLOCK_PINK": load(BLOCKSDIR + "wool_colored_pink.png").convert(),
        "BLOCK_PURPLE": load(BLOCKSDIR + "wool_colored_purple.png").convert(),
        "BLOCK_RED": load(BLOCKSDIR + "wool_colored_red.png").convert(),
        "BLOCK_SILVER": load(BLOCKSDIR + "wool_colored_silver.png").convert(),
        "BLOCK_WHITE": load(BLOCKSDIR + "wool_colored_white.png").convert(),
        "BLOCK_YELLOW": load(BLOCKSDIR + "wool_colored_yellow.png").convert(),
        "PUDDLE_SOULS_RICH": load(BLOCKSDIR + "puddle_souls/puddle_souls_rich.png").convert_alpha(),
        "PUDDLE_SOULS_SPENT": load(BLOCKSDIR + "puddle_souls/puddle_souls_spent.png").convert_alpha(),
        "PUDDLE_BLOOD_0": load(BLOCKSDIR + "puddle_blood/puddle_blood_0.png").convert_alpha(),
        "PUDDLE_BLOOD_1": load(BLOCKSDIR + "puddle_blood/puddle_blood_1.png").convert_alpha(),
        "PUDDLE_BLOOD_2": load(BLOCKSDIR + "puddle_blood/puddle_blood_2.png").convert_alpha(),
        "PUDDLE_BLOOD_3": load(BLOCKSDIR + "puddle_blood/puddle_blood_3.png").convert_alpha(),
        "PUDDLE_BLOOD_4": load(BLOCKSDIR + "puddle_blood/puddle_blood_4.png").convert_alpha(),
        "PUDDLE_BLOOD_NEUTRAL": load(BLOCKSDIR + "puddle_blood/puddle_blood_neutral.png").convert_alpha()
        }
    
    def update_scale(self, unitBlockPx):
        """
        Updates the scaling of all textures.
        """

        if unitBlockPx > self.TEXTURES["BLOCK_BLUE"].get_width():
            self.load_textures()

        temp = {}

        for texture in self.TEXTURES.items():
            temp[texture[0]] = scale(texture[1], (unitBlockPx, unitBlockPx))
        
        self.TEXTURES = temp

class Blocks():
    """
    Contains all section block definitions.
    """

    """
    class block_demo():
        def __init__(self):
            self.TEXTURE        :dict   =   {
                                            "neutral":                                  # Should be used as standard naming.
                                                                                        # NOTE: Can remove keys that are not in STATES, unlike shown.
                                                {
                                                "texture": [
                                                    Textures.TEXTURES["BLOCK_EXAMPLE_0"],
                                                    Textures.TEXTURES["BLOCK_EXAMPLE_1"]
                                                ], 
                                                "IS_ANIMATED":      True,               # Does this state has an animation, if so execute additional code to iterate through texture list.
                                                "ANIMATION_REPEATS":-1,                 # Value of -1 indicates that animation runs infinitely.
                                                                                        # Can be left out if not IS_ANIMATED.
                                                                                        # Indicates the number of times the animation will loop in this state before passing to FOLLOW_UP_STATE.
                                                "IS_INTERACTABLE":  True,               # Can be interacted with by the player during this state.
                                                "IS_COLLIDABLE":    False,              # Player and entities will collide with block during this state.
                                                "TIME_PER_FRAME":   1.0,                # Amount of time (s) given to display each frame. Can be left out if not IS_ANIMATED.
                                                "FOLLOW_UP_STATE":  "on_trigger"        # State that the block should progress to next. Can be left out if not IS_INTERACTABLE.
                                                },
                                            "on_trigger":                               # Should be used as standard naming.
                                                {
                                                "texture": [
                                                    Textures.TEXTURES["BLOCK_EXAMPLE"]
                                                ],
                                                "IS_ANIMATED":      False,
                                                "IS_INTERACTABLE":  False,
                                                "IS_COLLIDABLE":    False,
                                                "TIME_PER_FRAME":   1.0,                # TFP remains due to special temporary nature of on_trigger and off_trigger states.
                                                "FOLLOW_UP_STATE":  "while_trigger"     # FUS remains due to special temporary nature of on_trigger and off_trigger states.
                                                },
                                            "while_trigger":                            # Should be used as standard naming.
                                                {
                                                "texture": [
                                                    Textures.TEXTURES["BLOCK_EXAMPLE"]
                                                ], 
                                                "IS_ANIMATED":      False,
                                                "IS_INTERACTABLE":  True,
                                                "IS_COLLIDABLE":    False,
                                                "TIME_PER_FRAME":   1.0,
                                                "FOLLOW_UP_STATE":  "off_trigger"
                                                },
                                            "off_trigger":                             # Should be used as standard naming.
                                                {
                                                "texture": [
                                                    Textures.TEXTURES["BLOCK_EXAMPLE"]
                                                ],
                                                "IS_ANIMATED":      False,
                                                "IS_INTERACTABLE":  False,
                                                "IS_COLLIDABLE":    False,
                                                "TIME_PER_FRAME":   1.00,
                                                "FOLLOW_UP_STATE":  "neutral",
                                                "ANIMATION_REPEATS":1
                                                }
                                            }
            self.state          :str    =   "neutral",                                  # Variable for tracking the current texture.
            self.textureIndex   :int    =   "textureIndex": 0,                          # Tracks the index of the current animation frame.
            self.animationClock :int    =   "animationClock": 0                         # Used for tracking the time in frames, that a frame has been shown.
            self.animationRepeats:int   =   0                                           # Number of times that a limited animation has been run.
            self.is_triggered   :bool   =   False                                       # Only needed if block in current state IS_INTERACTABLE
            self.time           :float  =   0.0                                         # Used to track how long each frame has been displayed for animated textures.
            self.layer          :int    =   0                                           # Used to track the layer of the block. For example, a door will be layer 1.
                                                                                        # while the floor below it is layer 0, so door gets interaction check priority.
        def on_interact(self):
            '''
            Set of actions to execute upon interaction.
            '''
    """

    class block_blue():
        def __init__(self, layer:int = 0):
            self.TEXTURE = {
                "neutral":{
                    "texture": [
                        Textures.TEXTURES["BLOCK_BLUE"]
                    ],
                    "IS_ANIMATED": False,
                    "IS_INTERACTABLE": False,
                    "IS_COLLIDABLE": False
                    }
                }
            self.state = "neutral"
            self.layer = layer

    class block_rainbow():
        def __init__(self, layer:int = 1):
            self.TEXTURE = {
                "neutral": {
                    "texture": [
                        Textures.TEXTURES["BLOCK_RED"],
                        Textures.TEXTURES["BLOCK_ORANGE"],
                        Textures.TEXTURES["BLOCK_YELLOW"],
                        Textures.TEXTURES["BLOCK_LIME"],
                        Textures.TEXTURES["BLOCK_GREEN"],
                        Textures.TEXTURES["BLOCK_CYAN"],
                        Textures.TEXTURES["BLOCK_LIGHT_BLUE"],
                        Textures.TEXTURES["BLOCK_BLUE"],
                        Textures.TEXTURES["BLOCK_PURPLE"],
                        Textures.TEXTURES["BLOCK_MAGENTA"],
                        Textures.TEXTURES["BLOCK_PINK"]
                    ],
                    "IS_ANIMATED": True,
                    "ANIMATION_REPEATS": -1,
                    "IS_INTERACTABLE": True,
                    "IS_COLLIDABLE": True,
                    "TIME_PER_FRAME": 1.0,
                    "FOLLOW_UP_STATE": "on_trigger"
                },
                "on_trigger": {
                    "texture": [
                        Textures.TEXTURES["BLOCK_YELLOW"],
                        Textures.TEXTURES["BLOCK_ORANGE"],
                        Textures.TEXTURES["BLOCK_RED"]
                    ],
                    "IS_ANIMATED": True,
                    "IS_INTERACTABLE": False,
                    "IS_COLLIDABLE": True,
                    "TIME_PER_FRAME": 1.0,
                    "ANIMATION_REPEATS": 5,
                    "FOLLOW_UP_STATE": "while_trigger"
                },
                "while_trigger": {
                    "texture": [
                        Textures.TEXTURES["BLOCK_PINK"],
                        Textures.TEXTURES["BLOCK_MAGENTA"],
                        Textures.TEXTURES["BLOCK_PURPLE"],
                        Textures.TEXTURES["BLOCK_BLUE"],
                        Textures.TEXTURES["BLOCK_LIGHT_BLUE"],
                        Textures.TEXTURES["BLOCK_CYAN"],
                        Textures.TEXTURES["BLOCK_GREEN"],
                        Textures.TEXTURES["BLOCK_LIME"],
                        Textures.TEXTURES["BLOCK_YELLOW"],
                        Textures.TEXTURES["BLOCK_ORANGE"],
                        Textures.TEXTURES["BLOCK_RED"]
                    ],
                    "IS_ANIMATED": True,
                    "ANIMATION_REPEATS": -1,
                    "IS_INTERACTABLE": False,
                    "IS_COLLIDABLE": True,
                    "TIME_PER_FRAME": 1.0
                }
            }
            self.state = "neutral"
            self.textureIndex = 0
            self.animationClock = 0
            self.animationRepeats = 0
            self.layer = layer

        def on_interact(self):
            """
            Set of actions to execute on interaction
            """

            Section.update_state(section=self)
    
    class puddle_of_souls():
        def __init__(self, game, layer:int = 1):
            self.TEXTURE = {
                "neutral": {
                    "texture": [
                        Textures.TEXTURES["PUDDLE_SOULS_RICH"]
                    ],
                    "IS_ANIMATED": False,
                    "IS_INTERACTABLE": True,
                    "IS_COLLIDABLE": True,
                    "FOLLOW_UP_STATE": "while_trigger"
                },
                "while_trigger": {
                    "texture": [
                        Textures.TEXTURES["PUDDLE_SOULS_SPENT"]
                    ],
                    "IS_ANIMATED": False,
                    "IS_INTERACTABLE": False,
                    "IS_COLLIDABLE": False,
                }
            }
            self.state = "neutral"
            self.layer = layer

            self.GAME = game

        def on_interact(self):
            """
            Set of actions to execute on interaction
            """

            Section.update_state(section=self)

            if randint(1, 100) <= 25:
                self.GAME.PLAYER.health = self.GAME.PLAYER.health + 25

    class puddle_of_blood():
        def __init__(self, game, layer:int = 1):
            self.TEXTURE = {
                "on_death": {
                    "texture": [
                        Textures.TEXTURES["PUDDLE_BLOOD_0"],
                        Textures.TEXTURES["PUDDLE_BLOOD_1"],
                        Textures.TEXTURES["PUDDLE_BLOOD_2"],
                        Textures.TEXTURES["PUDDLE_BLOOD_3"],
                        Textures.TEXTURES["PUDDLE_BLOOD_4"]
                    ],
                    "IS_ANIMATED": True,
                    "ANIMATION_REPEATS": 1,
                    "IS_INTERACTABLE": True,
                    "IS_COLLIDABLE": False,
                    "TIME_PER_FRAME": 0.25,
                    "FOLLOW_UP_STATE": "neutral"
                },
                "neutral": {
                    "texture": [
                        Textures.TEXTURES["PUDDLE_BLOOD_NEUTRAL"]
                    ],
                    "IS_ANIMATED": False,
                    "IS_INTERACTABLE": True,
                    "IS_COLLIDABLE": False
                }
            }
            self.state = "on_death"
            self.textureIndex = 0
            self.animationClock = 0
            self.animationRepeats = 0
            self.layer = layer

            self.GAME = game

        def on_interact(self):
            """
            Set of actions to execute on interaction.
            """

            self.GAME.PLAYER.effects.append(effect.War_Paint(self.GAME, self.GAME.PLAYER.character))