import logging
import signal

from prompt_toolkit import PromptSession
from prompt_toolkit.output import create_output

from .api.message import ConfigError, ModuleError, CtlMessageType, LlmMessageType
from .api.model import AudioSink, OutputFilter, SentenceSegmenter
from .config import Config
from .factory import Factory, FactoryError
from .log import setup_logging, redirect_logging
from .queue import Message, MessageQueueR, MessageQueueW
from .utils.process import Rusage


class MainTts:
    def __init__(self, logger: logging.Logger, factory: Factory) -> None:
        self._logger: logging.Logger = logger
        self._factory: Factory = factory

    def _run(self, llm: MessageQueueR[LlmMessageType], ctl: MessageQueueW[CtlMessageType]) -> None:
        with self._factory.create_synthesizer() as synthesizer:
            sink: AudioSink = self._factory.create_sink_for(synthesizer)
            feedback: OutputFilter = self._factory.create_output_filter_for(synthesizer)
            segmenter: SentenceSegmenter = self._factory.create_sentence_segmenter_for(synthesizer)
            with sink:
                playing: bool = False

                for event in llm:
                    self._logger.debug(str(event))
                    for message in feedback.accept(event):
                        if message.msg == LlmMessageType.Token:
                            if playing and message.data is not None:
                                for sentence in segmenter.push(message.data):
                                    self._logger.info(f"Synth: {sentence}")
                                    sink.play_all(synthesizer.generate(sentence))
                        elif message.msg == LlmMessageType.Utterance:
                            if playing:
                                for sentence in segmenter.drain(message.data):
                                    self._logger.info(f"Synth: {sentence}")
                                    sink.play_all(synthesizer.generate(sentence))
                        else:
                            for sentence in segmenter.drain(None):
                                self._logger.info(f"Synth: {sentence}")
                                sink.play_all(synthesizer.generate(sentence))
                            for buffer in feedback.generate(message):
                                sink.play(buffer)

                            if message.msg == LlmMessageType.Stop:
                                sink.drain()
                                return
                            elif message.msg == LlmMessageType.Start:
                                self._logger.info("Starting playback")
                                playing = True
                            elif message.msg == LlmMessageType.End:
                                self._logger.info("Stopping playback")
                                playing = False
                                sink.drain()
                                ctl.put(Message(CtlMessageType.Listen, None))

    @classmethod
    def run(cls, config: Config, llm: MessageQueueR[LlmMessageType], ctl: MessageQueueW[CtlMessageType]) -> int:
        signal.signal(signal.SIGINT, signal.SIG_IGN)  # handled and propagated by llm main
        logger: logging.Logger = setup_logging(config.logging, cls.__name__, config.log_level)
        with redirect_logging(logger), Rusage(logger, logging.INFO):
            try:
                factory: Factory = Factory(config)
                cls(logger, factory)._run(llm, ctl)
            except FactoryError as e:
                logger.error(str(e), exc_info=e.__cause__)
                return 1
            except (ConfigError, ModuleError) as e:
                logger.error(str(e))
                return 1
            except BaseException as e:
                logger.error(str(e), exc_info=e)
                return 1
            else:
                return 0
            finally:
                ctl.put(Message(CtlMessageType.Stop))
                ctl.close()

    @classmethod
    def run_cli(cls, config: Config) -> int:
        logger: logging.Logger = setup_logging(config.logging, cls.__name__, config.log_level)
        try:
            with redirect_logging(logger) as stdout, Rusage(logger, logging.INFO):
                multiline: bool = False
                prompt_session: PromptSession = PromptSession(multiline=multiline, output=create_output(stdout))

                factory: Factory = Factory(config)
                with factory.create_synthesizer() as synthesizer:
                    sink: AudioSink = factory.create_sink_for(synthesizer)
                    segmenter: SentenceSegmenter = factory.create_sentence_segmenter_for(synthesizer)

                    with sink:
                        try:
                            while True:
                                prompt: str = prompt_session.prompt(message=[("ansigreen bold", "> ")],
                                                                    placeholder="<Meta+Enter>" if multiline else None)
                                if not prompt:  # or ^C or ^D
                                    break

                                for sentence in segmenter.push(prompt):
                                    for buffer in synthesizer.generate(sentence):
                                        sink.play(buffer)
                                for sentence in segmenter.drain(None):
                                    for buffer in synthesizer.generate(sentence):
                                        sink.play(buffer)
                                sink.drain()
                        except (KeyboardInterrupt, EOFError):
                            return 0
        except FactoryError as e:
            logger.error(str(e), exc_info=e.__cause__)
            return 1
        except (ConfigError, ModuleError) as e:
            logger.error(str(e))
            return 1
        except BaseException as e:
            logger.error(str(e), exc_info=e)
            return 1
        else:
            return 0