"""
おはなしボタン
小学校低学年向け音声入力・テキスト蓄積システム

必要なライブラリのインストール:
    pip install pyttsx3 sounddevice numpy scipy SpeechRecognition

使い方:
    python ohanashi_button.py
    
録音テキストは recordings.txt に追記されます。
"""

import tkinter as tk
from tkinter import font as tkfont
import threading
import time
import os
from datetime import datetime

import numpy as np
import sounddevice as sd
import scipy.io.wavfile as wav
import speech_recognition as sr
import pyttsx3


# ─── 設定 ────────────────────────────────────────────
SAMPLE_RATE   = 16000          # サンプリングレート (Hz)
DURATION      = 20             # 録音秒数
OUTPUT_FILE   = "recordings.txt"  # 蓄積テキストファイル
TEMP_WAV      = "temp_rec.wav"    # 一時WAVファイル
TTS_MESSAGE   = "にじゅうびょうかんで はなしてね"  # 読み上げテキスト
# ─────────────────────────────────────────────────────


def speak(text: str) -> None:
    """pyttsx3 でテキストを読み上げる（スレッド内でも安全に使えるよう毎回初期化）"""
    engine = pyttsx3.init()
    # Windows SAPI の日本語音声を優先選択
    voices = engine.getProperty("voices")
    for v in voices:
        if "japanese" in v.name.lower() or "haruka" in v.name.lower():
            engine.setProperty("voice", v.id)
            break
    engine.setProperty("rate", 140)  # 少しゆっくり
    engine.say(text)
    engine.runAndWait()
    engine.stop()


def save_text(text: str) -> None:
    """タイムスタンプ付きでテキストをファイルに追記"""
    timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    with open(OUTPUT_FILE, "a", encoding="utf-8") as f:
        f.write(f"[{timestamp}] {text}\n")


class OhanashiApp:
    def __init__(self, root: tk.Tk) -> None:
        self.root = root
        self.root.title("おはなしボタン")
        self.root.geometry("520x500")
        self.root.resizable(False, False)
        self.root.configure(bg="#FFF8E1")

        self.is_running = False
        self._build_ui()

    # ── UI 構築 ──────────────────────────────────────

    def _build_ui(self) -> None:
        # タイトル
        f_title = tkfont.Font(family="Meiryo UI", size=22, weight="bold")
        tk.Label(self.root, text="🎙 おはなしボタン", font=f_title,
                 bg="#FFF8E1", fg="#BF360C").pack(pady=(28, 0))

        # メインボタン
        f_btn = tkfont.Font(family="Meiryo UI", size=18, weight="bold")
        self.btn = tk.Button(
            self.root,
            text="ここを\nおしてね！",
            font=f_btn,
            bg="#FF7043", fg="white",
            activebackground="#E64A19", activeforeground="white",
            width=10, height=4,
            relief=tk.RAISED, bd=6,
            cursor="hand2",
            command=self._on_button_click,
        )
        self.btn.pack(pady=22)

        # ステータスラベル
        f_status = tkfont.Font(family="Meiryo UI", size=14)
        self.lbl_status = tk.Label(
            self.root, text="ボタンをおしてね！",
            font=f_status, bg="#FFF8E1", fg="#1565C0"
        )
        self.lbl_status.pack(pady=(0, 4))

        # カウントダウン表示
        f_count = tkfont.Font(family="Meiryo UI", size=44, weight="bold")
        self.lbl_count = tk.Label(
            self.root, text="",
            font=f_count, bg="#FFF8E1", fg="#C62828", width=4
        )
        self.lbl_count.pack()

        # 結果表示
        f_result = tkfont.Font(family="Meiryo UI", size=12)
        self.lbl_result = tk.Label(
            self.root, text="",
            font=f_result, bg="#FFF8E1", fg="#2E7D32",
            wraplength=480, justify="left"
        )
        self.lbl_result.pack(pady=12, padx=16)

    # ── ヘルパ：スレッドセーフな UI 更新 ────────────

    def _set_status(self, text: str, color: str = "#1565C0") -> None:
        self.root.after(0, lambda: self.lbl_status.config(text=text, fg=color))

    def _set_count(self, text: str) -> None:
        self.root.after(0, lambda: self.lbl_count.config(text=text))

    def _set_result(self, text: str) -> None:
        self.root.after(0, lambda: self.lbl_result.config(text=text))

    def _enable_button(self) -> None:
        self.root.after(0, lambda: self.btn.config(state=tk.NORMAL, bg="#FF7043"))

    def _disable_button(self) -> None:
        self.root.after(0, lambda: self.btn.config(state=tk.DISABLED, bg="#BDBDBD"))

    # ── メイン処理（別スレッド） ─────────────────────

    def _on_button_click(self) -> None:
        if self.is_running:
            return
        self.is_running = True
        self._disable_button()
        self._set_result("")
        t = threading.Thread(target=self._recording_flow, daemon=True)
        t.start()

    def _recording_flow(self) -> None:
        # 1. 音声ガイダンス
        self._set_status("じゅんびちゅう…")
        speak(TTS_MESSAGE)

        # 2. 録音開始
        self._set_status("はなしてください！🎤", color="#B71C1C")
        recording = sd.rec(
            int(DURATION * SAMPLE_RATE),
            samplerate=SAMPLE_RATE,
            channels=1,
            dtype="int16",
        )

        # 3. カウントダウン表示
        for remaining in range(DURATION, 0, -1):
            self._set_count(str(remaining))
            time.sleep(1)

        sd.wait()  # 録音終了まで待機
        self._set_count("")
        self._set_status("もじにしています… しばらくまってね", color="#E65100")

        # 4. WAV 保存
        wav.write(TEMP_WAV, SAMPLE_RATE, recording)

        # 5. 音声認識
        recognizer = sr.Recognizer()
        try:
            with sr.AudioFile(TEMP_WAV) as source:
                audio_data = recognizer.record(source)
            text = recognizer.recognize_google(audio_data, language="ja-JP")

            save_text(text)
            self._set_result(f"📝 「{text}」\n\n→ ファイルに ほぞんしたよ！")
            self._set_status("おわったよ！またおしてね 😊", color="#2E7D32")

        except sr.UnknownValueError:
            self._set_status("きこえなかったよ。もういちどためしてね", color="#B71C1C")
        except sr.RequestError as e:
            self._set_status(f"インターネットにつながっているか\nかくにんしてね", color="#B71C1C")
            print(f"[RequestError] {e}")
        except Exception as e:
            self._set_status("エラーがおきました", color="#B71C1C")
            print(f"[Error] {e}")
        finally:
            if os.path.exists(TEMP_WAV):
                os.remove(TEMP_WAV)
            self.is_running = False
            self._enable_button()


# ── エントリポイント ──────────────────────────────────

if __name__ == "__main__":
    root = tk.Tk()
    app = OhanashiApp(root)
    root.mainloop()
