#!/bin/python
# -*- coding: utf-8 -*-

# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributors.

import re
import string
import threading
import time

from fenrirscreenreader.core import debug
from fenrirscreenreader.core.i18n import _
from fenrirscreenreader.utils import line_utils


class OutputManager:
    def __init__(self):
        self.last_echo = ""
        self.interrupt_lock = threading.Lock()
        self.interrupt_running = False
        self.interrupt_thread = None
        self.interrupt_done = None
        self.interrupt_wait_timeout = 0.1

    def initialize(self, environment):
        self.env = environment
        self.env["runtime"]["SettingsManager"].load_driver(
            self.env["runtime"]["SettingsManager"].get_setting(
                "speech", "driver"
            ),
            "SpeechDriver",
        )
        self.env["runtime"]["SettingsManager"].load_driver(
            self.env["runtime"]["SettingsManager"].get_setting(
                "sound", "driver"
            ),
            "SoundDriver",
        )

    def shutdown(self):
        self.env["runtime"]["SettingsManager"].shutdown_driver("SoundDriver")
        self.env["runtime"]["SettingsManager"].shutdown_driver("SpeechDriver")

    def present_text(
        self,
        text,
        interrupt=True,
        sound_icon="",
        ignore_punctuation=False,
        announce_capital=False,
        flush=True,
    ):
        if text == "" and sound_icon == "":
            return
        if (
            self.env["runtime"]["SettingsManager"].get_setting_as_bool(
                "speech", "read_numbers_as_digits"
            )
            and len(text.strip()) > 1
        ):
            text = re.sub(r"(\d)", r"\1 ", text).rstrip()
        self.env["runtime"]["DebugManager"].write_debug_out(
            "present_text:\nsoundIcon:'" + sound_icon + "'\nText:\n" + text,
            debug.DebugLevel.INFO,
        )
        if self.play_sound_icon(sound_icon, interrupt):
            self.env["runtime"]["DebugManager"].write_debug_out(
                "sound_icon found", debug.DebugLevel.INFO
            )
            return
        if text == "":
            return
        if (len(text) > 1) and (text.strip(string.whitespace) == ""):
            return
        is_capital = self._should_announce_capital(text, announce_capital)
        use_pitch_for_capital = False

        if is_capital:
            indicator = self.env["runtime"]["SettingsManager"].get_setting(
                "speech", "capital_indicator"
            ).lower()

            if indicator == "none":
                pass  # No indication
            elif indicator == "beep":
                # Play beep with interrupt=True to fix stacking
                self.play_sound_icon("capital", True)
            elif indicator == "pitch":
                use_pitch_for_capital = True
            elif indicator == "both":
                self.play_sound_icon("capital", True)
                use_pitch_for_capital = True
            else:
                # Default to pitch for unknown values
                use_pitch_for_capital = True

        self.last_echo = text
        self.speak_text(
            text, interrupt, ignore_punctuation, use_pitch_for_capital, flush
        )

    def _should_announce_capital(self, text, announce_capital):
        if not announce_capital or not text:
            return False
        if len(text) == 1:
            return text.isupper()
        if any(char.isspace() for char in text):
            return False
        if not any(char.isalpha() for char in text):
            return False
        return text.isupper()

    def get_last_echo(self):
        return self.last_echo

    def process_mid_word_punctuation(self, text):
        """
        Process punctuation that appears mid-word to ensure proper pronunciation.
        Specifically handles dots between word characters (e.g., "settings.conf" -> "settings dot conf")
        and dots at word beginnings (e.g., ".local" -> "dot local")
        while preserving sentence-ending periods and other punctuation behavior.
        """
        if not text:
            return text
        
        # Handle dots at the beginning of words (like .local, .bashrc, .config)
        # Look for non-word character (or start of string), dot, then word characters
        text = re.sub(r'(?<!\w)\.(\w+)', r'dot \1', text)
        
        # Replace dots that appear between word characters with spoken form
        # Use a loop to handle multiple consecutive dots like www.example.com or a.b.c.d
        while re.search(r'\b\w+\.\w+\b', text):
            text = re.sub(r'\b(\w+)\.(\w+)\b', r'\1 dot \2', text)
        
        return text

    def speak_text(
        self,
        text,
        interrupt=True,
        ignore_punctuation=False,
        announce_capital=False,
        flush=True,
    ):
        if not self.env["runtime"]["SettingsManager"].get_setting_as_bool(
            "speech", "enabled"
        ):
            self.env["runtime"]["DebugManager"].write_debug_out(
                "Speech disabled in OutputManager.speak_text",
                debug.DebugLevel.INFO,
            )
            return
        if self.env["runtime"]["SpeechDriver"] is None:
            self.env["runtime"]["DebugManager"].write_debug_out(
                "No SpeechDriver in OutputManager.speak_text",
                debug.DebugLevel.ERROR,
            )
            return
        if interrupt or flush:
            self.interrupt_output()
        try:
            self.env["runtime"]["SpeechDriver"].set_language(
                self.env["runtime"]["SettingsManager"].get_setting(
                    "speech", "language"
                )
            )
        except Exception as e:
            self.env["runtime"]["DebugManager"].write_debug_out(
                "setting speech language in OutputManager.speak_text",
                debug.DebugLevel.ERROR,
            )
            self.env["runtime"]["DebugManager"].write_debug_out(
                str(e), debug.DebugLevel.ERROR
            )

        try:
            self.env["runtime"]["SpeechDriver"].set_voice(
                self.env["runtime"]["SettingsManager"].get_setting(
                    "speech", "voice"
                )
            )
        except Exception as e:
            self.env["runtime"]["DebugManager"].write_debug_out(
                "Error while setting speech voice in OutputManager.speak_text",
                debug.DebugLevel.ERROR,
            )
            self.env["runtime"]["DebugManager"].write_debug_out(
                str(e), debug.DebugLevel.ERROR
            )

        try:
            if announce_capital:
                self.env["runtime"]["SpeechDriver"].set_pitch(
                    self.env["runtime"][
                        "SettingsManager"
                    ].get_setting_as_float("speech", "capital_pitch")
                )
            else:
                self.env["runtime"]["SpeechDriver"].set_pitch(
                    self.env["runtime"][
                        "SettingsManager"
                    ].get_setting_as_float("speech", "pitch")
                )
        except Exception as e:
            self.env["runtime"]["DebugManager"].write_debug_out(
                "setting speech pitch in OutputManager.speak_text",
                debug.DebugLevel.ERROR,
            )
            self.env["runtime"]["DebugManager"].write_debug_out(
                str(e), debug.DebugLevel.ERROR
            )

        try:
            self.env["runtime"]["SpeechDriver"].set_rate(
                self.env["runtime"]["SettingsManager"].get_setting_as_float(
                    "speech", "rate"
                )
            )
        except Exception as e:
            self.env["runtime"]["DebugManager"].write_debug_out(
                "setting speech rate in OutputManager.speak_text",
                debug.DebugLevel.ERROR,
            )
            self.env["runtime"]["DebugManager"].write_debug_out(
                str(e), debug.DebugLevel.ERROR
            )

        try:
            self.env["runtime"]["SpeechDriver"].set_module(
                self.env["runtime"]["SettingsManager"].get_setting(
                    "speech", "module"
                )
            )
        except Exception as e:
            self.env["runtime"]["DebugManager"].write_debug_out(
                "setting speech module in OutputManager.speak_text",
                debug.DebugLevel.ERROR,
            )
            self.env["runtime"]["DebugManager"].write_debug_out(
                str(e), debug.DebugLevel.ERROR
            )

        try:
            self.env["runtime"]["SpeechDriver"].set_volume(
                self.env["runtime"]["SettingsManager"].get_setting_as_float(
                    "speech", "volume"
                )
            )
        except Exception as e:
            self.env["runtime"]["DebugManager"].write_debug_out(
                "setting speech volume in OutputManager.speak_text ",
                debug.DebugLevel.ERROR,
            )
            self.env["runtime"]["DebugManager"].write_debug_out(
                str(e), debug.DebugLevel.ERROR
            )

        try:
            if self.env["runtime"]["SettingsManager"].get_setting_as_bool(
                "general", "new_line_pause"
            ):
                clean_text = text.replace("\n", " , ")
            else:
                clean_text = text.replace("\n", " ")

            clean_text = self.env["runtime"]["TextManager"].replace_head_lines(
                clean_text
            )
            clean_text = self.process_mid_word_punctuation(clean_text)
            clean_text = self.env["runtime"][
                "PunctuationManager"
            ].proceed_punctuation(clean_text, ignore_punctuation)
            clean_text = re.sub(" +$", " ", clean_text)
            self.env["runtime"]["SpeechDriver"].speak(
                clean_text, True, ignore_punctuation
            )
            self.env["runtime"]["DebugManager"].write_debug_out(
                "Speak: " + clean_text, debug.DebugLevel.INFO
            )
        except Exception as e:
            self.env["runtime"]["DebugManager"].write_debug_out(
                '"speak" in OutputManager.speak_text ', debug.DebugLevel.ERROR
            )
            self.env["runtime"]["DebugManager"].write_debug_out(
                str(e), debug.DebugLevel.ERROR
            )

    def interrupt_output(self, wait=True):
        interrupt_done, started = self.start_interrupt_output()
        if wait and started and interrupt_done:
            interrupt_done.wait(timeout=self.interrupt_wait_timeout)

    def interrupt_output_async(self):
        self.start_interrupt_output()

    def start_interrupt_output(self):
        with self.interrupt_lock:
            if self.interrupt_running:
                return self.interrupt_done, False
            self.interrupt_running = True
            self.interrupt_done = threading.Event()
            self.interrupt_thread = threading.Thread(
                target=self.run_interrupt_output,
                args=(self.interrupt_done,),
                daemon=True,
            )
            interrupt_thread = self.interrupt_thread
            interrupt_done = self.interrupt_done
        interrupt_thread.start()
        return interrupt_done, True

    def run_interrupt_output(self, interrupt_done):
        try:
            self.cancel_speech()
        finally:
            interrupt_done.set()
            with self.interrupt_lock:
                if self.interrupt_done is interrupt_done:
                    self.interrupt_running = False

    def cancel_speech(self):
        try:
            self.env["runtime"]["SpeechDriver"].cancel()
            self.env["runtime"]["DebugManager"].write_debug_out(
                "Interrupt speech", debug.DebugLevel.INFO
            )
        except Exception as e:
            self.env["runtime"]["DebugManager"].write_debug_out(
                "OutputManager interrupt_output: Error canceling speech: "
                + str(e),
                debug.DebugLevel.ERROR,
            )

    def play_sound_icon(self, sound_icon="", interrupt=True):
        if sound_icon == "":
            return False
        sound_icon = sound_icon.upper()
        if not self.env["runtime"]["SettingsManager"].get_setting_as_bool(
            "sound", "enabled"
        ):
            self.env["runtime"]["DebugManager"].write_debug_out(
                "Sound disabled in OutputManager.play_sound_icon",
                debug.DebugLevel.INFO,
            )
            return False

        try:
            e = self.env["soundIcons"][sound_icon]
        except Exception as e:
            self.env["runtime"]["DebugManager"].write_debug_out(
                "OutputManager play_sound_icon: SoundIcon does not exist "
                + sound_icon
                + ": "
                + str(e),
                debug.DebugLevel.WARNING,
            )
            return False

        if self.env["runtime"]["SoundDriver"] is None:
            self.env["runtime"]["DebugManager"].write_debug_out(
                "No SoundDriver in OutputManager.play_sound_icon:  SoundDriver not loaded",
                debug.DebugLevel.ERROR,
            )
            return False

        try:
            self.env["runtime"]["SoundDriver"].set_volume(
                self.env["runtime"]["SettingsManager"].get_setting_as_float(
                    "sound", "volume"
                )
            )
        except Exception as e:
            self.env["runtime"]["DebugManager"].write_debug_out(
                "OutputManager.play_sound_icon::set_volume: " + str(e),
                debug.DebugLevel.ERROR,
            )

        try:
            self.env["runtime"]["SoundDriver"].play_sound_file(
                self.env["soundIcons"][sound_icon], interrupt
            )
            return True
        except Exception as e:
            self.env["runtime"]["DebugManager"].write_debug_out(
                "OutputManager.play_sound_icon::play_sound_file: " + str(e),
                debug.DebugLevel.ERROR,
            )
            return False

        return False

    def play_sound(self, sound_icon="", interrupt=True):
        aliases = {
            "ERROR": "ERRORSCREEN",
            "ERRORSOUND": "ERRORSCREEN",
        }
        normalized_icon = aliases.get(str(sound_icon).upper(), sound_icon)
        return self.play_sound_icon(normalized_icon, interrupt)

    def play_frequence(self, frequence, duration, interrupt=True):
        if not self.env["runtime"]["SettingsManager"].get_setting_as_bool(
            "sound", "enabled"
        ):
            self.env["runtime"]["DebugManager"].write_debug_out(
                "Sound disabled in OutputManager.play_frequence",
                debug.DebugLevel.INFO,
            )
            return False

        if frequence < 1 or frequence > 20000:
            self.env["runtime"]["DebugManager"].write_debug_out(
                "OutputManager.play_frequence::Filefrequence is out of range:"
                + str(frequence),
                debug.DebugLevel.INFO,
            )
            return False

        if self.env["runtime"]["SoundDriver"] is None:
            self.env["runtime"]["DebugManager"].write_debug_out(
                "No SoundDriver in OutputManager.play_frequence: SoundDriver not loaded",
                debug.DebugLevel.ERROR,
            )
            return False

        try:
            self.env["runtime"]["SoundDriver"].set_volume(
                self.env["runtime"]["SettingsManager"].get_setting_as_float(
                    "sound", "volume"
                )
            )
        except Exception as e:
            self.env["runtime"]["DebugManager"].write_debug_out(
                "OutputManager.play_sound_icon::set_volume: " + str(e),
                debug.DebugLevel.ERROR,
            )
        adjust_volume = 0.0
        try:
            adjust_volume = 1.0 - (frequence / 20000)
        except Exception as e:
            self.env["runtime"]["DebugManager"].write_debug_out(
                "OutputManager play_frequence: Error calculating adjust volume: "
                + str(e),
                debug.DebugLevel.ERROR,
            )
        if adjust_volume > 9.0:
            adjust_volume = 9.0

        try:
            self.env["runtime"]["SoundDriver"].play_frequence(
                frequence, duration, adjust_volume, interrupt
            )
            return True
        except Exception as e:
            self.env["runtime"]["DebugManager"].write_debug_out(
                "OutputManager.play_sound_icon::play_sound_file: " + str(e),
                debug.DebugLevel.ERROR,
            )
            return False

        return False

    def temp_disable_speech(self):
        if self.env["runtime"]["SettingsManager"].get_setting_as_bool(
            "speech", "enabled"
        ):
            self.present_text(
                _("speech temporary disabled"),
                sound_icon="SpeechOff",
                interrupt=True,
            )
            self.env["commandBuffer"]["enableSpeechOnKeypress"] = True
            # Also enable prompt watching for automatic speech restoration
            if "silenceUntilPrompt" not in self.env["commandBuffer"]:
                self.env["commandBuffer"]["silenceUntilPrompt"] = False
            self.env["commandBuffer"]["silenceUntilPrompt"] = True
            self.env["runtime"]["SettingsManager"].set_setting(
                "speech",
                "enabled",
                str(
                    not self.env["runtime"][
                        "SettingsManager"
                    ].get_setting_as_bool("speech", "enabled")
                ),
            )
            self.interrupt_output()

    def announce_active_cursor(self, interrupt_p=False):
        if self.env["runtime"]["CursorManager"].is_review_mode():
            self.present_text(" review cursor ", interrupt=interrupt_p)
        else:
            self.present_text(" text cursor ", interrupt=interrupt_p)

    def reset_SpeechDriver(self):
        """Reset speech driver to clean state - called by settings_manager"""
        if (
            "SpeechDriver" in self.env["runtime"]
            and self.env["runtime"]["SpeechDriver"]
        ):
            try:
                self.env["runtime"]["SpeechDriver"].reset()
                self.env["runtime"]["DebugManager"].write_debug_out(
                    "Speech driver reset successfully", debug.DebugLevel.INFO
                )
            except Exception as e:
                self.env["runtime"]["DebugManager"].write_debug_out(
                    f"reset_SpeechDriver error: {e}", debug.DebugLevel.ERROR
                )
