# Copyright 2025 PostalPortal LLC and Netsyms Technologies 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
from ssd1306big import setOLED, line1, line2
import framebuf
import math
from config import *

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(firmwareVersion):
    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, 16, 0)
            # Show info
            oled.text("     Parcel", 0, 40, 1)
            oled.text("  Dimensioner", 0, 48, 1)
            firmVerStr = "FW Ver " + firmwareVersion
            oled.text(f"{firmVerStr:^16}", 0, 56, 1)
            oled.show()
            sleep_ms(1500)
        except:
            pass

def errorDisplay(code, message = ""):
    global oled
    if ENABLE_DISPLAY:
        try:
            setOLED(oled)
            oled.fill(0)
            if code == 1:
                line1(" ERROR! ")
                oled.text(message, 0, 22, 2)
                if message == "Sensor timeout":
                    pass
            elif code == 3:
                line1("UNSTABLE")
                oled.text("Distance is not", 0, 22, 2)
                oled.text("stable. Waiting", 0, 30, 2)
                oled.text("for it to settle", 0, 38, 2)
                oled.text("down and stop", 0, 46, 2)
                oled.text("moving around.", 0, 54, 2)
            elif code == 5:
                line1("UNDER 0!")
                oled.text("Object farther", 0, 22, 2)
                oled.text("from platform", 0, 30, 2)
                oled.text("than it should", 0, 38, 2)
                oled.text("be. Check and", 0, 46, 2)
                oled.text("re-zero.", 0, 54, 2)
            elif code == 6:
                line1("TOO FAR!")
                oled.text("Distance is too", 0, 22, 2)
                oled.text("far from sensor", 0, 30, 2)
                oled.text("for accurate", 0, 38, 2)
                oled.text("measurement.", 0, 46, 2)
            elif code == 7:
                line1("TOO NEAR")
                oled.text("Object is too", 0, 22, 2)
                oled.text("close to sensor", 0, 30, 2)
                oled.text("for accurate", 0, 38, 2)
                oled.text("measurement.", 0, 46, 2)
            elif code == 8:
                line1("HOST ERR")
                oled.text("Connected PC", 0, 22, 2)
                oled.text("sent an invalid", 0, 30, 2)
                oled.text("command.", 0, 38, 2)
            elif code == 9:
                line1("BAD UNIT")
                oled.text("Measurement too", 0, 22, 2)
                oled.text("large to display", 0, 30, 2)
                oled.text("with current", 0, 38, 2)
                oled.text("unit setting.", 0, 46, 2)
                oled.text("Change units.", 0, 54, 2)
            elif code == 0x4D:
                line1("  REPL  ")
                oled.text("Device is in", 0, 22, 2)
                oled.text("debugging mode.", 0, 30, 2)
                oled.text("Power off and", 0, 38, 2)
                oled.text("back on to", 0, 46, 2)
                oled.text("cancel.", 0, 54, 2)
            elif code == 0x50:
                line1(" UPDATE ")
                oled.text("Device is in", 0, 22, 2)
                oled.text("firmware update", 0, 30, 2)
                oled.text("mode. Power off", 0, 38, 2)
                oled.text("and back on to", 0, 46, 2)
                oled.text("cancel.", 0, 54, 2)
            oled.show()
        except:
            pass

def updateDisplay(measurement, units, tempC, unstable = False):
    global oled
    if ENABLE_DISPLAY:
        try:
            setOLED(oled)
            numberLine = " "
            if unstable:
                if units == "mm":
                    wholeNumber = "-------"
                    decimalPart = ""
                elif units == "cm":
                    wholeNumber = "-----"
                    decimalPart = "-"
                else:
                    wholeNumber = "----"
                    decimalPart = "--"
            else:
                wholeNumber = str(math.floor(abs(measurement)))
                decimalPart = int(abs(measurement % 1) * 100)
                if measurement < 0:
                    numberLine = "-"
                    if decimalPart > 0:
                        decimalPart = 100 - decimalPart # Fix a bug where -.75 displays as -.25
            
            if units == "mm":
                numberLine = numberLine + f"{wholeNumber:>7}"
            elif units == "cm":
                numberLine = numberLine + f"{wholeNumber:>5}.{decimalPart:0<1}"
            elif units == "in":
                numberLine = numberLine + f"{wholeNumber:>4}.{decimalPart:0<2}"
            elif units == "ft":
                numberLine = numberLine + f"{wholeNumber:>4}.{decimalPart:0<2}"
            
            oled.fill(0) # Fill with black to clear screen
            line1(numberLine[:8]) # Write measurement to screen (but only first 8 chars to prevent IndexError: list index out of range)
            oled.text(f"{units:^16}", 0, 22, 1) # Write units centered below measurement
            if measurement == 0: # Write zero indicator on right below measurement
                oled.text("            ZERO", 0, 22, 1)
            tempStr = str(tempC) + "C"
            oled.text(f"{tempStr:>16}", 0, 50, 1) # Write temperature in bottom-right corner
            if ENABLE_BUTTON_LEGEND:
                # Draw a vertical, double-ended arrow for the two physical buttons
                oled.line(5, 24, 5, 40, 1) # Vertical line for top arrow
                oled.line(5, 46, 5, 62, 1) # Vertical line for top arrow
                oled.line(0, 28, 5, 24, 1) # Left-top arrow part
                oled.line(10, 28, 5, 24, 1) # Right-top arrow part
                oled.line(0, 58, 5, 62, 1) # Left-bottom arrow part
                oled.line(10, 58, 5, 62, 1) # Right-bottom arrow part
                oled.text("SET 0", 10, 30, 1)
                oled.text("UNITS", 10, 50, 1)
            oled.show()
        except:
            pass
        
def clearDisplay():
    try:
        oled.fill(0)
        oled.show()
    except:
        pass