import argparse
import asyncio
import asyncio.subprocess
import contextlib
import math
import pathlib
import random
import sys

from attrs import define, field

from rabdomancia.utils import Tasker

# Inspired by: http://listen.hatnote.com/


def gen_sound_library():
    sounds_root = pathlib.Path("listen-to-wikipedia/static/sounds")
    for sd in sounds_root.iterdir() if sounds_root.is_dir() else []:
        if not sd.is_dir():
            continue
        yield (sd.name, [str(sf) for sf in sd.iterdir() if sf.suffix.lower() == ".ogg"])


sound_library = dict(gen_sound_library())


@define(auto_attribs=True)
class RabdomanciaSound:
    player: str = field(default="mpv")

    current_notes: int = field(default=0)
    note_overlap: int = field(default=15)
    note_timeout_s: float = field(default=0.300)

    known_users: set[str] = field(factory=set)
    visit_counters: dict[str, int] = field(factory=dict)

    tasker: Tasker = field(factory=Tasker)

    def release_note(self):
        self.current_notes -= 1

    async def do_play(self, tipus, index):
        # Sound in python is a big ugly mess
        proc = await asyncio.create_subprocess_exec(
            self.player,
            sound_library[tipus][index],
            # subprocess args
            stdin=None,
            stdout=asyncio.subprocess.DEVNULL,
            stderr=None,
            close_fds=True,
        )
        loop = asyncio.get_event_loop()
        # lower current_notes after note_timeout
        loop.call_later(self.note_timeout_s, self.release_note)
        # Wait on process, else it may be GC'd and the process killed
        await proc.communicate()

    def play_sound(self, size, path):
        max_pitch = 100.0
        log_used = 1.0715307808111486871978099
        pitch = 100 - min(max_pitch, math.log(size + log_used) / math.log(log_used))
        index = math.floor(pitch / 100.0 * len(sound_library["celesta"]))
        fuzz = random.randint(-2, 1)
        index += fuzz
        index = min(len(sound_library["celesta"]) - 1, index)
        index = max(0, index)  # TODO: JS has a 1, why? we'd expect a 0
        if self.current_notes < self.note_overlap:
            self.current_notes += 1
            big_internet = path.startswith(("big-tech", "mid-tech"))
            sound_section = "clav" if big_internet else "celesta"
            self.tasker.one_off(self.do_play(sound_section, index))

    def play_random_swell(self):
        self.tasker.one_off(self.do_play("swells", random.randrange(0, len(sound_library["swells"]))))

    def process_line(self, line: str):
        _epoch, ip, _action, path = line.split("|", maxsplit=3)
        short_path = "/".join(path.split("/")[:2])
        # We start visits at -1, to better profit from the log scale for the pitch
        self.visit_counters[short_path] = self.visit_counters.get(short_path, -1) + 1
        if ip not in self.known_users:
            self.play_random_swell()
        else:
            self.play_sound(self.visit_counters[short_path], path)
        self.known_users.add(ip)


async def main(**kwargs):
    rs = RabdomanciaSound(**kwargs)
    loop = asyncio.get_event_loop()
    while True:
        line = await loop.run_in_executor(None, sys.stdin.readline)
        rs.process_line(line)


def run():
    parser = argparse.ArgumentParser(description=main.__doc__)
    parser.add_argument("--player", type=str, default="mpv")

    ns = parser.parse_args()

    for st in ["celesta", "clav", "swells"]:
        if not sound_library.get(st, []):
            sys.stderr.write("""
ERROR: Could not load sounds library.

Please check that the submodule listen-to-wikipedia was checked out with:
$ git submodule update --init --recursive

""")
            sys.exit(1)
    with contextlib.suppress(KeyboardInterrupt):
        asyncio.run(main(**vars(ns)))


if __name__ == "__main__":
    run()
