from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Optional, Dict, Literal

import yaml

from .api.message import ConfigError


@dataclass(frozen=True)
class Config:
    log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR"]
    """Global log level, as passed from commandline."""

    source: Literal["alsa", "pulse", "pyaudio", "wave"]
    """:class:`.api.model.AudioSource`"""
    sink: Literal["alsa", "pulse", "pyaudio", "wave"]
    """:class:`.api.model.AudioSink`"""

    recognizer: Literal["sphinx", "vosk", "whisper"]
    """:class:`.api.model.RecognizerModel`"""
    processor: Literal["noop", "ollama", "gpt4all"]
    """:class:`.api.model.ProcessorModel`"""
    synthesizer: Literal["espeak", "coqui"]
    """:class:`.api.model.SynthesizerModel`"""

    alsa_source: Dict = field(default_factory=dict)
    """:class:`.audio.alsa.AlsaRecorder`"""
    alsa_sink: Dict = field(default_factory=dict)
    """:class:`.audio.alsa.AlsaPlayer`"""
    pulse_source: Dict = field(default_factory=dict)
    """:class:`.audio.pulse.PulseRecorder`"""
    pulse_sink: Dict = field(default_factory=dict)
    """:class:`.audio.pulse.PulsePlayer`"""
    pyaudio_source: Dict = field(default_factory=dict)
    """:class:`.audio.pyaudio.PyAudioRecorder`"""
    pyaudio_sink: Dict = field(default_factory=dict)
    """:class:`.audio.pyaudio.PyAudioPlayer`"""
    wave_source: Dict = field(default_factory=dict)
    """:class:`.audio.wave.WavePlayer`"""
    wave_sink: Dict = field(default_factory=dict)
    """:class:`.audio.wave.WaveRecorder`"""

    speech_segmenter: Literal["simple", "median", "band", "sphinx"] = "simple"
    """:class:`.api.model.SpeechSegmenter`"""
    speech_buffer_limit: float = 30.0
    """Limit on a possibly spurious single utterance in seconds, enforced by :class:`.api.model.SpeechSegmenter`."""
    speech_pause_limit: float = 30.0
    """Limit on silence during an utterance in seconds, enforced by :class:`.api.model.SpeechSegmenter`."""

    simple_speech_segmenter: Dict = field(default_factory=dict)
    """:class:`.recognizer.speech.median.SimpleSegmenter`"""
    median_speech_segmenter: Dict = field(default_factory=dict)
    """:class:`.recognizer.speech.median.MedianSegmenter`"""
    band_speech_segmenter: Dict = field(default_factory=dict)
    """:class:`.recognizer.speech.rosa.BandSegmenter`"""
    sphinx_speech_segmenter: Dict = field(default_factory=dict)
    """:class:`.recognizer.speech.sphinx.SphinxSegmenter`"""

    sphinx_recognizer: Dict = field(default_factory=dict)
    """:class:`.recognizer.sphinx.SphinxRecognizer`"""
    vosk_recognizer: Dict = field(default_factory=dict)
    """:class:`.recognizer.vosk.VoskRecognizer`"""
    whisper_recognizer: Dict = field(default_factory=dict)
    """:class:`.recognizer.whisper.WhisperRecognizer`"""

    ollama_processor: Dict = field(default_factory=dict)
    """:class:`.processor.ollama.OllamaProcessorModel`"""
    gpt4all_processor: Dict = field(default_factory=dict)
    """:class:`.processor.gpt4all.Gpt4AllProcessorModel`"""

    sentence_segmenter: Literal["split", "sbd"] = "split"
    """:class:`.api.model.SentenceSegmenter`"""
    split_sentence_segmenter: Dict = field(default_factory=dict)
    """:class:`.synthesizer.sentence.segmenter.SentenceSplitSegmenter`"""
    sbd_sentence_segmenter: Dict = field(default_factory=dict)
    """:class:`.synthesizer.sentence.sbd.SentenceBoundarySegmenter`"""

    feedback: Literal["noop", "speech", "beep"] = "beep"
    """:class:`.api.model.OutputFilter`"""
    beep_feedback: Dict = field(default_factory=dict)
    """:class:`.synthesizer.feedback.beep.BeepFeedbackGenerator`"""

    espeak_synthesizer: Dict = field(default_factory=dict)
    """:class:`.synthesizer.espeak.EspeakSynthesizer`"""
    coqui_synthesizer: Dict = field(default_factory=dict)
    """:class:`.synthesizer.coqui.CoquiSynthesizerModel`"""

    keywords: Dict[str, str] = field(default_factory=dict)
    """Set alternate hotwords for ``start``, ``reset``, ``commit``, or ``stop``."""

    logging: Optional[Dict] = None
    """`Logging config <https://docs.python.org/3/library/logging.config.html>`__, applied by each process."""

    @classmethod
    def from_dict(cls, **kwargs: Any) -> "Config":
        try:
            return cls(**kwargs)
        except TypeError as e:
            raise ConfigError(f"Cannot apply config: {str(e)}") from None

    @classmethod
    def from_file(cls, filename: Optional[Path], **kwargs: Any) -> "Config":
        if filename is None:
            return cls.from_dict(**kwargs)
        try:
            with filename.open("rt", encoding="utf-8", errors="strict") as fp:
                doc: Any = yaml.safe_load(fp)
            if isinstance(doc, dict):
                return cls.from_dict(**doc, **kwargs)
            else:
                raise ValueError(f"Expected YAML object, got {type(doc)}")
        except (OSError, yaml.YAMLError, ValueError) as e:
            raise ConfigError(f"Cannot load config from '{filename}': {str(e)}") from None