import time
import os
import random
import threading
import subprocess
import speech_recognition as sr
import google.generativeai as genai
import pygame
import digitalio
import board
from PIL import Image, ImageDraw, ImageFont
import adafruit_rgb_display.st7735 as st7735

# ==========================================
# 1. 各種設定（APIキー・ピン配置・フォント）
# ==========================================
GEMINI_API_KEY = "Gemini API キー" # ←AIza・・・から始まる長い文字列のAPIキーに書き換えてください
genai.configure(api_key=GEMINI_API_KEY)
model = genai.GenerativeModel('gemini-2.5-flash')#gemini-2.5-flash-Liteに変更すると無料枠が増えます

# ディスプレイ設定（文字が正しい向きになるよう rotation=270 に設定）
cs_pin = digitalio.DigitalInOut(board.D23)
dc_pin = digitalio.DigitalInOut(board.D25)
reset_pin = digitalio.DigitalInOut(board.D24)
BAUDRATE = 24000000
spi = board.SPI()
disp = st7735.ST7735R(spi, rotation=270, cs=cs_pin, dc=dc_pin, rst=reset_pin, baudrate=BAUDRATE, bgr=True)

if disp.rotation % 180 == 90:
    width, height = disp.height, disp.width
else:
    width, height = disp.width, disp.height

face_image = Image.new("RGB", (width, height))
draw = ImageDraw.Draw(face_image)

# 日本語フォントの設定（サイズ11px）
try:
    font_path = "/usr/share/fonts/opentype/ipafont-gothic/ipag.ttf"
    font = ImageFont.truetype(font_path, 11)
except IOError:
    font = ImageFont.load_default()

# オーディオ設定
pygame.mixer.init()
mic_index = 1

# 状態管理
current_status = "idle"
speaking_text = ""  # 画面に表示するおしゃべり文字
is_running = True

# ==========================================
# 2. 画面描画＆アニメーション用スレッド
# ==========================================
def face_loop():
    global current_status, is_running
    direction = "center"
    
    while is_running:
        if current_status == "idle":
            draw_face(blink=False, direction=direction)
            time.sleep(random.uniform(2.0, 4.0))
            if random.random() < 0.3:
                direction = random.choice(["center", "left", "right", "up", "down"])
            else:
                direction = "center"
            draw_face(blink=True, direction=direction)
            time.sleep(0.15)
            
        elif current_status == "thinking":
            # 考えてる時は目を上に向けて早くまばたき
            draw_face(blink=False, direction="up")
            time.sleep(0.3)
            draw_face(blink=True, direction="up")
            time.sleep(0.1)
            
        elif current_status == "speaking":
            # 喋ってる時は目が上下にジタバタ
            draw_face(blink=False, direction="down")
            time.sleep(0.15)
            draw_face(blink=False, direction="center")
            time.sleep(0.15)

def draw_face(blink=False, direction="center"):
    """液晶に顔と吹き出しを描画する"""
    global speaking_text
    
    BG_COLOR = (255, 215, 0) # 黄色
    EYE_COLOR = (0, 0, 0)    # 黒
    
    # 1. 背景の黄色
    draw.rectangle((0, 0, width, height), fill=BG_COLOR)
    
    # 吹き出し表示時は、目が潰れないように少し上に配置する
    y_offset = -12 if current_status == "speaking" else 0
    
    eye_w = int(width * 0.12)
    eye_h = int(height * 0.22)
    lx, rx = int(width * 0.32), int(width * 0.68)
    ly = ry = int(height * 0.45) + y_offset
    
    # 視線オフセット
    ox, oy = 0, 0
    if direction == "left": ox = -4
    elif direction == "right": ox = 4
    elif direction == "up": oy = -4
    elif direction == "down": oy = 4

    # 2. 目の描画（ジト目やお困り目のパターンも作れますが、今回は標準で）
    if blink:
        bh = 4
        draw.rectangle((lx+ox - eye_w//2, ly+oy - bh//2, lx+ox + eye_w//2, ly+oy + bh//2), fill=EYE_COLOR)
        draw.rectangle((rx+ox - eye_w//2, ry+oy - bh//2, rx+ox + eye_w//2, ry+oy + bh//2), fill=EYE_COLOR)
    else:
        draw.ellipse((lx+ox - eye_w//2, ly+oy - eye_h//2, lx+ox + eye_w//2, ly+oy + eye_h//2), fill=EYE_COLOR)
        draw.ellipse((rx+ox - eye_w//2, ry+oy - eye_h//2, rx+ox + eye_w//2, ry+oy + eye_h//2), fill=EYE_COLOR)
        
    # 3. 喋っている時だけ画面下部に「吹き出し」を描画
    if current_status == "speaking" and speaking_text:
        pad = 4
        box_y1 = height - 42
        box_y2 = height - pad
        draw.rounded_rectangle((pad, box_y1, width - pad, box_y2), radius=4, fill=(255, 255, 255), outline=EYE_COLOR, width=1)
        
        # 吹き出しの上の三角の突起
        draw.polygon([(width//2 - 4, box_y1), (width//2 + 4, box_y1), (width//2, box_y1 - 4)], fill=(255, 255, 255), outline=EYE_COLOR)
        draw.polygon([(width//2 - 3, box_y1), (width//2 + 3, box_y1), (width//2, box_y1 - 2)], fill=(255, 255, 255))

        # 文字の表示（長すぎる場合は13文字で改行）
        display_text = speaking_text
        if len(display_text) > 13:
            display_text = display_text[:13] + "\n" + display_text[13:]
            
        draw.text((pad + 6, box_y1 + 4), display_text, fill=EYE_COLOR, font=font)
        
    disp.image(face_image)

# ==========================================
# 3. 発話関数（ローカル音声合成 ＋ 余韻）
# ==========================================
def jtalk_speak(text):
    global current_status, speaking_text
    
    mech_dic = "/var/lib/mecab/dic/open-jtalk/naist-jdic"
    voice_file = "/usr/share/fonts/truetype/hts/mei_happy.htsvoice"
    out_wav = "jtalk_output.wav"
    
    cmd = f"open_jtalk -x {mech_dic} -m {voice_file} -ow {out_wav} -r 1.1"
    subprocess.run(cmd.split(), input=text.encode('utf-8'), stdout=subprocess.DEVNULL)
    
    if os.path.exists(out_wav):
        speaking_text = text
        current_status = "speaking"
        
        pygame.mixer.music.load(out_wav)
        pygame.mixer.music.play()
        while pygame.mixer.music.get_busy():
            time.sleep(0.05)
        
        # 確実に再生をストップさせてから安全に解放する
        pygame.mixer.music.stop()
        try:
            pygame.mixer.music.unload()
        except AttributeError:
            pass # もしclose等のエラーが出ても無視して次に進む
            
        # ファイルが確実に存在する場合のみ削除する
        if os.path.exists(out_wav):
            try:
                os.remove(out_wav)
            except PermissionError:
                pass
        
        # 1.5秒間、喋り終わった後も文字を画面に残す
        time.sleep(1.5)
        speaking_text = ""

# ==========================================
# 4. メイン対話ループ（Gemini脳みそ版）
# ==========================================
def main():
    global current_status, is_running
    r = sr.Recognizer()
    
    r.pause_threshold = 0.5
    r.non_speaking_duration = 0.3
    
    t = threading.Thread(target=face_loop)
    t.daemon = True
    t.start()
    
    print("--- Gemini 2.5 Flash ＋ 吹き出し表示スタックチャン 起動 ---")
    
    try:
        with sr.Microphone(device_index=mic_index) as source:
            print("[システム] ノイズ測定中...")
            r.adjust_for_ambient_noise(source, duration=2)
            r.dynamic_energy_threshold = True
            
            while True:
                current_status = "idle"
                print("\n★ スタックチャンに話しかけてください...")
                
                try:
                    audio = r.listen(source, timeout=None, phrase_time_limit=7)
                    
                    # 考えている状態にする
                    current_status = "thinking"
                    print("[音声認識・Gemini思考中...]")
                    
                    user_text = r.recognize_google(audio, language="ja-JP")
                    print(f"あなた: 「{user_text}」")
                    
                    # Geminiへの指示出し
                    prompt = f"""
                    あなたは手のひらサイズの可愛いロボット「スタックチャン」です。
                    以下のルールに絶対に従って返答してください。
                    ・短く、1文（20文字以内）で答えること。
                    ・語尾は「〜だよ！」「〜だね！」など、元気で親しみやすい口調にすること。
                    
                    ユーザーからの言葉: {user_text}
                    """
                    
                    response = model.generate_content(prompt)
                    reply_text = response.text.strip()
                    
                    print(f"スタックチャン: 「{reply_text}」")
                    jtalk_speak(reply_text)
                    
                except sr.UnknownValueError:
                    # 聞き取れなかったらスルーして待機に戻る
                    continue
                except sr.WaitTimeoutError:
                    continue
                except Exception as e:
                    # 無料枠制限（429エラーなど）に引っかかった場合の処理
                    print(f"[エラーが発生しました]: {e}")
                    # 画面上でスタックチャンがエラーを教えてくれる
                    jtalk_speak("無料枠の上限に達しちゃったよ。")
                    time.sleep(5) # 5秒待ってから再開

    except KeyboardInterrupt:
        print("\n終了します。")
        is_running = False
        t.join(timeout=1)
        draw.rectangle((0, 0, width, height), fill=(0, 0, 0))
        disp.image(face_image)

if __name__ == "__main__":
    main()