#!/usr/bin/env python3
"""
G1 Arabic TTS — Offline text-to-speech with playback on G1 built-in speaker.
Supports Piper TTS (recommended) and tts_arabic as backends.

Install on robot (gemini env):
  pip install piper-tts
  # First run auto-downloads Arabic voice model (~50MB)

Usage (run ON the robot):
  python3 g1_tts_arabic.py "مرحبا بكم في لوتاه"
  python3 g1_tts_arabic.py "مرحبا" --save "/home/unitree/SanadVoice/recorded voices/marhaba.wav"
  python3 g1_tts_arabic.py --interactive
  python3 g1_tts_arabic.py "Hello welcome" --lang en
  python3 g1_tts_arabic.py --list-voices
"""

import argparse
import io
import json
import os
import sys
import time
import wave
import numpy as np

TARGET_RATE = 16000
DDS_IFACE = "eth0"


def _init_g1_audio():
    """Initialize G1 AudioClient."""
    from unitree_sdk2py.core.channel import ChannelFactoryInitialize
    from unitree_sdk2py.g1.audio.g1_audio_client import AudioClient
    ChannelFactoryInitialize(0, DDS_IFACE)
    client = AudioClient()
    client.SetTimeout(10.0)
    client.Init()
    client.SetVolume(100)
    return client


def _play_on_g1(client, audio_16k: np.ndarray):
    """Play 16kHz mono int16 audio on G1 speaker."""
    from unitree_sdk2py.g1.audio.g1_audio_api import (
        ROBOT_API_ID_AUDIO_START_PLAY,
        ROBOT_API_ID_AUDIO_STOP_PLAY,
    )

    client._Call(ROBOT_API_ID_AUDIO_STOP_PLAY, json.dumps({"app_name": "tts"}))
    time.sleep(0.3)

    pcm = audio_16k.tobytes()
    sid = f"s_{int(time.time() * 1000)}"
    param = json.dumps({
        "app_name": "tts",
        "stream_id": sid,
        "sample_rate": TARGET_RATE,
        "channels": 1,
        "bits_per_sample": 16,
    })
    client._CallRequestWithParamAndBin(ROBOT_API_ID_AUDIO_START_PLAY, param, list(pcm))

    duration = len(audio_16k) / TARGET_RATE
    time.sleep(duration + 0.5)
    client._Call(ROBOT_API_ID_AUDIO_STOP_PLAY, json.dumps({"app_name": "tts"}))
    return duration


def _resample(audio: np.ndarray, src_rate: int) -> np.ndarray:
    """Resample to 16kHz."""
    if src_rate == TARGET_RATE:
        return audio
    tl = int(len(audio) * TARGET_RATE / src_rate)
    return np.interp(
        np.linspace(0, len(audio), tl, endpoint=False),
        np.arange(len(audio)),
        audio.astype(np.float64),
    ).astype(np.int16)


def _save_wav(path: str, audio: np.ndarray, rate: int = TARGET_RATE):
    """Save audio as WAV file."""
    wf = wave.open(path, "wb")
    wf.setnchannels(1)
    wf.setsampwidth(2)
    wf.setframerate(rate)
    wf.writeframes(audio.tobytes())
    wf.close()


# ─── PIPER TTS BACKEND ──────────────────────────────────────

def tts_piper(text: str, voice: str = "ar_JO-kareem-medium", speaker: int = None):
    """Generate speech using Piper TTS. Returns (audio_int16, sample_rate)."""
    try:
        import subprocess
        # Use piper CLI — most reliable on ARM64
        cmd = ["piper", "--model", voice, "--output_raw"]
        if speaker is not None:
            cmd += ["--speaker", str(speaker)]

        proc = subprocess.run(
            cmd, input=text.encode("utf-8"),
            capture_output=True, timeout=30,
        )

        if proc.returncode != 0:
            stderr = proc.stderr.decode()
            # If model not found, try downloading
            if "not found" in stderr.lower() or "No such file" in stderr:
                print(f"  Downloading voice model '{voice}'...")
                proc = subprocess.run(
                    cmd, input=text.encode("utf-8"),
                    capture_output=True, timeout=120,
                )

        if proc.returncode != 0:
            raise RuntimeError(f"Piper error: {proc.stderr.decode()[:200]}")

        # Piper outputs raw 16-bit mono PCM at 22050Hz by default
        audio = np.frombuffer(proc.stdout, dtype=np.int16)
        return audio, 22050

    except FileNotFoundError:
        raise RuntimeError("Piper not installed. Run: pip install piper-tts")


def tts_piper_python(text: str, voice: str = "ar_JO-kareem-medium"):
    """Generate speech using Piper Python API."""
    try:
        from piper import PiperVoice

        model_path = os.path.expanduser(f"~/.local/share/piper-voices/{voice}.onnx")
        if not os.path.exists(model_path):
            # Try auto-download via CLI first
            print(f"  Model not found at {model_path}")
            print(f"  Run: piper --model {voice} --output_raw <<< 'test'")
            raise FileNotFoundError(model_path)

        pv = PiperVoice.load(model_path)
        buf = io.BytesIO()
        with wave.open(buf, "wb") as wf:
            pv.synthesize(text, wf)

        buf.seek(0)
        with wave.open(buf, "rb") as wf:
            rate = wf.getframerate()
            audio = np.frombuffer(wf.readframes(wf.getnframes()), dtype=np.int16)

        return audio, rate

    except ImportError:
        raise RuntimeError("Piper not installed. Run: pip install piper-tts")


# ─── ESPEAK BACKEND (FALLBACK) ──────────────────────────────

def tts_espeak(text: str, lang: str = "ar", speed: int = 130):
    """Generate speech using espeak-ng. Low quality but always available."""
    import subprocess

    wav_path = "/tmp/_espeak_tts.wav"
    cmd = ["espeak-ng", "-v", lang, "-s", str(speed), "-w", wav_path, text]
    proc = subprocess.run(cmd, capture_output=True)

    if proc.returncode != 0:
        raise RuntimeError(f"espeak-ng error: {proc.stderr.decode()[:200]}")

    with wave.open(wav_path, "rb") as wf:
        rate = wf.getframerate()
        audio = np.frombuffer(wf.readframes(wf.getnframes()), dtype=np.int16)

    os.unlink(wav_path)
    return audio, rate


# ─── UNIFIED TTS ─────────────────────────────────────────────

def synthesize(text: str, backend: str = "auto", lang: str = "ar", voice: str = None):
    """Synthesize text to audio. Returns (audio_int16_16kHz, duration)."""

    if backend == "auto":
        # Try piper first, fall back to espeak
        for try_backend in ["piper", "espeak"]:
            try:
                return synthesize(text, backend=try_backend, lang=lang, voice=voice)
            except RuntimeError as e:
                print(f"  {try_backend} failed: {e}")
                continue
        raise RuntimeError("No TTS backend available!")

    if backend == "piper":
        if voice is None:
            voice = "ar_JO-kareem-medium" if lang == "ar" else "en_US-lessac-medium"
        audio, rate = tts_piper(text, voice=voice)

    elif backend == "espeak":
        audio, rate = tts_espeak(text, lang=lang)

    else:
        raise ValueError(f"Unknown backend: {backend}")

    # Resample to 16kHz
    audio_16k = _resample(audio, rate)
    duration = len(audio_16k) / TARGET_RATE

    return audio_16k, duration


# ─── COMMANDS ─────────────────────────────────────────────────

def cmd_speak(args):
    """Synthesize and play on G1."""
    text = args.text
    print(f'Text: "{text}"')
    print(f"Backend: {args.backend}, lang: {args.lang}")

    audio, duration = synthesize(text, backend=args.backend, lang=args.lang, voice=args.voice)
    print(f"Generated: {duration:.1f}s")

    if args.save:
        _save_wav(args.save, audio)
        print(f"Saved: {args.save}")

    if not args.no_play:
        print("Playing on G1...")
        client = _init_g1_audio()
        _play_on_g1(client, audio)
        print("Done.")


def cmd_interactive(args):
    """Interactive Arabic TTS loop."""
    print("G1 Arabic TTS — Interactive Mode")
    print("Type Arabic (or English) text, press Enter to speak.")
    print("Commands: /lang ar|en, /voice <name>, /backend piper|espeak, /quit\n")

    client = _init_g1_audio()
    lang = "ar"
    backend = args.backend
    voice = args.voice

    while True:
        try:
            text = input(f"[{lang}] > ").strip()
        except (KeyboardInterrupt, EOFError):
            print("\nBye.")
            break

        if not text:
            continue

        if text.startswith("/"):
            parts = text.split()
            cmd = parts[0]
            if cmd == "/quit":
                break
            elif cmd == "/lang" and len(parts) > 1:
                lang = parts[1]
                print(f"  Language: {lang}")
            elif cmd == "/voice" and len(parts) > 1:
                voice = parts[1]
                print(f"  Voice: {voice}")
            elif cmd == "/backend" and len(parts) > 1:
                backend = parts[1]
                print(f"  Backend: {backend}")
            elif cmd == "/save" and len(parts) > 1:
                save_path = " ".join(parts[1:])
                print(f"  Next speech will be saved to: {save_path}")
            else:
                print("  Commands: /lang ar|en, /voice <name>, /backend piper|espeak, /quit")
            continue

        try:
            audio, duration = synthesize(text, backend=backend, lang=lang, voice=voice)
            print(f"  {duration:.1f}s — playing...", end=" ", flush=True)
            _play_on_g1(client, audio)
            print("done")
        except Exception as e:
            print(f"  Error: {e}")


def cmd_list_voices(args):
    """List available Piper voices."""
    import subprocess

    print("Piper Arabic voices:")
    print("  ar_JO-kareem-low      — Jordanian Arabic (low quality, fastest)")
    print("  ar_JO-kareem-medium   — Jordanian Arabic (medium quality, recommended)")
    print()
    print("Piper English voices:")
    print("  en_US-lessac-medium   — US English male")
    print("  en_US-amy-medium      — US English female")
    print("  en_GB-alba-medium     — British English")
    print()
    print("espeak-ng Arabic voices:")
    try:
        result = subprocess.run(["espeak-ng", "--voices=ar"], capture_output=True, text=True)
        print(result.stdout)
    except FileNotFoundError:
        print("  espeak-ng not installed")


def main():
    parser = argparse.ArgumentParser(
        description="G1 Arabic TTS — Offline text-to-speech on G1 speaker",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Examples:
  %(prog)s "مرحبا بكم في لوتاه"
  %(prog)s "مرحبا" --save marhaba.wav
  %(prog)s "Hello" --lang en
  %(prog)s --interactive
  %(prog)s --list-voices
  %(prog)s "مرحبا" --backend espeak
        """,
    )
    parser.add_argument("text", nargs="?", help="Text to speak")
    parser.add_argument("--lang", "-l", default="ar", help="Language: ar, en (default: ar)")
    parser.add_argument("--backend", "-b", default="auto", choices=["auto", "piper", "espeak"],
                        help="TTS backend (default: auto)")
    parser.add_argument("--voice", "-V", default=None, help="Piper voice name")
    parser.add_argument("--save", "-s", default=None, help="Save WAV to path")
    parser.add_argument("--no-play", action="store_true", help="Don't play on G1, just save")
    parser.add_argument("--interactive", "-i", action="store_true", help="Interactive mode")
    parser.add_argument("--list-voices", action="store_true", help="List available voices")

    args = parser.parse_args()

    if args.list_voices:
        cmd_list_voices(args)
    elif args.interactive:
        cmd_interactive(args)
    elif args.text:
        cmd_speak(args)
    else:
        parser.print_help()


if __name__ == "__main__":
    main()
