# MicroPython I2C LCD library
from lcd_api import LcdApi
from machine import I2C
import time

# commands
LCD_CLR = 0x01
LCD_HOME = 0x02

class I2cLcd(LcdApi):
    def __init__(self, i2c, i2c_addr, num_lines, num_columns):
        self.i2c = i2c
        self.i2c_addr = i2c_addr
        self.num_lines = num_lines
        self.num_columns = num_columns
        self.backlight = 0x08
        self.displaycontrol = 0x0C
        self.displayfunction = 0x28
        self.displaymode = 0x06
        super().__init__(num_lines, num_columns)
        self.hal_write_init_nibble()
        time.sleep_ms(50)
        self.hal_write_command(LCD_CLR)
        time.sleep_ms(2)

    # low-level I2C communication
    def hal_write_init_nibble(self):
        self.i2c.writeto(self.i2c_addr, bytes([0x30 | self.backlight]))
        time.sleep_ms(5)
        self.i2c.writeto(self.i2c_addr, bytes([0x30 | self.backlight]))
        time.sleep_ms(1)
        self.i2c.writeto(self.i2c_addr, bytes([0x30 | self.backlight]))
        time.sleep_ms(1)

    def hal_write_command(self, cmd):
        self.i2c.writeto(self.i2c_addr, bytes([cmd & 0xF0 | self.backlight]))
        self.i2c.writeto(self.i2c_addr, bytes([(cmd << 4) & 0xF0 | self.backlight]))

    def hal_backlight_on(self):
        self.backlight = 0x08
        self.i2c.writeto(self.i2c_addr, bytes([self.backlight]))

    def hal_backlight_off(self):
        self.backlight = 0x00
        self.i2c.writeto(self.i2c_addr, bytes([self.backlight]))

    def hal_write_data(self, data):
        self.i2c.writeto(self.i2c_addr, bytes([(data & 0xF0) | 0x01 | self.backlight]))
        self.i2c.writeto(self.i2c_addr, bytes([((data << 4) & 0xF0) | 0x01 | self.backlight]))
        
# ---- MicroPython I2C LCD class ----
class I2cLcd:
    # commands
    LCD_CLR = 0x01
    LCD_HOME = 0x02
    LCD_ENTRYMODESET = 0x04
    LCD_DISPLAYCONTROL = 0x08
    LCD_FUNCTIONSET = 0x20
    LCD_SETDDRAMADDR = 0x80

    LCD_ENTRYLEFT = 0x02
    LCD_ENTRYSHIFTDECREMENT = 0x00
    LCD_DISPLAYON = 0x04
    LCD_2LINE = 0x08
    LCD_5x8DOTS = 0x00
    LCD_BACKLIGHT = 0x08

    def __init__(self, i2c, i2c_addr, num_lines=2, num_columns=16):
        self.i2c = i2c
        self.i2c_addr = i2c_addr
        self.num_lines = num_lines
        self.num_columns = num_columns
        self.backlight = self.LCD_BACKLIGHT

        time.sleep_ms(20)
        self._write_init_nibble(0x03)
        time.sleep_ms(5)
        self._write_init_nibble(0x03)
        time.sleep_us(150)
        self._write_init_nibble(0x03)
        self._write_init_nibble(0x02)

        self._write_command(self.LCD_FUNCTIONSET | self.LCD_2LINE | self.LCD_5x8DOTS)
        self._write_command(self.LCD_DISPLAYCONTROL | self.LCD_DISPLAYON)
        self._write_command(self.LCD_CLR)
        self._write_command(self.LCD_ENTRYMODESET | self.LCD_ENTRYLEFT | self.LCD_ENTRYSHIFTDECREMENT)
        time.sleep_ms(2)

    def _write_init_nibble(self, nibble):
        self.i2c.writeto(self.i2c_addr, bytes([(nibble << 4) | self.backlight | 0x04]))
        self.i2c.writeto(self.i2c_addr, bytes([(nibble << 4) | self.backlight]))

    def _write_command(self, cmd):
        self._write_byte(cmd, 0)

    def _write_data(self, data):
        self._write_byte(data, 0x01)

    def _write_byte(self, data, mode):
        hi = data & 0xF0
        lo = (data << 4) & 0xF0
        for val in [hi, lo]:
            self.i2c.writeto(self.i2c_addr, bytes([val | mode | self.backlight | 0x04]))
            self.i2c.writeto(self.i2c_addr, bytes([val | mode | self.backlight]))

    def putstr(self, string):
        for char in string:
            self._write_data(ord(char))

    def clear(self):
        self._write_command(self.LCD_CLR)
        time.sleep_ms(2)
    
    def move_to(self, line, col):
        """Move cursor to line (0 or 1) and column (0-15)"""
        if line == 0:
            addr = 0x00 + col
        elif line == 1:
            addr = 0x40 + col
        else:
            return
        self._write_command(0x80 | addr)
# ---- End of class ----
