import math
import random
import tidal
from app import TextApp
from scheduler import get_scheduler
import vga2_8x8 as default_font


class LunarLander(TextApp):
    def on_start(self):
        super().on_start()

    def on_activate(self):
        super().on_activate()
        self.restart()
        self.update()
        
        # Need this or else the game sleeps and resets after ~1800 frames
        get_scheduler().set_sleep_enabled(False)


    def on_deactivate(self):
        super().on_deactivate()
        self.update_task.cancel()

        # Turn light sleep back on
        get_scheduler().set_sleep_enabled(True)

    def restart(self):
        tidal.display.fill(tidal.BLACK)

        self.lander_poly = [(0, -11), (-5, 2), (5, 2)]
        self.explosion_cols = [tidal.RED, tidal.BRAND_ORANGE, tidal.YELLOW, tidal.color565(40, 40, 40)]

        self.time = 0
        self.alive = True
        self.won = False
        
        self.rotation = (random.uniform(0, math.pi) - (math.pi*0.25)) % (2 * math.pi)
        self.pos_x = 64.0
        self.pos_x_int = int(self.pos_x)
        self.pos_y = 15.0
        self.pos_y_int = 5
        self.thruster_offset = (0,6)
        start_speed = random.uniform(0, 0.5)
        self.speed_x = math.cos(self.rotation + math.pi) * start_speed
        self.speed_y = -math.sin(self.rotation + math.pi) * start_speed
        
        self.gravity = 0.001
        self.rotation_speed = 0.1
        self.thrust = 2
        self.thrust_changed = False
        self.thrust_enabled = False
        self.update_time = 10
        self.level_height = []
        self.f0 = 0
        self.o0 = 0
        self.f1 = 0
        self.o1 = 0
        self.f2 = 0
        self.o2 = 0
        self.create_level()
        self.explode_step = 0
        self.update_task = None

    def update(self):
        self.draw_ship(True)
    
        self.time += 1
            
        if self.alive:
            self.speed_y += self.gravity

            if tidal.JOY_LEFT.value() == 0:
                self.rotation -= self.rotation_speed
                if self.rotation < 0:
                    self.rotation += 2*math.pi
            if tidal.JOY_RIGHT.value() == 0:
                self.rotation += self.rotation_speed
                if self.rotation >= 2 * math.pi:
                    self.rotation -= 2*math.pi

            if tidal.JOY_DOWN.value() == 0:
                if not self.thrust_changed:
                    self.thrust -= 1
                    self.thrust_changed = True
                    if self.thrust < 1:
                        self.thrust = 3
            elif tidal.JOY_UP.value() == 0:
                if not self.thrust_changed:
                    self.thrust += 1
                    self.thrust_changed = True
                    if self.thrust > 3:
                        self.thrust = 1
            else:
                self.thrust_changed = False

            if tidal.BUTTON_A.value() == 0 or tidal.JOY_CENTRE.value() == 0:
                self.speed_x += math.sin(self.rotation) * self.thrust * 0.002
                self.speed_y -= math.cos(self.rotation) * self.thrust * 0.002
                self.thrust_enabled = True
            else:
                self.thrust_enabled = False

            self.pos_x += self.speed_x
            self.pos_y += self.speed_y

            self.pos_x_int = int(self.pos_x)
            self.pos_y_int = int(self.pos_y)

            if self.pos_x_int < 0 or self.pos_x_int >= tidal.display.width() or self.pos_y_int < 0:
                tidal.display.text(default_font, "  CRASH LANDING   ", 0, 100)
                tidal.display.text(default_font, "  lost to space   ", 0, 116)
                self.die()
            elif self.pos_y_int >= self.level_height[self.pos_x_int]-1:
                if self.check_win():
                    self.win()
                else:
                    self.die()

            if self.alive:
                self.draw_ship()
        else:
            if self.explode_step <= 6:
                self.draw_explosion(self.pos_x_int, self.pos_y_int, self.explode_step)
                self.explode_step += 1
            elif self.explode_step == 7:
                tidal.display.fill_circle(self.pos_x_int, self.pos_y_int, 6, tidal.BLACK)
                self.draw_level()
                self.explode_step += 1

            if tidal.BUTTON_A.value() == 0 or tidal.JOY_CENTRE.value() == 0:
                if not self.thrust_changed:
                    self.restart()
                    self.thrust_changed = True
            else:
                self.thrust_changed = False

        self.draw_ingame_text()

        self.update_task = self.after(self.update_time, self.update)

    def check_win(self):
    
        x = self.pos_x_int
        rot = self.get_slope(x)
        speed = (self.speed_x * self.speed_x) + (self.speed_y * self.speed_y)
        
        if speed >= 0.025:
            tidal.display.text(default_font, "  CRASH LANDING   ", 0, 100)
            tidal.display.text(default_font, "    too fast      ", 0, 116)
            return False
            
        if speed >= 0.01:
            tidal.display.text(default_font, "  hard landing    ", 0, 116)
            return True
            
        delta_angle = math.atan2(math.sin(self.rotation - rot), math.cos(self.rotation - rot))
        safe_landing = abs(delta_angle) < math.pi / 8
        if not safe_landing:
            tidal.display.text(default_font, "  CRASH LANDING   ", 0, 100)
            tidal.display.text(default_font, "    bad angle     ", 0, 116)
        return safe_landing
    
    # derivative of the wave of the level shape. yes i know i could clean this up, no i am not going to
    def get_slope(self, x):
        aterm = (50 * math.cos(x/self.f0 + self.o0) * math.sin((x / self.f1) + self.o1) * math.sin((x / self.f2) + self.o2)) / self.f0
        bterm = (50 * math.sin(x/self.f0 + self.o0) * math.cos((x / self.f1) + self.o1) * math.sin((x / self.f2) + self.o2)) / self.f1
        cterm = (50 * math.sin(x/self.f0 + self.o0) * math.sin((x / self.f1) + self.o1) * math.cos((x / self.f2) + self.o2)) / self.f2
    
        return aterm + bterm + cterm

    def die(self):
        self.explode_step = 0
        self.alive = False
        
    def win(self):
        self.explode_step = 7
        self.alive = False
        self.draw_ship()
        tidal.display.text(default_font, "    good job!     ", 0, 100)

    def draw_ingame_text(self):
        speed = (self.speed_x * self.speed_x) + (self.speed_y * self.speed_y)
        tidal.display.text(default_font, "Thrust {}".format(self.thrust), 0, 216)
        tidal.display.text(default_font, "Speed  {}".format(speed), 0, 232)

    def draw_explosion(self, x, y, step):
        for i in range(3):
            if i+step >= len(self.explosion_cols):
                break
            tidal.display.fill_circle(x, y, 6-(2*i), self.explosion_cols[i+step])

    def draw_ship(self, clear=False):
        if self.thrust_enabled:
            # thank god for matrix mutiplication
            offset_x = self.pos_x
            offset_x += math.cos(self.rotation) * self.thruster_offset[0] + -math.sin(self.rotation) * self.thruster_offset[1]
            offset_y = self.pos_y
            offset_y += math.sin(self.rotation) * self.thruster_offset[0] + math.cos(self.rotation) * self.thruster_offset[1]

            tidal.display.fill_circle(int(offset_x), int(offset_y), 1+self.thrust+self.time%2, self.explosion_cols[self.time % 3] if not clear else tidal.BLACK)

        if self.alive:
            tidal.display.fill_polygon(self.lander_poly, self.pos_x_int, self.pos_y_int, tidal.WHITE if not clear else tidal.BLACK, self.rotation)

    def create_level(self):
        w = tidal.display.width()
        h = tidal.display.height()

        self.level_height = []

        self.f0 = random.randint(40, 120) * math.pi
        self.o0 = random.randint(0, 80) * math.pi
        self.f1 = random.randint(20, 40) * math.pi
        self.o1 = random.randint(0, 20)
        self.f2 = random.randint(5, 15)
        self.o2 = random.randint(0, 10)
        for x in range(w):
            y = int(h - 8 - (((math.sin((x / self.f0) + self.o0) * math.sin((x / self.f1) + self.o1) * math.sin((x / self.f2) + self.o2))) * 50 + 50))
            self.level_height.append(y)

        self.draw_level()

    def draw_level(self):
        for x in range(1, tidal.display.width()):
            x0 = x-1
            y0 = self.level_height[x0]
            tidal.display.line(x0, y0, x, self.level_height[x], tidal.WHITE)

main = LunarLander