from __future__ import annotations

from dataclasses import dataclass, field


LCD_E_CLOCK_STATUS = 0xF200
LCD_E_CLOCK_DATA = 0xF201

LCD_LINE_WIDTH = 16
LCD_LINE_STARTS = (0x00, 0x40, 0x10, 0x50)
LCD_DDRAM_SIZE = 0x80


@dataclass
class LCD:
    ddram: bytearray = field(default_factory=lambda: bytearray([0x20] * LCD_DDRAM_SIZE))
    cursor: int = 0
    data_latch: int = 0
    command_latch: int = 0

    def read_status(self) -> int:
        return self.cursor & 0x7F

    def read_data(self) -> int:
        return self.data_latch & 0xFF

    def write_command(self, value: int) -> None:
        value &= 0xFF
        self.command_latch = value
        if value & 0x80:
            self.cursor = value & 0x7F
        elif value == 0x01:
            self.ddram[:] = bytes([0x20]) * LCD_DDRAM_SIZE
            self.cursor = 0
        elif value == 0x02:
            self.cursor = 0

    def write_data(self, value: int) -> None:
        value &= 0xFF
        self.data_latch = value
        if 0 <= self.cursor < LCD_DDRAM_SIZE:
            self.ddram[self.cursor] = value
        self.cursor = (self.cursor + 1) & 0x7F

    def line_text(self, line: int, width: int = LCD_LINE_WIDTH) -> str:
        if not 0 <= line < len(LCD_LINE_STARTS):
            raise ValueError(f"LCD line out of range: {line}")
        start = LCD_LINE_STARTS[line]
        return "".join(_display_char(self.ddram[(start + offset) & 0x7F]) for offset in range(width))

    def display_lines(self, lines: int = 4, width: int = LCD_LINE_WIDTH) -> list[str]:
        return [self.line_text(line, width) for line in range(lines)]

    def display_text(self, lines: int = 4, width: int = LCD_LINE_WIDTH) -> str:
        return " | ".join(self.display_lines(lines, width))


def _display_char(value: int) -> str:
    return chr(value) if 0x20 <= value <= 0x7E else "."


__all__ = [
    "LCD",
    "LCD_DDRAM_SIZE",
    "LCD_E_CLOCK_DATA",
    "LCD_E_CLOCK_STATUS",
    "LCD_LINE_STARTS",
    "LCD_LINE_WIDTH",
]
