'''
    Basic test scene made using Chelone
'''
import random
import math
import json
import tkinter as tk
from time import time
import sound

from Chelone import init, Sprite, SpriteLoader, check_keys
from BoxPhys import get_collision_displacement


chelone = None
playsound = lambda a: 0
sound_finished = lambda a: False

class Player(Sprite):
    """
        A class for the main character.
        Handles smooth camera, game over, movement and attacks
    """

    grounded = False
    w_pressed = False
    camera_lagbehind = [0.01, 0.05]
    camera_offset = [600, 250]

    def setup(self, kargs):
        print("setup Player")

        # set up anim states
        self.states = {"run": self.run_state, "idle": self.idle_state, "jump": self.jump_state}

        self.grounded = False
        self.w_pressed = False
        self.orientation = "right"

        self.camera_lagbehind = kargs.get("camera_lagbehind", [0.05, 0.05])

        self.camera_offset = kargs.get("camera_offset", [600, 250])

    def update(self):
        if self.anim_state == "None":
            self.update_anim_state('idle')

        self.update_smooth_camera()

    def last_update(self):
        self.grounded = False

    def run_state(self):
        '''the code that runs while the player is running'''
        movement = self.move_on_command()
        if not movement[0] and not movement[1]:
            self.update_anim_state('idle')
        if chelone.settings["jump"] in chelone.pressed_keys and self.grounded:
            self.update_anim_state('jump')
            self.add_vel(0, -30)

    def idle_state(self):
        '''the code that runs while the player is idling'''
        movement = self.move_on_command()
        if movement[0] or movement[1]:
            self.update_anim_state('run')
        if chelone.settings["jump"] in chelone.pressed_keys and self.grounded:
            self.update_anim_state('jump')
            self.add_vel(0, -30)

    def jump_state(self):
        '''the code that runs while the player is jumping'''
        movement = self.move_on_command()#noqa could be used later
        if self.grounded:
            self.update_anim_state('idle')

    def update_smooth_camera(self):
        '''handles updating the camera position smoothly'''
        camera_desired = [
            self.x-self.camera_offset[0],
            self.y-self.camera_offset[1]
        ]

        chelone.camera.move(
            -self.camera_lagbehind[0]*(chelone.camera.x-camera_desired[0]),
            self.camera_lagbehind[1]*(chelone.camera.y-camera_desired[1])
        )

    def move_on_command(self):
        '''handles movement with keypresses'''
        ret = [False, False]

        if chelone.settings['run_left'] in chelone.pressed_keys:
            ret[0] = True
            self.move(-7, 0)

            if self.orientation != "left":
                self.flip()

        elif chelone.settings['run_right'] in chelone.pressed_keys:
            ret[1] = True
            self.move(7, 0)

            if self.orientation != "right":
                self.flip()

        return ret

    def handle_collision(self, collided_obj, my_collider, other_collider, handled=False, displacement=None):
        '''check if we ever intersect ground to be able to jump'''
        if displacement is None:
            displacement = get_collision_displacement(my_collider, other_collider)

        super().handle_collision(collided_obj, my_collider, other_collider, handled, displacement)

        if displacement[1] < 0 and self.vel.y >= 0:
            if my_collider.type != "trigger" and other_collider.type != "trigger":
                self.grounded = True

    def delete_self(self):
        '''
        instead of deleting, we need to pause and restart the game
        TODO: this is a POC, needs to be remade properly
        '''

        print("game_over")
        saved_sprites = chelone._sprites
        chelone._sprites = [{} for i in range(50)]
        game_over = Sprite(
            id="game_over", frame=self.frame.parent.load("game_over.png"),
            x=450+chelone.camera.x, y=chelone.camera.y, phys_type="immovable", layer=1,
        )
        chelone.add_sprite(game_over, 1)


        # loop while dead
        while "Return" not in chelone.pressed_keys:
            check_keys()
            chelone.root.update()

        chelone.next_frame = time()
        chelone.now = time()
        chelone._sprites = saved_sprites

        super().delete_self()

        player = Player(
            id="Player", frame=self.frame.parent.load("tmp.png"),\
            x=250, y=0, gravity=self.gravity,\
            state_anim_directory=self.state_anim_directory
        )
        chelone.add_sprite(player)
        game_over.delete_self()

    def update_active(self):
        '''never unload the player'''
        pass

class Droid(Sprite):
    """
        Basic enemy. Partrols an area and shoots the player if it sees him.
    """

    class Laser(Sprite):
        """
            Projectile that the Droid enemy shoots.
        """
        def setup(self, kargs):
            self.velocity = kargs.get("velocity", [-3, 0])
            self.gravity = 0

        def update(self):
            self.move(self.velocity[0], self.velocity[1])

        def handle_trigger(self, collided_obj, my_collider, other_collider):
            if not isinstance(collided_obj, Droid):
                self.delete_self()
                collided_obj.delete_self()
                print("Laser attacked " + collided_obj.id)


    def setup(self, kargs):
        self.states = {"walk": lambda: 0}
        self.patrol_range = kargs.get("patrol_range", [self.x-300, self.x])
        self.speed = kargs.get("speed", 0)
        self.shooting_cooldown_limit = kargs.get("shooting_cooldown", 240)
        self.shooting_cooldown = self.shooting_cooldown_limit

        # make sure we spawn in our patrol range
        self.move(self.patrol_range[1] - self.x, 0)
        self.orientation = "left"


    def update(self):
        if self.anim_state == "None":
            self.update_anim_state('walk')

        if self.shooting_cooldown >= self.shooting_cooldown_limit:
            if self.orientation == "left":
                self.move(-self.speed, 0)
            else:
                self.move(self.speed, 0)

        if self.x < self.patrol_range[0] and self.orientation == "left":
            self.flip()

        elif self.x > self.patrol_range[1] and self.orientation == "right":
            self.flip()

        if self.shooting_cooldown < self.shooting_cooldown_limit:
            self.shooting_cooldown += 1


    def handle_trigger(self, collided_obj, my_collider, other_collider):

        if isinstance(collided_obj, Player):
            if self.shooting_cooldown >= self.shooting_cooldown_limit:
                self.shooting_cooldown = 0
                if my_collider.id == "left_search":
                    laser_x = self.x-50
                    laser_vel = -3

                elif my_collider.id == "right_search":
                    laser_x = self.x+self.colliders['collider_1'].width+50
                    laser_vel = 3

                self.Laser(
                    id="Laser", frame=self.frame.parent.load("laser.png"), gravity=0,
                    x=laser_x,
                    y=self.y+self.colliders['collider_1'].height/2,
                    velocity=[laser_vel, 0]
                )

class background_sound(Sprite):
    '''loops the bg music'''
    def setup(self, kargs):

        self.sound = kargs['sound']

        self.playing_sound = playsound(self.sound)

    def update(self):

        if sound_finished(self.playing_sound):
            self.playing_sound = playsound(self.sound)

    def update_active(self):
        '''stop this class from unloading'''
        pass

def droid_generator(droid_x, sector_settings):
    '''generates multiple droids'''
    loader = SpriteLoader()

    for i in range(sector_settings["droid_quantity"]):
        patrol_range_start = droid_x + sector_settings["patrol_range_offset"]
        patrol_range_end = droid_x + sector_settings["patrol_range_size"]
        patrol_range = [patrol_range_start, patrol_range_end]
        Droid("Droid", loader.load("droid.png"), patrol_range=patrol_range, speed=1, state_anim_directory="droid")
        droid_x += sector_settings["droidstart_x"]


def block_generator(block_x, sector_settings, ground_y):
    '''generates a block pile'''
    loader = SpriteLoader()

    upperspawn_y = ground_y + sector_settings["upperblock_y"]
    lowerspawn_y = ground_y + sector_settings["lowerblock_y"]
    upperlayer = sector_settings["upperboxlayer"]
    lowerlayer = sector_settings["lowerboxlayer"]

    for i in range(sector_settings["block_quantity"] - 1):
        upperspawn_x = block_x + sector_settings["upperblock_x"]
        Sprite("Block", loader.load("box.png"), phys_type="immovable", x=block_x, y=lowerspawn_y, layer=lowerlayer)
        Sprite("Block", loader.load("box.png"), phys_type="immovable", x=upperspawn_x, y=upperspawn_y)
        block_x += sector_settings["blockstart_x"]

    Sprite("Block", loader.load("box.png"), phys_type="immovable", x=block_x, y=lowerspawn_y, layer=lowerlayer)

def level_generator(start_x=0, obj_quantity=1, ground_y=650):
    '''generating simple levels from patterns found in level_patterns.json'''
    loader = SpriteLoader()

    try:
        pattern = json.load(open("level_patterns.json", "r"))
    except:
        pattern = []

    b = 0
    i = 0
    obj_x = 0
    ground_width = pattern["global_settings"]["ground_width"]
    ground_sprite_name = pattern["global_settings"]["ground_sprite"]

    while b < obj_quantity:
        random_index = random.randint(0, len(pattern["patterns"]) - 1)
        selected_sector = pattern["patterns"][random_index]
        obj_x += selected_sector["width"]
        block_generator(obj_x, selected_sector, ground_y)
        droid_generator(obj_x + selected_sector["droidspawn_offset"], selected_sector)
        b += 1

    while i < math.ceil(obj_x/float(ground_width)):
        Sprite("Ground", loader.load(ground_sprite_name), phys_type="immovable", x=start_x, y=ground_y, layer=49)
        start_x += ground_width
        i += 1



def start_level(root=None):
    '''main function for loading the level'''
    global chelone, playsound, sound_finished

    # setting up global objects for rendering and loading, respectively
    chelone = init(root)

    loader = SpriteLoader()

    Player("Player", loader.load("tmp.png"), gravity=-1, x=400, y=300, layer=10, state_anim_directory="anakin")

    if chelone.settings["sound"]:
        playsound = sound.playsound
        sound_finished = sound.sound_finished

    level_generator(obj_quantity=50)

    background_sound("background", loader.load("clear.png"), phys_type="inmovable", sound="sounds/imperial_march.wav")

    fps_tracker = tk.Label(root, text='fps')
    fps_tracker.config(font=("sans-serif", 44))
    fps_tracker.place(relx=0, rely=0, anchor=tk.NW)

    while 1:
        startTime = time()
        chelone.advance_frame()
        endTime = time()
        elapsedTime = endTime - startTime
        fps_tracker.config(text=str(int(1./elapsedTime)))



if __name__ == '__main__':
    start_level()


'''
    Basic test scene made using Chelone
'''
import random
import math
import json
import tkinter as tk
from time import time
import sound

from Chelone import init, Sprite, SpriteLoader, check_keys
from BoxPhys import get_collision_displacement


chelone = None
playsound = lambda a: 0
sound_finished = lambda a: False

class Player(Sprite):
    """
        A class for the main character.
        Handles smooth camera, game over, movement and attacks
    """

    grounded = False
    w_pressed = False
    camera_lagbehind = [0.01, 0.05]
    camera_offset = [600, 250]

    def setup(self, kargs):
        print("setup Player")

        # set up anim states
        self.states = {"run": self.run_state, "idle": self.idle_state, "jump": self.jump_state}

        self.grounded = False
        self.w_pressed = False
        self.orientation = "right"

        self.camera_lagbehind = kargs.get("camera_lagbehind", [0.05, 0.05])

        self.camera_offset = kargs.get("camera_offset", [600, 250])

    def update(self):
        if self.anim_state == "None":
            self.update_anim_state('idle')

        self.update_smooth_camera()

    def last_update(self):
        self.grounded = False

    def run_state(self):
        '''the code that runs while the player is running'''
        movement = self.move_on_command()
        if not movement[0] and not movement[1]:
            self.update_anim_state('idle')
        if chelone.settings["jump"] in chelone.pressed_keys and self.grounded:
            self.update_anim_state('jump')
            self.add_vel(0, -30)

    def idle_state(self):
        '''the code that runs while the player is idling'''
        movement = self.move_on_command()
        if movement[0] or movement[1]:
            self.update_anim_state('run')
        if chelone.settings["jump"] in chelone.pressed_keys and self.grounded:
            self.update_anim_state('jump')
            self.add_vel(0, -30)

    def jump_state(self):
        '''the code that runs while the player is jumping'''
        movement = self.move_on_command()#noqa could be used later
        if self.grounded:
            self.update_anim_state('idle')

    def update_smooth_camera(self):
        '''handles updating the camera position smoothly'''
        camera_desired = [
            self.x-self.camera_offset[0],
            self.y-self.camera_offset[1]
        ]

        chelone.camera.move(
            -self.camera_lagbehind[0]*(chelone.camera.x-camera_desired[0]),
            self.camera_lagbehind[1]*(chelone.camera.y-camera_desired[1])
        )

    def move_on_command(self):
        '''handles movement with keypresses'''
        ret = [False, False]

        if chelone.settings['run_left'] in chelone.pressed_keys:
            ret[0] = True
            self.move(-7, 0)

            if self.orientation != "left":
                self.flip()

        elif chelone.settings['run_right'] in chelone.pressed_keys:
            ret[1] = True
            self.move(7, 0)

            if self.orientation != "right":
                self.flip()

        return ret

    def handle_collision(self, collided_obj, my_collider, other_collider, handled=False, displacement=None):
        '''check if we ever intersect ground to be able to jump'''
        if displacement is None:
            displacement = get_collision_displacement(my_collider, other_collider)

        super().handle_collision(collided_obj, my_collider, other_collider, handled, displacement)

        if displacement[1] < 0 and self.vel.y >= 0:
            if my_collider.type != "trigger" and other_collider.type != "trigger":
                self.grounded = True

    def delete_self(self):
        '''
        instead of deleting, we need to pause and restart the game
        TODO: this is a POC, needs to be remade properly
        '''

        print("game_over")
        saved_sprites = chelone._sprites
        chelone._sprites = [{} for i in range(50)]
        game_over = Sprite(
            id="game_over", frame=self.frame.parent.load("game_over.png"),
            x=450+chelone.camera.x, y=chelone.camera.y, phys_type="immovable", layer=1,
        )
        chelone.add_sprite(game_over, 1)


        # loop while dead
        while "Return" not in chelone.pressed_keys:
            check_keys()
            chelone.root.update()

        chelone.next_frame = time()
        chelone.now = time()
        chelone._sprites = saved_sprites

        super().delete_self()

        player = Player(
            id="Player", frame=self.frame.parent.load("tmp.png"),\
            x=250, y=0, gravity=self.gravity,\
            state_anim_directory=self.state_anim_directory
        )
        chelone.add_sprite(player)
        game_over.delete_self()

    def update_active(self):
        '''never unload the player'''
        pass

class Droid(Sprite):
    """
        Basic enemy. Partrols an area and shoots the player if it sees him.
    """

    class Laser(Sprite):
        """
            Projectile that the Droid enemy shoots.
        """
        def setup(self, kargs):
            self.velocity = kargs.get("velocity", [-3, 0])
            self.gravity = 0

        def update(self):
            self.move(self.velocity[0], self.velocity[1])

        def handle_trigger(self, collided_obj, my_collider, other_collider):
            if not isinstance(collided_obj, Droid):
                self.delete_self()
                collided_obj.delete_self()
                print("Laser attacked " + collided_obj.id)


    def setup(self, kargs):
        self.states = {"walk": lambda: 0}
        self.patrol_range = kargs.get("patrol_range", [self.x-300, self.x])
        self.speed = kargs.get("speed", 0)
        self.shooting_cooldown_limit = kargs.get("shooting_cooldown", 240)
        self.shooting_cooldown = self.shooting_cooldown_limit

        # make sure we spawn in our patrol range
        self.move(self.patrol_range[1] - self.x, 0)
        self.orientation = "left"


    def update(self):
        if self.anim_state == "None":
            self.update_anim_state('walk')

        if self.shooting_cooldown >= self.shooting_cooldown_limit:
            if self.orientation == "left":
                self.move(-self.speed, 0)
            else:
                self.move(self.speed, 0)

        if self.x < self.patrol_range[0] and self.orientation == "left":
            self.flip()

        elif self.x > self.patrol_range[1] and self.orientation == "right":
            self.flip()

        if self.shooting_cooldown < self.shooting_cooldown_limit:
            self.shooting_cooldown += 1


    def handle_trigger(self, collided_obj, my_collider, other_collider):

        if isinstance(collided_obj, Player):
            if self.shooting_cooldown >= self.shooting_cooldown_limit:
                self.shooting_cooldown = 0
                if my_collider.id == "left_search":
                    laser_x = self.x-50
                    laser_vel = -3

                elif my_collider.id == "right_search":
                    laser_x = self.x+self.colliders['collider_1'].width+50
                    laser_vel = 3

                self.Laser(
                    id="Laser", frame=self.frame.parent.load("laser.png"), gravity=0,
                    x=laser_x,
                    y=self.y+self.colliders['collider_1'].height/2,
                    velocity=[laser_vel, 0]
                )

class background_sound(Sprite):
    '''loops the bg music'''
    def setup(self, kargs):

        self.sound = kargs['sound']

        self.playing_sound = playsound(self.sound)

    def update(self):

        if sound_finished(self.playing_sound):
            self.playing_sound = playsound(self.sound)

    def update_active(self):
        '''stop this class from unloading'''
        pass

def droid_generator(droid_x, sector_settings):
    '''generates multiple droids'''
    loader = SpriteLoader()

    for i in range(sector_settings["droid_quantity"]):
        patrol_range_start = droid_x + sector_settings["patrol_range_offset"]
        patrol_range_end = droid_x + sector_settings["patrol_range_size"]
        patrol_range = [patrol_range_start, patrol_range_end]
        Droid("Droid", loader.load("droid.png"), patrol_range=patrol_range, speed=1, state_anim_directory="droid")
        droid_x += sector_settings["droidstart_x"]


def block_generator(block_x, sector_settings, ground_y):
    '''generates a block pile'''
    loader = SpriteLoader()

    upperspawn_y = ground_y + sector_settings["upperblock_y"]
    lowerspawn_y = ground_y + sector_settings["lowerblock_y"]
    upperlayer = sector_settings["upperboxlayer"]
    lowerlayer = sector_settings["lowerboxlayer"]

    for i in range(sector_settings["block_quantity"] - 1):
        upperspawn_x = block_x + sector_settings["upperblock_x"]
        Sprite("Block", loader.load("box.png"), phys_type="immovable", x=block_x, y=lowerspawn_y, layer=lowerlayer)
        Sprite("Block", loader.load("box.png"), phys_type="immovable", x=upperspawn_x, y=upperspawn_y)
        block_x += sector_settings["blockstart_x"]

    Sprite("Block", loader.load("box.png"), phys_type="immovable", x=block_x, y=lowerspawn_y, layer=lowerlayer)

def level_generator(start_x=0, obj_quantity=1, ground_y=650):
    '''generating simple levels from patterns found in level_patterns.json'''
    loader = SpriteLoader()

    try:
        pattern = json.load(open("level_patterns.json", "r"))
    except:
        pattern = []

    b = 0
    i = 0
    obj_x = 0
    ground_width = pattern["global_settings"]["ground_width"]
    ground_sprite_name = pattern["global_settings"]["ground_sprite"]

    while b < obj_quantity:
        random_index = random.randint(0, len(pattern["patterns"]) - 1)
        selected_sector = pattern["patterns"][random_index]
        obj_x += selected_sector["width"]
        block_generator(obj_x, selected_sector, ground_y)
        droid_generator(obj_x + selected_sector["droidspawn_offset"], selected_sector)
        b += 1

    while i < math.ceil(obj_x/float(ground_width)):
        Sprite("Ground", loader.load(ground_sprite_name), phys_type="immovable", x=start_x, y=ground_y, layer=49)
        start_x += ground_width
        i += 1



def start_level(root=None):
    '''main function for loading the level'''
    global chelone, playsound, sound_finished

    # setting up global objects for rendering and loading, respectively
    chelone = init(root)

    loader = SpriteLoader()

    Player("Player", loader.load("tmp.png"), gravity=-1, x=400, y=300, layer=10, state_anim_directory="anakin")

    if chelone.settings["sound"]:
        playsound = sound.playsound
        sound_finished = sound.sound_finished

    level_generator(obj_quantity=50)

    background_sound("background", loader.load("clear.png"), phys_type="inmovable", sound="sounds/imperial_march.wav")

    fps_tracker = tk.Label(root, text='fps')
    fps_tracker.config(font=("sans-serif", 44))
    fps_tracker.place(relx=0, rely=0, anchor=tk.NW)

    while 1:
        startTime = time()
        chelone.advance_frame()
        endTime = time()
        elapsedTime = endTime - startTime
        fps_tracker.config(text=str(int(1./elapsedTime)))



if __name__ == '__main__':
    start_level()


Basic test scene made using Chelone A class for the main character.         Handles smooth camera, game over, movement and attacks # set up anim states the code that runs while the player is running the code that runs while the player is idling the code that runs while the player is jumping #noqa could be used later handles updating the camera position smoothly handles movement with keypresses check if we ever intersect ground to be able to jump instead of deleting, we need to pause and restart the game         TODO: this is a POC, needs to be remade properly # loop while dead never unload the player Basic enemy. Partrols an area and shoots the player if it sees him. Projectile that the Droid enemy shoots. # make sure we spawn in our patrol range loops the bg music stop this class from unloading generates multiple droids generates a block pile generating simple levels from patterns found in level_patterns.json main function for loading the level # setting up global objects for rendering and loading, respectively