import pygame
import sys
import random

from bullets import FriendlyProjectile, EnemyProjectile
from particles import Particle, DamageNumber
from player import Player
from objects import Ninja, Grunt, Heavy, Rico, Virus, Dasher, Infested
import helper
import math

pygame.init()
pygame.mixer.init()
hit = pygame.mixer.Sound("assets/sounds/hit.mp3")
kill = pygame.mixer.Sound("assets/sounds/kill.mp3")
mosaic = pygame.font.Font("assets/fonts/PaletteMosaic-Regular.ttf", 72)
mosaic32 = pygame.font.Font("assets/fonts/PaletteMosaic-Regular.ttf", 24)
mosaic48 = pygame.font.Font("assets/fonts/PaletteMosaic-Regular.ttf", 32)
maven = pygame.font.Font("assets/fonts/MavenPro-Bold.ttf", 32)
jo = pygame.font.Font("assets/fonts/Jo_wrote_a_lovesong.ttf", 144)
jo32 = pygame.font.Font("assets/fonts/Jo_wrote_a_lovesong.ttf", 28)
SONGS = ["assets/music/impatient.wav", "assets/music/cheery.wav", "assets/music/riff.wav"]
clock = pygame.time.Clock()
screen_width, screen_height = 1920, 1080
screen = pygame.display.set_mode((screen_width, screen_height))
pygame.mouse.set_visible(False)


def play():
    if pygame.mixer.music.get_busy(): return
    song = random.choice(SONGS)
    pygame.mixer.music.load(song)
    pygame.mixer.music.play()


zoom = 1
decay_zoom = 0
shake = 0
decay_shake = 0
display = pygame.Surface((screen_width, screen_height))

NUMBER_KEYS = {
    pygame.K_0: 0,
    pygame.K_1: 1,
    pygame.K_2: 2,
    pygame.K_3: 3,
    pygame.K_4: 4,
    pygame.K_5: 5,
    pygame.K_6: 6,
    pygame.K_7: 7,
    pygame.K_8: 8,
    pygame.K_9: 9
}


def decay():
    global decay_zoom, decay_shake
    decay_zoom = max(0, decay_zoom - 0.01)
    decay_shake = max(0, decay_shake - 1)


def render(centerx=None, centery=None):
    if centerx is None:
        centerx = screen_width / 2
    if centery is None:
        centery = screen_height / 2
    real_zoom = decay_zoom + zoom
    real_shake = int(math.log((decay_shake + shake) ** 2, 1.25)) if decay_shake + shake > 0 else 0
    screen.blit(pygame.transform.scale(display, (int(screen_width * real_zoom), int(screen_height * real_zoom))),
                (-int(centerx * (real_zoom - 1)) + random.randint(0, real_shake),
                 -int(centery * (real_zoom - 1)) + random.randint(0, real_shake)))


def bar(progress, fg, bg, x, y, width, height, label=None, label_font=None, labelcolor=(255, 255, 255), ypad=0):
    helper.bar(screen, progress, fg, bg, x, y, width, height, label, label_font, labelcolor, ypad)


def center_bar(progress, fg, bg, stack, label=None, label_font=None, labelcolor=(255, 255, 255), ypad=0):
    helper.center_bar(screen, progress, fg, bg, stack, label, label_font, labelcolor, ypad)


def arena():
    global decay_zoom, zoom, shake, decay_shake, hit, kill

    player = Player(screen_width / 2, screen_height / 2, 32, 100, 100, 3, 8)

    enemies = []
    enemy_timer = 0
    difficulty = 0

    def new_enemy():
        typen = random.randint(0, int(difficulty))
        pos = random.randint(800, 1000) * (random.randint(0, 1) * 2 - 1) + player.x, \
            random.randint(800, 1000) * (random.randint(0, 1) * 2 - 1) + player.y
        enem = Grunt
        if typen <= 25:
            enem = Grunt
        elif typen < 50:
            enem = Heavy
        elif typen < 60:
            enem = Ninja
        elif typen < 70:
            enem = Dasher
        elif typen < 80:
            enem = Virus
        elif typen < 85:
            enem = Rico
        elif typen < 86:
            enem = Infested
        enemies.append(enem(*pos))

    objects = []
    bullets = []
    particles = []

    killstreak = 0
    last_kill_timer = 0
    score = 0
    kill_delay = 200

    minimap_size = 125
    marker_size = minimap_size / 16

    def explode(x, y, amt, minforce=1, maxforce=5):
        p = []
        for i in range(math.ceil(amt / 4)):
            angle = math.radians(random.randint(0, 360))
            force = random.randint(minforce * 100, maxforce * 100) / 100

            xv = math.cos(angle) * force
            yv = math.sin(angle) * force

            p.append(Particle((random.randint(150, 255), 0, 0), x, y, xv, yv,
                              random.randint(math.ceil(i ** 0.25), math.ceil(2 * i ** (1 / 3))),
                              decay=45, friction=0.1))
        return p

    def hallucinate():
        return random.randint(0, 1500) < player.insanity

    while True:
        map_points = []
        play()
        if killstreak:
            player.insanity += killstreak / 1200
            player.insanity += 0.5

        last_kill_timer -= 1
        if last_kill_timer < 0:
            killstreak = 0
        difficulty = min(100, difficulty + 0.01)

        mx, my = pygame.mouse.get_pos()
        display.fill((30, 30, 30))
        helper.checkerboard(display, (player.x, player.y))
        # noinspection PyTypeChecker
        decay()
        attack_location = None

        enemy_timer -= random.randint(1, 5) * 0.66
        if enemy_timer <= 0:
            new_enemy()
            enemy_timer = random.randint(200, 300)

        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()
            elif event.type == pygame.MOUSEBUTTONDOWN:
                if event.button == 3:
                    attack_location = player.swipe()
            elif event.type == pygame.MOUSEMOTION:
                pygame.mouse.set_pos(helper.clamp(0, mx, screen_width), helper.clamp(0, my, screen_height))
                pygame.event.set_grab(True)

            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_e:
                    if player.phase():
                        decay_zoom = 0.25
                elif event.key == pygame.K_f:
                    player.blood_event(screen)
                elif event.key in NUMBER_KEYS:
                    player.equip(NUMBER_KEYS[event.key])
                elif event.key == pygame.K_F11:
                    pygame.display.toggle_fullscreen()

        keys = pygame.key.get_pressed()

        if pygame.mouse.get_pressed(3)[0]:
            s = player.shoot()
            bullets += s
            if s:
                if player.equipped == "shotgun":
                    decay_shake = 25
                elif player.equipped == "pistol":
                    decay_shake = 5

        if keys[pygame.K_LSHIFT]:
            player.run()
        else:
            player.walk()

        zoom = max(1, (player.speed - player.walks) / (player.runs - player.walks) / 6 + 1)
        mouse_pos = pygame.mouse.get_pos()
        player.set_angle(*mouse_pos)

        if keys[pygame.K_a] or keys[pygame.K_d] or keys[pygame.K_s] or keys[pygame.K_w]:
            player.moving = True
        else:
            player.moving = False
            player.speed = 0

        if keys[pygame.K_ESCAPE]:
            pygame.quit()
            sys.exit()

        if keys[pygame.K_a]:
            player.xv = -player.speed
        elif keys[pygame.K_d]:
            player.xv = player.speed
        else:
            player.xv = 0
        if keys[pygame.K_s]:
            player.yv = player.speed
        elif keys[pygame.K_w]:
            player.yv = -player.speed
        else:
            player.yv = 0

        for obj in objects:
            obj.main(display, player)

        final = []
        for enemy in enemies.copy():
            enemy.hit -= 1
            if attack_location:
                if helper.segment_to_point(*attack_location, player.x, player.y, enemy.x + enemy.s / 2,
                                           enemy.y + enemy.s / 2) < enemy.s / 2 + 32:
                    decay_shake = 15
                    enemy.damage(30)
                    if enemy.dmgnum is None:
                        dn = DamageNumber(30, mosaic48, *attack_location, enemy)
                        particles.append(dn)
                        enemy.dmgnum = dn
                    else:
                        enemy.dmgnum.stack(30)
                        enemy.dmgnum.x, enemy.dmgnum.y = attack_location
                    if player.siphon:
                        player.hp = min(player.mhp, player.hp + 5)

            enemy_bullets, dead = enemy.mutator(enemies)
            if enemy_bullets:
                bullets += enemy_bullets

            if helper.dist(enemy.x + enemy.s / 2, enemy.y + enemy.s / 2, player.x, player.y) > 7500:
                dead = True

            if enemy.hp <= 0 or dead:
                enemies.remove(enemy)

                mnf, mxf = 1, 5
                if 500 <= enemy.mhp < 1000:
                    mxf = 7
                elif 1000 <= enemy.mhp < 1500:
                    mxf = 10
                elif 1500 <= enemy.mhp:
                    mxf = 20

                particles += explode(enemy.x + enemy.s / 2, enemy.y + enemy.s / 2, enemy.blood, mnf, mxf)

                if not dead:
                    pygame.mixer.Sound.play(kill)
                    killstreak += 1
                    last_kill_timer = kill_delay
                    decay_shake += killstreak
                    score += enemy.score * 100 + random.randint(0, 99)
                    player.blood_meter = min(player.blood_meter + enemy.score ** (0.5 if player.active_blood else 1),
                                             player.blood_max)
                continue

            move_change_x, move_change_y, enemy_bullets = enemy.main(display, player)
            bullets += enemy_bullets
            enemy.dodge(bullets)

            total_change = move_change_x, move_change_y
            if helper.dist(enemy.x + enemy.s / 2, enemy.y + enemy.s / 2, player.x, player.y) < enemy.s * 0.6 + 16:
                if player.iframes <= 0:
                    pygame.mixer.Sound.play(hit)
                    particles += explode(player.x, player.y, enemy.dmg)
                player.damage(enemy.dmg)
                evade_direction = math.radians(random.randint(0, 360))

                evade_x = math.cos(evade_direction) * enemy.speed * 6
                evade_y = math.sin(evade_direction) * enemy.speed * 6

                print(evade_x, evade_y)

                enemy.dcxv = evade_x
                enemy.dcyv = evade_y

            if helper.dist(enemy.x + enemy.s / 2, enemy.y + enemy.s / 2, player.x, player.y) < (minimap_size - 16) * 20:
                map_points.append((
                    (enemy.x - player.x) / 20,
                    (enemy.y - player.y) / 20,
                    enemy.color
                ))

            tolerance = 16
            for target in final:
                if enemy == target: continue
                if helper.dist(target.x, target.y, enemy.x + enemy.s / 2, enemy.y + enemy.s / 2) < tolerance:
                    if (target.x == enemy.x and target.y == enemy.y) or (total_change[0] == 0 and total_change[1] == 0):
                        enemy.x -= target.s
                        enemy.y -= target.s
                    else:
                        enemy.x -= total_change[0]
                        enemy.y -= total_change[1]
                    break
            enemy.draw(display, player)
            final.append(enemy)

        for particle in particles.copy():
            if particle.main(display, player):
                particles.remove(particle)

        for bullet in bullets.copy():
            bullet.main(display, player)

            if type(bullet) == FriendlyProjectile:
                for enemy in enemies.copy():
                    if helper.dist(enemy.x + enemy.s / 2, enemy.y + enemy.s / 2, bullet.x, bullet.y) < enemy.s * 0.6:
                        enemy.dcxv += bullet.xv / 5
                        enemy.dcyv += bullet.yv / 5
                        enemy.stagger = 5
                        bullet.xv *= 1 - enemy.s / 150
                        bullet.yv *= 1 - enemy.s / 150

                        if bullet.damage > enemy.hp:
                            total_damage = enemy.hp
                        else:
                            total_damage = bullet.damage

                        enemy.damage(bullet.damage)

                        if enemy.dmgnum is None:
                            dn = DamageNumber(total_damage, mosaic48, bullet.x, bullet.y, enemy)
                            particles.append(dn)
                            enemy.dmgnum = dn
                        else:
                            enemy.dmgnum.stack(total_damage)
                            enemy.dmgnum.x = bullet.x
                            enemy.dmgnum.y = bullet.y

                        if enemy.hit < 0:
                            pygame.mixer.Sound.play(hit)

                        enemy.hit = 10
                        particles += explode(bullet.x, bullet.y, bullet.damage)
                        if player.siphon:
                            player.hp = min(player.mhp, player.hp + 0.3)
                        break
            elif type(bullet) == EnemyProjectile:
                if helper.dist(player.x + player.width / 2, player.y + player.height / 2, bullet.x, bullet.y) < \
                        math.sqrt(player.width ** 2 + player.height ** 2):
                    bullets.remove(bullet)
                    if player.iframes <= 0:
                        pygame.mixer.Sound.play(hit)
                        particles += explode(player.x, player.y, bullet.damage)
                    player.damage(bullet.damage)
                    break

            if bullet not in bullets:
                continue
            if helper.dist(player.x, player.y, bullet.x, bullet.y) > 1500 or bullet.speed() < 1:
                bullets.remove(bullet)

        player.main(display)

        if player.active_blood:
            tint = pygame.Surface((screen_width, screen_height), pygame.SRCALPHA).convert_alpha()
            tint.fill((255, 0, 0, player.blood_meter / player.blood_max * 100))
            display.blit(tint, (0, 0))

        render(player.rx, player.ry)

        center_bar(player.stamina / player.max_stamina, (255, 255, 255), (50, 50, 50), 1, "STAMINA", maven,
                   (0, 0, 0), -4)
        center_bar(player.hp / player.mhp, (255 * (player.mhp - player.hp) / player.mhp,
                                            255 * player.hp / player.mhp, 0),
                   (80 * (player.mhp - player.hp) / player.mhp,
                    80 * player.hp / player.mhp, 0), 2, "HEALTH", mosaic32, ypad=-2)
        center_bar(player.blood_meter / player.blood_max, (255, 0, 0), (75, 50, 50), 3, "BLOOD", jo32, ypad=1)

        minimap_surf = pygame.Surface((minimap_size * 2, minimap_size * 2), pygame.SRCALPHA)
        minimap_surf.fill((0, 0, 0, 0))

        pygame.draw.circle(minimap_surf, (0, 0, 0), (minimap_size, minimap_size), minimap_size)
        pygame.draw.circle(minimap_surf, (0, 255, 0), (minimap_size, minimap_size), marker_size)

        for point in map_points:
            pygame.draw.circle(minimap_surf, point[2], (point[0] + minimap_size, point[1] + minimap_size), marker_size)

        screen.blit(minimap_surf, (screen_width - minimap_size * 2 - 32, 32))

        screen.blit(mosaic.render(f"Score: {score}", True, (255, 255, 255)), (16, 0))

        bar(last_kill_timer / kill_delay, (255, 255, 255), (50, 50, 50), 16, 168, 200, 8)
        screen.blit(mosaic.render(f"x{killstreak}", True, (255, 255, 255)), (16, 72))

        if hallucinate():
            hallucination = random.choice([
                "KILL KILL KILL",
                "END THEM ALL",
                "NO SQUARE LEFT",
                "GENOCIDE GENOCIDE GENOCIDE",
                "INSANE INSANE INSANE",
                "DON'T STOP"
            ])
            text = jo.render(hallucination, False, (255, 255, 255))
            screen.blit(text, (screen_width / 2 - text.get_width() / 2, screen_height / 2 - 144))
            decay_shake += 15

        if player.hp <= 0:
            tint = pygame.Surface((screen_width, screen_height), pygame.SRCALPHA).convert_alpha()
            tint.fill((0, 0, 0, 100))
            screen.blit(tint, (0, 0))

            t = mosaic.render("GAME OVER", True, (255, 255, 255))
            screen.blit(t, (screen_width / 2 - t.get_width() / 2, screen_height / 2 - 144))
            pygame.display.flip()

            waiting = True
            actiondelay = 100
            while waiting:
                actiondelay -= 1
                for event in pygame.event.get():
                    if event.type == pygame.QUIT:
                        pygame.quit()
                        sys.exit()
                    elif event.type == pygame.KEYDOWN:
                        if event.key == pygame.K_ESCAPE:
                            pygame.quit()
                            sys.exit()
                        elif actiondelay < 0:
                            waiting = False
                            break
                    elif actiondelay < 0 and event.type == pygame.MOUSEBUTTONDOWN:
                        waiting = False
                clock.tick(60)
            break

        clock.tick(60)
        pygame.display.flip()


def menu():
    global decay_shake, shake, decay_zoom, zoom
    decay_zoom = 2
    zoom = 1
    shake = 5
    decay_shake = 25
    fonts144 = [
        pygame.font.Font(f"assets/fonts/{x}.ttf", 144)
        for x in ("Jo_wrote_a_lovesong", "MavenPro-Bold", "Monofett-Regular", "PaletteMosaic-Regular")
    ]
    fonts72 = [
        pygame.font.Font(f"assets/fonts/{x}.ttf", 72)
        for x in ("Jo_wrote_a_lovesong", "MavenPro-Bold", "Monofett-Regular", "PaletteMosaic-Regular")
    ]

    options = ["start", "quit"]
    index = 0
    font144 = random.choice(fonts144)
    font72 = random.choice(fonts72)
    fonttimer = 10
    while True:
        play()
        decay_shake = max(0, decay_shake - 1)
        decay_zoom = max(0, decay_zoom - 0.1)
        fonttimer -= 1
        if fonttimer < 0:
            newfont144 = font144
            while newfont144 == font144:
                newfont144 = random.choice(fonts144)
            newfont72 = font72
            while newfont72 == font72:
                newfont72 = random.choice(fonts72)
            font72, font144 = newfont72, newfont144
            fonttimer = 10
        helper.checkerboard(display, (screen_width / 2, screen_height / 2))
        render()
        title = font144.render("SQR", False, (255, 255, 255))
        screen.blit(title, (screen_width / 2 - title.get_width() / 2, 16))

        for i in range(len(options)):
            option = font72.render(options[i].upper(), False,
                                                   (255, 255, 0) if i == index else (255, 255, 255))
            screen.blit(option, (screen_width / 2 - option.get_width() / 2, 256 + 72 * i))

        done = False
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_ESCAPE:
                    pygame.quit()
                    sys.exit()
                elif event.key == pygame.K_w or event.key == pygame.K_UP:
                    index = max(0, index - 1)
                elif event.key == pygame.K_s or event.key == pygame.K_DOWN:
                    index = min(len(options) - 1, index + 1)
                elif event.key == pygame.K_RETURN:
                    done = True
                    break
        if done: break

        clock.tick(60)
        pygame.display.flip()
    selection = options[index]
    shake = 0
    decay_zoom = 2
    if selection == "quit":
        pygame.quit()
        sys.exit()
    elif selection == "start":
        return


if __name__ == "__main__":
    play()
    while True:
        menu()
        arena()
