"""Vorze CSV script module."""
import csv
import io
from abc import abstractmethod
from dataclasses import dataclass
from typing import BinaryIO
from typing import Type
from typing import TypeVar

from typing_extensions import TypeAlias

from ..command.vorze import BaseVorzeCommand
from ..command.vorze import VorzeRotateCommand
from ..exceptions import ParseError
from .base import ScriptCommand
from .base import SerializableScript


_T = TypeVar("_T", bound=BaseVorzeCommand)


@dataclass
class VorzeScriptCommand(ScriptCommand[_T]):
    """Timed Vorze script command."""


_SC: TypeAlias = VorzeScriptCommand


class VorzeScript(SerializableScript[_T]):
    """Generic Vorze CSV script.

    Note:
        Vorze CSVs time offsets are integers in tenths of a second. Internally
        we store offsets in milliseconds so loss of resolution will occur when
        converting to CSV.
    """

    OFFSET_DENOM = 100

    @classmethod
    @abstractmethod
    def _command_cls(cls) -> Type[_T]:
        """Return command class for this script."""

    def dump(self, fp: BinaryIO) -> None:
        """Serialize script to file.

        Arguments:
            fp: A file-like object opened for writing.
        """
        text_fp = io.TextIOWrapper(fp, newline="")
        try:
            writer = csv.writer(text_fp)
            writer.writerows(
                (self.offset_from_ms(cmd.offset),) + tuple(cmd.cmd.to_csv())
                for cmd in self.commands
            )
        finally:
            text_fp.detach()

    @classmethod
    def load(cls, fp: BinaryIO) -> "VorzeScript[_T]":
        """Deserialize script from file.

        Arguments:
            fp: A file-like object opened for reading.

        Returns:
            Loaded command script.

        Raises:
            ParseError: A CSV parsing error occured.
        """
        try:
            reader = csv.reader(io.TextIOWrapper(fp, newline=""))
            return cls(
                VorzeScriptCommand(
                    cls.offset_to_ms(int(row[0])),
                    cls._command_cls().from_csv(row[1:]),
                )
                for row in reader
            )
        except (csv.Error, ValueError) as e:
            raise ParseError("Failed to parse file as Vorze CSV.") from e

    @classmethod
    def offset_from_ms(cls, offset_ms: int) -> int:
        """Convert millisecond time offset to Vorze CSV offsets."""
        return round(offset_ms / cls.OFFSET_DENOM)

    @classmethod
    def offset_to_ms(cls, offset_csv: int) -> int:
        """Convert Vorze CSV time offsets to millisecond offsets."""
        return offset_csv * cls.OFFSET_DENOM


class VorzeRotateScript(VorzeScript[VorzeRotateCommand]):
    """Vorze rotation commands script.

    Note:
        Shared script format for both Cyclone and UFO devices.
    """

    @classmethod
    def _command_cls(cls) -> Type[VorzeRotateCommand]:
        """Return command class for this script."""
        return VorzeRotateCommand
