import asyncio
import mido
import socketio
from prompt_toolkit import PromptSession
from prompt_toolkit.patch_stdout import patch_stdout

sio = socketio.AsyncClient()

note_names = ["c", "cs", "d", "ds", "e", "f", "fs", "g", "gs", "a", "as", "b"]

playStop = False


class AsyncGenerator:
    def __init__(self, generator):
        self.generator = generator
        self._iter = iter(generator)

    def __aiter__(self):
        return self

    async def __anext__(self):
        try:
            value = await asyncio.to_thread(self._safe_next)
            return value
        except StopAsyncIteration:
            raise StopAsyncIteration

    def _safe_next(self):
        try:
            return next(self._iter)
        except StopIteration:
            raise StopAsyncIteration


def noteToName(note):
    """MIDIノート番号を音階に変換"""
    octave = (note // 12) - 2  # オクターブ番号

    if octave < -1:  # A-1以下のオクターブは無効
        return None
    elif octave > 7:  # C7以上のオクターブは無効
        return None

    note_name = note_names[note % 12]  # 音名（C, Cs, D, Ds...）
    return f"{note_name}{octave}"


@sio.on("update:room_info")
async def updateRoomInfo(data):
    print("Room info: ", data)


@sio.on("update:room_member")
async def updateRoomMember(data):
    print(f"{len(data) - 1}人が聴いています")


@sio.on("update:room_member")
async def updateRoomMember(data):
    print(f"{len(data) - 1}人が聴いています")


@sio.on("talk")
async def onTalk(data):
    print(f"{data["uid"]}: {data["comment"]}")


@sio.on("iine")
async def onTalk(uid):
    print(f"{uid} +iine")


async def playMidi(file: str):
    global playStop

    # MIDIファイル全体をメモリに読み込む
    midi = mido.MidiFile(file)

    async for msg in AsyncGenerator(midi.play()):
        if playStop:
            playStop = False
            break

        # ノートオンのメッセージの場合にリクエストを送信
        if msg.type == "note_on":
            noteName = noteToName(msg.note)
            if noteName:
                await sio.emit(
                    "p",
                    (
                        noteToName(msg.note),
                        0.5,
                    ),
                )


async def consoleInput():
    global playStop

    session = PromptSession()
    with patch_stdout():
        while True:
            raw = await session.prompt_async("> ")
            args = raw.split(" ")
            cmd = args[0]
            if cmd == "stop":
                print("クライアント停止中...")
                break
            if cmd == "songStop":
                print("曲停止中...")
                playStop = True
                break
            elif cmd == "reconnect":
                print("Connecting...")
                await sio.connect("https://epiano.jp:2096", transports=["websocket"])
                await sio.emit("room_set", "main")
                print("Connected")
            elif cmd == "play":
                asyncio.create_task(playMidi(args[1]))
            elif cmd == "room":
                await sio.emit("room_set", args[1])
            elif cmd == "roomList":
                print(
                    ", ".join(
                        [
                            "main",
                            "zatsudan",
                            "ongaku",
                            "rendan1",
                            "rendan2",
                            "kodomo",
                            "newbe1",
                            "newbe2",
                            "newbe3",
                            "middle1",
                            "middle2",
                            "izakaya",
                            "lounge",
                            "pianist",
                            "pasonist",
                            "piko",
                            "anon_1",
                            "anon_2",
                        ]
                    )
                )
            elif cmd == "chat":
                await sio.emit("talk", args[1])
            elif cmd == "help":
                print(
                    ", ".join(
                        [
                            "stop: 停止",
                            "reconnect: 再読み込み",
                            "play: midiファイルを再生",
                            "songStop: 再生しているmidiファイルを停止します",
                            "room: ルームに接続",
                            "roomList: サーバーからのルームリスト",
                            "chat: チャットを送信",
                            "help: このコマンド",
                        ]
                    )
                )
            else:
                print(f"Unknown command: {cmd}")
    # ループ終わったら止める
    await sio.disconnect()
    for task in asyncio.all_tasks():
        if task is not asyncio.current_task():
            task.cancel()


async def main():
    print("Connecting...")
    await sio.connect("https://epiano.jp:2096", transports=["websocket"])
    await sio.emit("room_set", "main")
    print("Connected")
    await consoleInput()
    await sio.disconnect()


if __name__ == "__main__":
    asyncio.run(main())
