import array
import math

import pygame

from styles.base import AlarmStyle

# Time between beep retriggers; a short beep followed by this gap gives the
# classic alarm-clock "beep ... beep ... beep".
BEEP_INTERVAL = 0.5


def _make_beep_buffer(
    freq: int = 1000, duration: float = 0.15, rate: int = 44100
) -> bytes:
    """Synthesize a short square-wave beep as signed-16-bit stereo PCM.

    Matches the mixer format used by wecker.setup() (rate, -16, 2 channels) so
    it feeds straight into ``pygame.mixer.Sound``. No audio file, no extra dep.
    """
    n = int(rate * duration)
    buf = array.array("h")
    for i in range(n):
        v = 32767 if math.sin(2 * math.pi * freq * (i / rate)) >= 0 else -32767
        buf.append(v)
        buf.append(v)  # duplicate to stereo
    return buf.tobytes()


_BEEP_BUFFER = _make_beep_buffer()


class SimpleStyle(AlarmStyle):
    """Press the button once to stop. Rings with a repeating beep.

    This style plays no music file; it owns its tone via a synthesised square
    wave played through ``pygame.mixer.Sound``.
    """

    def __init__(self, set_led, music_file=None):
        super().__init__(set_led, music_file)
        self._next_beep = 0.0
        self._beep = None

    def start(self):
        # LED solid on so the button is findable in the dark.
        self.set_led(True)
        self._beep = pygame.mixer.Sound(_BEEP_BUFFER)

    def update(self, now, is_pressed):
        self.set_led(True)
        if now >= self._next_beep:
            self._beep.play()
            self._next_beep = now + BEEP_INTERVAL
        return not is_pressed  # first press -> False -> stop


if __name__ == "__main__":
    # Self-check: the timer beeps on the interval and a press stops it.
    # Bypasses start() (which needs the audio engine) by stubbing the beep.

    class _Beep:
        def __init__(self):
            self.plays = 0

        def play(self):
            self.plays += 1

    style = SimpleStyle(lambda on: None)
    style._beep = _Beep()

    assert style.update(0.0, False) is True
    assert style._beep.plays == 1  # first tick beeps

    assert style.update(0.4, False) is True  # before interval -> no beep
    assert style._beep.plays == 1

    assert style.update(0.5, False) is True  # at interval -> beep
    assert style._beep.plays == 2

    assert style.update(0.6, True) is False  # press -> stop
    print("simple self-check ok")
