from __future__ import annotations

import io
import os.path
import re
from pathlib import Path
from typing import TextIO

from ..constants import DEFAULT_MLF_PRECISION
from ..domain.mlf import MLF
from ..domain.models import IntervalTier, TextGrid
from ..exceptions import ErrorContext, ParseError, SerializationError
from .praat import PraatTextGridWriter


class MLFReader:
    def __init__(self, *, samplerate: float = 10e6, round_digits: int = DEFAULT_MLF_PRECISION):
        self.samplerate = samplerate
        self.round_digits = round_digits

    def read_text(self, text: str) -> MLF:
        return self._read_source(io.StringIO(text))

    def read_file(self, path: str | Path) -> MLF:
        with open(path, encoding="utf-8") as source:
            return self._read_source(source, path=Path(path))

    def _read_source(self, source: TextIO, *, path: Path | None = None) -> MLF:
        try:
            grids: list[TextGrid] = []
            source.readline()
            while True:
                line = source.readline()
                if not line:
                    break
                match = re.match(r'"(.*)"', line.rstrip())
                if not match:
                    continue
                name = match.groups()[0]
                phones = IntervalTier(name="phones")
                words = IntervalTier(name="words")
                current_word = ""
                word_start = 0.0
                word_end = 0.0
                while True:
                    parts = source.readline().rstrip().split()
                    if len(parts) == 4:
                        phone_min = round(float(parts[0]) / self.samplerate, self.round_digits)
                        phone_max = round(float(parts[1]) / self.samplerate, self.round_digits)
                        phones.add(phone_min, phone_max, parts[2])
                        if current_word:
                            words.add(word_start, word_end, current_word)
                        current_word = parts[3]
                        word_start = phone_min
                        word_end = phone_max
                    elif len(parts) == 3:
                        phone_min = round(float(parts[0]) / self.samplerate, self.round_digits)
                        phone_max = round(float(parts[1]) / self.samplerate, self.round_digits)
                        if parts[2] == "sp" and phone_min != phone_max:
                            if current_word:
                                words.add(word_start, word_end, current_word)
                            current_word = parts[2]
                            word_start = phone_min
                            word_end = phone_max
                        elif phone_min != phone_max:
                            phones.add(phone_min, phone_max, parts[2])
                        word_end = phone_max
                    else:
                        if current_word:
                            words.add(word_start, word_end, current_word)
                        break
                grid = TextGrid(name=name, tiers=(phones, words))
                grids.append(grid)
            return MLF(grids=tuple(grids))
        except Exception as exc:
            raise ParseError(
                str(exc),
                context=ErrorContext(format_name="MLF", phase="read", path=path),
            ) from exc


class MLFWriter:
    def write_textgrids(self, mlf: MLF, prefix: str | Path = "") -> int:
        writer = PraatTextGridWriter()
        target_prefix = Path(prefix)
        target_prefix.mkdir(parents=True, exist_ok=True)
        try:
            for grid in mlf:
                _, tail = os.path.split(grid.name or "")
                root, _ = os.path.splitext(tail)
                output_path = target_prefix / f"{root}.TextGrid"
                output_path.write_text(writer.write_textgrid(grid), encoding="utf-8")
            return len(mlf)
        except Exception as exc:
            raise SerializationError(str(exc)) from exc
