from __future__ import annotations

import codecs
import re
from pathlib import Path
from typing import Protocol

from .exceptions import TextGridError


class ReadableSource(Protocol):
    def readline(self) -> str:
        ...


def detect_encoding(path: str | Path) -> str:
    for encoding in ("utf-16", "utf-8-sig", "utf-8", "ascii"):
        try:
            with codecs.open(str(path), "r", encoding=encoding) as source:
                source.readline()
            return encoding
        except UnicodeError:
            continue
    return "ascii"


def format_mark(text: str | None) -> str:
    return (text or "").replace('"', '""')


def get_mark(source: ReadableSource, short: bool) -> str:
    line = source.readline()
    if not short and not re.match(r'^\s*(text|mark) = "', line):
        raise ValueError(f"Bad entry: {line}")

    while line.count('"') % 2:
        next_line = source.readline()
        if not next_line:
            raise EOFError(f"Bad entry: {line[:20]}...")
        line += next_line

    pattern = r'^"(.*?)"\s*$' if short else r'^\s*(text|mark) = "(.*?)"\s*$'
    entry = re.match(pattern, line, re.DOTALL)
    if entry is None:
        raise ValueError(f"Bad entry: {line}")
    return entry.groups()[-1].replace('""', '"')


def parse_line(line: str, short: bool, round_digits: int | str) -> str | float:
    line = line.strip()
    if short:
        if '"' in line:
            return line[1:-1].replace('""', '"')
        return round(float(line), int(round_digits))

    if '"' in line:
        match = re.match(r'.+? = "(.*)"', line, re.DOTALL)
        if match is None:
            raise ValueError(f"Bad line: {line}")
        return match.groups()[0].replace('""', '"')

    match = re.match(r".+? = (.*)", line)
    if match is None:
        raise ValueError(f"Bad line: {line}")
    return round(float(match.groups()[0]), int(round_digits))


def parse_header(source: ReadableSource) -> tuple[str, bool]:
    header = source.readline()
    match = re.match(r'File type = "([\w ]+)"', header)
    if match is None or not match.groups()[0].startswith("ooTextFile"):
        raise TextGridError(
            "The input could not be parsed as a Praat text file because it is lacking "
            "a proper header."
        )

    short = "short" in match.groups()[0]
    file_type = parse_line(source.readline(), short, 0)
    source.readline()
    if not isinstance(file_type, str):
        raise TextGridError("The Praat object header is malformed.")
    return file_type, short
