# Copyright 2026 PostalPortal LLC
#
# Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that
# the following conditions are met:
#   1. Redistributions of source code must retain
#      the above copyright notice, this list of conditions
#      and the following disclaimer.
#   2. Redistributions in binary form must reproduce
#      the above copyright notice, this list of conditions
#      and the following disclaimer in the documentation
#      and/or other materials provided with the distribution.
#   3. Neither the name of the copyright holder
#       nor the names of its contributors may be used
#       to endorse or promote products derived from
#       this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
# CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES,
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
# WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
# THE POSSIBILITY OF SUCH DAMAGE.
#
# This code displays device info on an attached screen.
# Requires the ssd1306 module/package installed.
# Connect to GPIO 0 and 1 for i2c
#

from machine import Pin, I2C
from ssd1306 import SSD1306_I2C
from time import sleep_ms
import framebuf
import math
from config import *
from scanmode import getCurrentModeStr, getModes, modeListIsSet, getPrevModeStr, getNextModeStr

i2c = None
oled = None
if ENABLE_DISPLAY:
    i2c = I2C(DISPLAY_I2C_CONTROLLER, sda=Pin(DISPLAY_SDA_PIN), scl=Pin(DISPLAY_SCL_PIN), freq=400000)
    oled = SSD1306_I2C(DISPLAY_WIDTH, DISPLAY_HEIGHT, i2c)

    
def bootDisplay():
    if ENABLE_DISPLAY:
        try:
            # Bootup dead pixel self-test
            oled.fill(0)
            oled.show()
            oled.fill(1)
            oled.show()
            sleep_ms(500)
            oled.fill(0)
            oled.show()
            # 128x32 PostalPoint logo bitmap
            logo = bytearray([
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0xc0, 0x40, 0x40, 0xc0, 0x80, 
    0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
    0x00, 0x00, 0x00, 0xfc, 0x0e, 0x0a, 0x1a, 0x13, 0xf1, 0x19, 0x08, 0x08, 0xfc, 0xfc, 0x0c, 0x08, 
    0x19, 0xf1, 0xf1, 0x1b, 0x0a, 0x0e, 0xfc, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0xf8, 0xf8, 
    0x1c, 0x1c, 0x1c, 0xf8, 0xf8, 0xf0, 0x00, 0x00, 0x80, 0xc0, 0xe0, 0xe0, 0xe0, 0xc0, 0xc0, 0x00, 
    0x00, 0x00, 0xc0, 0xe0, 0xe0, 0xe0, 0xe0, 0xc0, 0x00, 0x00, 0x80, 0xf0, 0xf8, 0xf8, 0xe0, 0xe0, 
    0x00, 0x00, 0x80, 0xc0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0x40, 0x00, 0x00, 0xf0, 0xfc, 0xfe, 0x0e, 
    0x00, 0x00, 0xf0, 0xf8, 0xf8, 0x1c, 0x1c, 0x18, 0xf8, 0xf8, 0xf0, 0x00, 0x00, 0x80, 0xc0, 0xe0, 
    0xe0, 0xe0, 0xc0, 0x80, 0x00, 0x00, 0x00, 0xc8, 0xfc, 0xfe, 0x0c, 0x00, 0xc0, 0xe0, 0xe0, 0xe0, 
    0xe0, 0xe0, 0xc0, 0x00, 0x00, 0x00, 0xf0, 0xf8, 0xf8, 0xe0, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 
    0x00, 0x00, 0x00, 0x7f, 0xc0, 0x80, 0x80, 0x80, 0x07, 0x0e, 0x0a, 0x1a, 0xf1, 0xf1, 0x1b, 0x0a, 
    0x0e, 0x0f, 0x07, 0x80, 0x80, 0xc0, 0x7f, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x7f, 0x3f, 0x07, 
    0x07, 0x07, 0x03, 0x03, 0x01, 0x00, 0x00, 0x1f, 0x3f, 0x7f, 0x70, 0x70, 0x38, 0x1f, 0x0f, 0x03, 
    0x20, 0x71, 0x73, 0x77, 0x76, 0x3e, 0x3c, 0x08, 0x00, 0x18, 0x3f, 0x7f, 0x73, 0x70, 0x30, 0x00, 
    0x0c, 0x3f, 0x7f, 0x73, 0x70, 0x30, 0x3f, 0x7f, 0x3f, 0x00, 0x00, 0x3f, 0x7f, 0x7f, 0x20, 0x00, 
    0x70, 0x7f, 0x7f, 0x0f, 0x07, 0x07, 0x07, 0x03, 0x03, 0x01, 0x00, 0x0c, 0x3f, 0x3f, 0x73, 0x70, 
    0x70, 0x3f, 0x1f, 0x0f, 0x00, 0x70, 0x7f, 0x3f, 0x0f, 0x00, 0x20, 0x7e, 0x7f, 0x1f, 0x01, 0x20, 
    0x7f, 0x7f, 0x0f, 0x00, 0x00, 0x3f, 0x7f, 0x7f, 0x70, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x02, 0x06, 0x07, 0x07, 0x06, 0x02, 
    0x02, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
            fbuf = framebuf.FrameBuffer(logo, 128, 32, framebuf.MONO_VLSB)
            oled.blit(fbuf, 0, 0, 0)
            # Show info
            oled.text("Barcode Scanner", 0, 36, 1)
            firmVerStr = "FW Ver " + FIRMWARE_VERSION
            oled.text(f"{firmVerStr:^16}", 0, 56, 1)
            oled.show()
            sleep_ms(1500)
            mainDisplay()
        except:
            pass

# Center a line of text on the screen (vertical and horizontal)
# If arrows, display up and down arrows above / below the text.
# If showNow, clears the display before drawing, and displays the text as well. Otherwise this function only renders the display.
# If arrows, the aboveLine and belowLine text will be rendered, centered horizontally, above/below the arrows.
def centerText(text, arrows=False, showNow = False, aboveLine = "", belowLine = ""):
    if ENABLE_DISPLAY:
        if showNow:
            oled.fill(0)
        textWidth = len(text) * CHAR_WIDTH
        vCenter = DISPLAY_HEIGHT // 2
        textY = ((DISPLAY_HEIGHT - CHAR_HEIGHT) // 2)
        oled.text(text, (DISPLAY_WIDTH - textWidth) // 2, textY, 1)
        if arrows:
            arrowLineXDistance = CHAR_WIDTH // 2
            arrowLineYDistance = CHAR_HEIGHT // 2
            topArrowPoint = vCenter - CHAR_HEIGHT - arrowLineYDistance
            bottomArrowPoint = vCenter + CHAR_HEIGHT + arrowLineYDistance
            xArrowCenterPoint = DISPLAY_WIDTH // 2
            topLineY = vCenter - CHAR_HEIGHT - (arrowLineYDistance // 2)
            bottomLineY = vCenter + CHAR_HEIGHT + (arrowLineYDistance // 2)
            oled.line(xArrowCenterPoint - arrowLineXDistance, topArrowPoint + arrowLineYDistance, xArrowCenterPoint, topArrowPoint, 1) # Left-top arrow part
            oled.line(xArrowCenterPoint + arrowLineXDistance, topArrowPoint + arrowLineYDistance, xArrowCenterPoint, topArrowPoint, 1) # Right-top arrow part
            oled.line(xArrowCenterPoint - arrowLineXDistance, bottomArrowPoint - arrowLineYDistance, xArrowCenterPoint, bottomArrowPoint, 1) # Left-bottom arrow part
            oled.line(xArrowCenterPoint + arrowLineXDistance, bottomArrowPoint - arrowLineYDistance, xArrowCenterPoint, bottomArrowPoint, 1) # Right-bottom arrow part
            # Horizontal lines above/below center text, with a gap in the middle for the arrows
            oled.line(0, topLineY, xArrowCenterPoint - (arrowLineXDistance * 2), topLineY, 1)
            oled.line(xArrowCenterPoint + (arrowLineXDistance * 2), topLineY, DISPLAY_WIDTH, topLineY, 1)
            oled.line(0, bottomLineY, xArrowCenterPoint - (arrowLineXDistance * 2), bottomLineY, 1)
            oled.line(xArrowCenterPoint + (arrowLineXDistance * 2), bottomLineY, DISPLAY_WIDTH, bottomLineY, 1)
            # Top and bottom text lines, outside the arrows and lines
            oled.text(aboveLine, (DISPLAY_WIDTH - 1 - (len(aboveLine) * CHAR_WIDTH)) // 2, 6, 1)
            oled.text(belowLine, (DISPLAY_WIDTH - 1 - (len(belowLine) * CHAR_WIDTH)) // 2, DISPLAY_HEIGHT - CHAR_HEIGHT - 6, 1)
        if showNow:
            oled.show()


def mainDisplay(scanInProgress = False):
    if ENABLE_DISPLAY:
        try:
            oled.fill(0)
            if modeListIsSet():
                if modeListIsSet() and len(getModes()) > 1:
                    centerText(getCurrentModeStr(), True, False, getPrevModeStr(), getNextModeStr())
                else:
                    centerText(getCurrentModeStr(), False, False)
            if scanInProgress:
                # Outline around edge of display
                oled.line(0, 0, DISPLAY_WIDTH - 1, 0, 1)
                oled.line(0, 0, 0, DISPLAY_HEIGHT - 1, 1)
                oled.line(DISPLAY_WIDTH - 1, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1, 1)
                oled.line(0, DISPLAY_HEIGHT - 1, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1, 1)
            oled.show()
        except:
            pass

def iconDisplay(icon):
    # TODO: add code to render icons
    if icon == "ERR":
        pass
    elif icon == "OK":
        pass
    elif icon == "NAF":
        pass
    elif icon == "POW":
        bootDisplay()

def clearDisplay(showMain = False):
    if ENABLE_DISPLAY:
        try:
            oled.fill(0)
            oled.show()
            if showMain:
                mainDisplay()
        except:
            pass
    
def brightDisplay():
    if ENABLE_DISPLAY:
        try:
            oled.fill(1)
            oled.show()
        except:
            pass


