# -*- coding: utf-8 -*-
import logging
from math import degrees, atan2
from typing import List

import pygame.mixer

from game.features import PPU

logger = logging.getLogger(__name__)


class SoundResource:

    def __init__(self, path, volume, hear_radius):
        self.path = path
        self.volume = volume
        self.sound = None
        self.id = -1
        self.hear_radius = hear_radius * PPU


resources = {
    0: SoundResource("game/data/sounds/buzz.ogg", 1.0, 10),
    1: SoundResource("game/data/sounds/vent.ogg", 1.0, 20),
    2: SoundResource("game/data/sounds/switch_strength_stove_01.ogg", 0.2, 20),
    3: SoundResource("game/data/sounds/wind.ogg", 0.3, 0),
}


class AudioSystem:
    class PlayInfo:

        def __init__(self, res, pos, queue_id, loops, channel):
            self.channel: pygame.mixer.Channel | None = channel
            self.res: SoundResource = res
            self.position = pos
            self.queue_id = queue_id
            self.loops = loops

    def __init__(self):
        self._master_volume = 0.5

        self.play_info: List[AudioSystem.PlayInfo] = []
        self._random_id = -100

    def get_master_volume(self):
        return self._master_volume

    def set_master_volume(self, volume):
        if volume > 1.0:
            volume = 1.0
        if volume < 0.0:
            volume = 0.0
        self._master_volume = volume

    def load(self):
        for sid, res in resources.items():
            res.sound = pygame.mixer.Sound(res.path)
            res.sound.set_volume(res.volume)
            res.id = sid

    def stop(self, sound_id, queue_id):
        for pi in self.play_info:
            if pi.queue_id == queue_id and pi.res.id == sound_id:
                if pi.channel:
                    pi.channel.stop()

                self.play_info.remove(pi)
                return

    def play_no_position(self, sound_id, loops=0):
        channel = pygame.mixer.find_channel()
        if channel:
            res = resources[sound_id]
            channel.play(res.sound, loops=loops)
            channel.set_volume(self._master_volume)

        else:
            logger.warning("could not find channel to play: %s", sound_id)

    def play(self, position, sound_id, queue_id=None, loops=0):
        """
        Play sound.
        :param queue_id:
        :param sound_id: resource id
        :param position: position where the object is emitting the sound
        :param loops: use -1 to repeat indefinitely
        """
        if queue_id is None:
            self._random_id -= 1
            queue_id = self._random_id

        for pi in self.play_info:
            if pi.queue_id == queue_id and pi.res.id:
                return

        channel = pygame.mixer.find_channel()
        if channel:
            res = resources[sound_id]
            channel.play(res.sound, loops=loops)
            channel.set_volume(self._master_volume)

            pi = AudioSystem.PlayInfo(res, position, queue_id, loops, channel)
            self.play_info.append(pi)
        else:
            logger.warning("could not find channel to play: %s/%s", sound_id, queue_id)

    def update(self, hear_position: pygame.Vector3):
        # remove channels playing something differently than originally registered because they have own play info
        self.play_info[:] = [p_ for p_ in self.play_info if (p_.channel is None and p_.loops == -1) or
                             (p_.channel and p_.channel.get_busy() and p_.channel.get_sound() == p_.res.sound)]

        for pi in self.play_info:

            diff = pi.position - hear_position
            dist = diff.length()
            if dist > pi.res.hear_radius:
                # stop playing it and remove it
                if pi.channel:
                    pi.channel.stop()
                    pi.channel = None
                continue
            else:
                if pi.channel is None:
                    channel = pygame.mixer.find_channel()
                    if channel:
                        channel.play(pi.res.sound, loops=pi.loops)
                        channel.set_volume(self._master_volume)
                        pi.channel = channel

            distance = dist / pi.res.hear_radius * 255  # range 0, 255
            if distance >= 255:
                distance = 255

            angle = degrees(atan2(diff.y, diff.x)) + 90
            if angle < 0:
                angle += 360

            pi.channel.set_source_location(angle, distance)


if __name__ == '__main__':

    north = pygame.Vector3(0, -1, 0)
    x_axis = pygame.Vector3(1, 0, 0)


    def get_angle(source, hearer):
        diff = source - hearer
        angle = degrees(atan2(diff.y, diff.x)) + 90
        if angle < 0:
            return angle + 360
        return angle


    s = pygame.Vector3(5, 1, 0)
    h = pygame.Vector3(5, 0, 0)  # S
    print(get_angle(s, h), "expected 180")

    h = pygame.Vector3(4, 0, 0)  # SE
    print(get_angle(s, h), "expected 135")

    h = pygame.Vector3(6, 0, 0)  # SW
    print(get_angle(s, h), "expected 225")

    h = pygame.Vector3(5, 1, 0)  # spot on
    print(get_angle(s, h), "expected 0")

    h = pygame.Vector3(5, 2, 0)  # N
    print(get_angle(s, h), "expected 0/360")

    h = pygame.Vector3(6, 2, 0)  # NW
    print(get_angle(s, h), "expected 315")

    h = pygame.Vector3(4, 2, 0)  # NE
    print(get_angle(s, h), "expected 45")

    h = pygame.Vector3(4, 1, 0)  # E
    print(get_angle(s, h), "expected 90")

    h = pygame.Vector3(6, 1, 0)  # W
    print(get_angle(s, h), "expected 270")
