import time
from .pcf8574 import PCF8574

class I2CLCD(PCF8574):
    """
    Minimal-Implementation des LCD-Interfaces über I2C (PCF8574).
    """
    # Steuer-Bits für das LCD-Backpack Mapping
    MASK_RS = 0x01  # Register Select (0=Befehl, 1=Daten)
    MASK_RW = 0x02  # Read/Write (wird meist auf 0/Write gelassen)
    MASK_E  = 0x04  # Enable (Flanke zum Übernehmen)
    MASK_BL = 0x08  # Backlight Control

  

    def __init__(self, device_address, rows=2, cols=16):
        # Initialisiere als normaler PCF8574 (meist Class 0x4)
        super().__init__(device_address, io_mask=0x00)
        self._rows = rows
        self._cols = cols
        self._backlight = True
        self._initialized = False  # Merker hinzufügen

        # Das LCD braucht nach dem Einschalten etwas Zeit
        #time.sleep(0.05)
        #self._init_lcd()

    def _ensure_init(self):
        """Prüft, ob I2C verfügbar ist und initialisiert bei Bedarf."""
        if not self._initialized and self.i2c is not None:
            self._init_lcd()
            self._initialized = True

    def _pulse_enable(self, data):
        """Erzeugt die nötige Enable-Flanke am LCD."""
        # Enable HIGH
        self.port.data = data | self.MASK_E
        self.apply()
        time.sleep(0.000001) # Enable Pulsweite > 450ns
        # Enable LOW
        self.port.data = data & ~self.MASK_E
        self.apply()
        time.sleep(0.000050) # Wartezeit für Befehlsausführung

    def _write_nibble(self, nibble, mode=0):
        """Schreibt 4 Bit (High-Nibble von 'nibble') zum LCD."""
        bl_flag = self.MASK_BL if self._backlight else 0
        # Daten vorbereiten (oberen 4 Bits des PCF sind D4-D7 des LCD)
        data = (nibble & 0xF0) | mode | bl_flag
        self.port.data = data
        self.apply()
        self._pulse_enable(data)

    def _send(self, value, mode=0):
        """Sendet ein Byte in zwei 4-Bit-Schritten."""
        self._write_nibble(value & 0xF0, mode)      # High Nibble
        self._write_nibble((value << 4) & 0xF0, mode) # Low Nibble

    def _init_lcd(self):
        """Die magische 4-Bit Initialisierungssequenz."""
        # Laut Datenblatt: 3x Reset-Kommando senden
        self._write_nibble(0x30)
        time.sleep(0.005)
        self._write_nibble(0x30)
        time.sleep(0.001)
        self._write_nibble(0x30)
        # In den 4-Bit Modus schalten
        self._write_nibble(0x20)

        # Ab hier im 4-Bit Modus mit 2 Zeilen (0x28)
        self._send(0x28, 0) 
        # Display an, Cursor aus, Blinken aus
        self._send(0x0C, 0)
        
        self._send(0x01, 0) # LCD_CLEARDISPLAY direkt senden
        time.sleep(0.002)
        
        #self.clearLCD()
        
        
        # Entry Mode: Auto-Increment
        self._send(0x06, 0)

    # --- Die wichtigsten Methoden aus dem LCD Interface ---

    def clearLCD(self):
        self._ensure_init() # Sicherstellen, dass das LCD bereit ist
        self._send(0x01, 0)
        time.sleep(0.002) # Clear ist ein langsamer Befehl

    def setCursor(self, row, col):
        self._ensure_init() # Sicherstellen, dass das LCD bereit ist
        """Setzt den Cursor (row 0-3, col 0-15/19)."""
        offsets = [0x00, 0x40, 0x14, 0x54]
        if row < self._rows:
            addr = col + offsets[row]
            self._send(0x80 | addr, 0)

    def writeString(self, message):
        self._ensure_init() # Sicherstellen, dass das LCD bereit ist
        """Schreibt einen String an die aktuelle Position."""
        for char in message:
            self._send(ord(char), self.MASK_RS)

    def backlight(self, on: bool):
        self._ensure_init() # Sicherstellen, dass das LCD bereit ist
        """Hintergrundbeleuchtung an/aus."""
        self._backlight = on
        # Ein Dummy-Senden um den Status am PCF zu aktualisieren
        self._write_nibble(0x00, 0)

    def createChar(self, location, bitmap):
        """
        Erstellt ein benutzerdefiniertes Zeichen im CGRAM (0-7).
        bitmap: Liste mit 8 Integern (0-31)
        """
        if not self._initialized: self._ensure_init()
        location &= 0x07  # Nur 0-7 erlaubt
        # Befehl 0x40 setzt die CGRAM-Adresse
        self._send(0x40 | (location << 3), 0)
        for i in range(8):
            self._send(bitmap[i], self.MASK_RS)
        # Danach Cursor zurück zum Display-Speicher (DDRAM)
        self._send(0x80, 0)

    def home(self):
        """Setzt den Cursor auf Position 0,0."""
        self._send(0x02, 0)
        time.sleep(0.002)

    def display(self, on: bool):
        """Schaltet das Display (den Text) an oder aus."""
        if on:
            self._send(0x0C, 0) # Display an, Cursor aus
        else:
            self._send(0x08, 0) # Alles aus

    def cursor(self, on: bool):
        """Schaltet den Unterstrich-Cursor an oder aus."""
        if on:
            self._send(0x0E, 0)
        else:
            self._send(0x0C, 0)

    def blink(self, on: bool):
        """Schaltet den blinkenden Block-Cursor an oder aus."""
        if on:
            self._send(0x0F, 0)
        else:
            self._send(0x0C, 0)

    def scrollDisplayLeft(self):
        self._send(0x18, 0)

    def scrollDisplayRight(self):
        self._send(0x1C, 0)       


