# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries
# SPDX-FileCopyrightText: Copyright (c) Waveshare
# SPDX-FileCopyrightText: Copyright (c) 2022 Neradoc
#
# SPDX-License-Identifier: MIT
#
# In order to keep basic compatibility with the orifinal library, ignore "bad" names.
# pylint: disable=invalid-name
"""
`waveshare_lcd1602`
================================================================================

Drive for Waveshare's I2C character display LCD1602.
A port of Waveshare's RGB1602 Micropython library.

* Author(s): Neradoc

Implementation Notes
--------------------

**Hardware:**

* `Waveshare LCD1602 RGB Module <https://www.waveshare.com/wiki/LCD1602_RGB_Module>`_

**Software and Dependencies:**

* Adafruit CircuitPython firmware for the supported boards:
  https://circuitpython.org/downloads
* Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice
"""

import time
from adafruit_bus_device.i2c_device import I2CDevice

try:
    from typing import Union
    import busio
except ImportError:
    pass

__version__ = "1.0.0"
__repo__ = "https://github.com/Neradoc/Circuitpython_Waveshare_LCD1602.git"

# Device I2C Arress
LCD_ADDRESS = 0x7C >> 1
RGB_ADDRESS = 0xC0 >> 1

# color define

REG_RED = 0x04
REG_GREEN = 0x03
REG_BLUE = 0x02
REG_MODE1 = 0x00
REG_MODE2 = 0x01
REG_OUTPUT = 0x08
LCD_CLEARDISPLAY = 0x01
LCD_RETURNHOME = 0x02
LCD_ENTRYMODESET = 0x04
LCD_DISPLAYCONTROL = 0x08
LCD_CURSORSHIFT = 0x10
LCD_FUNCTIONSET = 0x20
LCD_SETCGRAMADDR = 0x40
LCD_SETDDRAMADDR = 0x80

# flags for display entry mode
LCD_ENTRYRIGHT = 0x00
LCD_ENTRYLEFT = 0x02
LCD_ENTRYSHIFTINCREMENT = 0x01
LCD_ENTRYSHIFTDECREMENT = 0x00

# flags for display on/off control
LCD_DISPLAYON = 0x04
LCD_DISPLAYOFF = 0x00
LCD_CURSORON = 0x02
LCD_CURSOROFF = 0x00
LCD_BLINKON = 0x01
LCD_BLINKOFF = 0x00

# flags for display/cursor shift
LCD_DISPLAYMOVE = 0x08
LCD_CURSORMOVE = 0x00
LCD_MOVERIGHT = 0x04
LCD_MOVELEFT = 0x00

# flags for function set
LCD_8BITMODE = 0x10
LCD_4BITMODE = 0x00
LCD_2LINE = 0x08
LCD_1LINE = 0x00
LCD_5X8DOTS = 0x00


class LCD1602:
    """
    Setup a new RGB LCD1602 display

    :param busio.I2C i2c: I2C bus object to use.
    :param int columns: The number of columns on the display.
    :param int rows: The number of rows on the display.
    """

    def __init__(self, i2c: busio.I2C, columns: int, rows: int):
        self.lcd_device = I2CDevice(i2c, LCD_ADDRESS)
        self.rgb_device = I2CDevice(i2c, RGB_ADDRESS)
        self._row = rows
        self._col = columns

        self._showfunction = LCD_4BITMODE | LCD_1LINE | LCD_5X8DOTS
        self.begin(self._row, self._col)

    def command(self, cmd: int):
        """
        Send a command.

        :param int cmd: The command code.
        """
        with self.lcd_device:
            self.lcd_device.write(bytes([0x80]) + chr(cmd))

    def write(self, data: int):
        """
        Write a data byte.

        :param int data: The data byte to write.
        """
        with self.lcd_device:
            self.lcd_device.write(bytes([0x40]) + chr(data))

    def setReg(self, reg, data):
        """
        Set the value of a register.

        :param int reg: The register to write to.
        :param int data: The data byte to write.
        """
        with self.rgb_device:
            self.rgb_device.write(bytes([reg]) + chr(data))

    def setRGB(self, r, g, b):
        """
        Set the color of the RGB backlight.

        :param int r: Red byte.
        :param int g: Green byte.
        :param int b: Blue byte.
        """
        self.setReg(REG_RED, r)
        self.setReg(REG_GREEN, g)
        self.setReg(REG_BLUE, b)

    def setCursor(self, col, row):
        """
        Place the cursor on the display.

        :param int col: The column.
        :param int row: The row.
        """
        if row == 0:
            col |= 0x80
        else:
            col |= 0xC0
        with self.lcd_device:
            self.lcd_device.write(bytearray([0x80, col]))

    def clear(self):
        """Clear the display."""
        self.command(LCD_CLEARDISPLAY)
        time.sleep(0.002)

    def printout(self, arg: Union[str, int]):
        """
        Print a string to the display. Ints are converted to strings.

        :param str|int arg: The string or int to printout.
        """
        if isinstance(arg, int):
            arg = str(arg)

        for x in bytearray(arg, "utf-8"):
            self.write(x)

    def display(self):
        """Turn on the display."""
        self._showcontrol |= LCD_DISPLAYON
        self.command(LCD_DISPLAYCONTROL | self._showcontrol)

    def begin(self, columns: int, rows: int):  # pylint: disable=unused-argument
        """
        Initial configuration, called from init.

        :param int columns: The number of columns on the display.
        :param int rows: The number of rows on the display.
        """
        if rows > 1:
            self._showfunction |= LCD_2LINE

        self._numlines = rows
        self._currline = 0

        time.sleep(0.05)

        # Send function set command sequence
        self.command(LCD_FUNCTIONSET | self._showfunction)
        # delayMicroseconds(4500);  # wait more than 4.1ms
        time.sleep(0.005)
        # second try
        self.command(LCD_FUNCTIONSET | self._showfunction)
        # delayMicroseconds(150);
        time.sleep(0.005)
        # third go
        self.command(LCD_FUNCTIONSET | self._showfunction)
        # finally, set # lines, font size, etc.
        self.command(LCD_FUNCTIONSET | self._showfunction)
        # turn the display on with no cursor or blinking default
        self._showcontrol = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF
        self.display()
        # clear it off
        self.clear()
        # Initialize to default text direction (for romance languages)
        self._showmode = LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT
        # set the entry mode
        self.command(LCD_ENTRYMODESET | self._showmode)
        # backlight init
        self.setReg(REG_MODE1, 0)
        # set LEDs controllable by both PWM and GRPPWM registers
        self.setReg(REG_OUTPUT, 0xFF)
        # set MODE2 values
        # 0010 0000 -> 0x20  (DMBLNK to 1, ie blinky mode)
        self.setReg(REG_MODE2, 0x20)
        self.setColorWhite()

    def setColorWhite(self):
        """Set the color of the RGB backlight to white."""
        self.setRGB(255, 255, 255)
