"""
uv-belichter-software - Some utilities for better customization and modularization
Copyright (C) 2024  Benjamin Burkhardt

This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program.  If not, see <https://www.gnu.org/licenses/>.
"""

from gc import collect

"""
A small wrapper class as storing machine.Pin, LCD and machine.I2C objects in a file is not that cool :)
Now only pin numbers and strings etc. are stored on the uC, and the complex objects are generated on the fly
"""
class Config:
    """
    The initializer method
    - config_file: the path to the config file laying on the uC
    """
    def __init__(self, config_file: str = "config.json"):
        self._attr_list = ["LOG_LEVEL",                 # there are three log levels: warn (0), info (1), debug (2)
                                                        # this value defines which log messages to show
                                                        # e.g. 2 means show [debug], [warn] and [info] messages
                           "STARTUP_WELCOME_SHOW",      # show the name and a startup message
                           "STARTUP_PROJECT_NAME",      # the name to show at startup
                           "STARTUP_MESSAGE_STARTING",  # the message to show at startup
                           "STARTUP_MESSAGE_FINISHED",  # the message to show at startup
                           "STARTUP_WELCOME_CYCLES",    # how often shall "Starting..." run over the screen
                           "PIN_IN_BTN_1",              # input of the first btn
                           "PIN_IN_BTN_2",              # input of the second btn
                           "PIN_IN_SWITCH",             # input of the switch
                           "PIN_OUT_RELAIS",            # where the relais is connected (for the UV lights)
                           "PIN_SDA",                   # just some standard I2C serial data (SDA) output
                           "PIN_SCL",                   # just some standard I2C serial clock (SCL) output
                           "LCD_I2C_CH",                # where the relais is connected (for the UV lights)
                           "LCD_I2C_ADDR",              # the i2c adress of the display
                           "LCD_I2C_NUM_ROWS",          # how many rows for character display has the display?
                           "LCD_I2C_NUM_COLS",          # and how many characters can it display per row?
                           "LCD",                       # the actual lcd object (of the PCF8574T I2C_LCD class, see libraries)
                           "TIMER_1_DURATION",          # the duration of the first timer in seconds
                           "TIMER_2_DURATION",          # the duration of the second first timer in seconds
                           "TIMER_3_DURATION"]          # the duration of the third timer in seconds
        self._config_file = config_file
        self.load_config()
    
    def load_config(self):
        # prepare the class
        with open(self._config_file, "r") as f:
            from json import load
            self._config = load(f)
            del load
            collect()
    
    def save_config(self):
        with open(self._config_file, "w") as f:
            from json import dump
            dump(self._config, f, separators=(',\n', ': '))
            del dump
            collect()
    
    
    def __getattr__(self, name):
        if name.startswith("_"):  # make private attributes unaccessible
            raise AttributeError(f"'Access to the private attribute '{name}' of the object '{self.__class__.__name__}' is forbidden")
        elif name in self._attr_list:  # valid attributes (only capital letters and -_ etc. are allowed)
            try:
                # now some if statements to check if the lcd or some pin object is asked
                if name.startswith("PIN_"):
                    from machine import Pin
                    if name.startswith("PIN_IN"):
                        if self._config[name]["pull"].lower() == "down":
                            p = Pin(self._config[name]["pin"], Pin.IN, Pin.PULL_DOWN)
                        elif self._config[name]["pull"].lower() == "up":
                            p = Pin(self._config[name]["pin"], Pin.IN, Pin.PULL_UP)
                        else:
                            p = Pin(self._config[name]["pin"], Pin.IN)
                    elif name.startswith("PIN_OUT"):
                        p = Pin(self._config[name], Pin.OUT)
                    else:
                        p = Pin(self._config[name])
                    del Pin
                    collect()
                    return p
                elif name == "LCD":
                    try:
                        return self._lcd
                    except:
                        from machine import I2C, Pin
                        from PCF8574T import I2C_LCD
                        self._lcd = I2C_LCD(I2C(self.LCD_I2C_CH, sda=self.PIN_SDA, scl=self.PIN_SCL, freq=400000),
                                            self.LCD_I2C_ADDR,
                                            self.LCD_I2C_NUM_ROWS,
                                            self.LCD_I2C_NUM_COLS)
                        del I2C, Pin, I2C_LCD
                        collect()
                        return self._lcd
                return self._config[name]
            except KeyError:
                raise AttributeError(f"Attribute '{name}' does not exist in the config file '{self._config_file}'")
        else:
            raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'")
    
    
    def __setattr__(self, name, value):
        if name.startswith("_"):  # make private attributes settable as normal
            object.__setattr__(self, name, value)
        elif name in self._attr_list:  # valid attributes (only capital letters and -_ etc. are allowed)
            try:
                self._config[name] = value
                self.save_config()
            except KeyError:
                raise AttributeError(f"Attribute '{name}' does not exist in the config file '{self._config_file}'")
        else:
            raise AttributeError(f"Can't set attribute '{name}' for a '{self.__class__.__name__}' object: forbidden")


    def __delattr__(self, name):
        raise AttributeError(f"You may not delete any attribute of the '{self.__class__.__name__}' object")


"""
Very simple logging function
Overall log level can be specified in config.py
"""
def log(log_level: int, message: str):
    log_mapping = {0: "WARN", 1: "INFO", 2: "DEBUG"}
    log_level = int(log_level)  # make sure log_level is an integer
    if log_level not in [0, 1, 2]:
        print(f"[LOGGER] Got a message of unknown log level ({log_level}). Original message is printed below.")
        print(f"{message}")
    elif Config().LOG_LEVEL >= log_level:  # if log level is valid
        print(f"[{log_mapping[log_level]}] {message}")