"""
Name: sign_firmloader
Title: Sign Firmloader
Author: Cooper
Date: 04/06/2024

Desc:  This is the module for any firmloader related tasks.  It's primary role is to update sign firmware, but it is
capable of loading other things:

Command:
  load [app|lib|config|sn|boot] [crc] file
                    load typed file into region (default app)



"""
import os
import shutil
import logging
import subprocess

class SignFirmloader(object):
    def __init__(self, hw_dict):
        self.hw_dict = hw_dict

        self.firmloader_path = "/usr/bin/firmloader-signs"
        self.backup_dir = "/usr/share/sign_firmware"
        self.flag_path = os.path.join(self.backup_dir, "firmware.install")

    """
    ###################################################################################################################
    Firmloader call
    """

    def call_firmloader(self, file_type: str, file_path: str):
        """
        This calls firmware with a given file type to load.


        """
        supported_filetypes = ["app", "lib", "config", "sn", "boot", "config-ini", "font"]

        if file_type not in supported_filetypes:
            return 1, "Invalid load type"

        firmloader_commands = [
            self.firmloader_path,
            "-C/dev/ttyS1",
            "-B38400",
            "-S%s" % self.hw_dict["address"],
            "load",
            file_type,
            file_path
        ]

        logging.info("SFL: Installing %s: %s" % (file_type, file_path))
        logging.info("SFL: Calling firmloader please wait.")

        process = subprocess.Popen(firmloader_commands, stdout=subprocess.PIPE)
        output, error = process.communicate()

        output_result = output.decode("latin-1")
        returncode = process.returncode

        return returncode, output_result

    """
    ###################################################################################################################
    Sign firmare update
    """

    def check_update_required(self):
        """
        This looks for the update required flag which lives in /usr/share/sign_firmware.

        Then calls the routine to carry out the update process.
        """
        if os.path.isfile(self.flag_path):
            logging.info("SFL: Sign firmware needs processing")

            firmware_path = os.path.join(self.backup_dir, "signfirmware.bin")
            status = self.update_firmware(firmware_path, backup= False)      #Use the path in the backed up file
            return status

        else:
            logging.info("SFL: No sign firmware update to process")
            return "NONE"

    def update_firmware(self, file_path, backup=True):
        """
        Call and process and then looks at the subsequent result.
        """
        if backup:
            if self.backup_all_things():
                return "CANT"

        #Create a flag just incase
        self.create_update_flag()

        status, result = self.call_firmloader("app", file_path)

        if status == 0:
            logging.info("SFL: Sign firmware update complete")
            self.delete_update_flag()
            self.delete_backup()
            return "SUCCESS"
        else:
            logging.warning("SFL: Sign firmware update FAILED")
            if "No connection." in result or "No response." in result:
                logging.warning("Cannot connect to sign")
                return "FAIL"
            elif "Failed." in result:
                logging.warning("Failed to erase/load firmware")
                return "FAIL"

    def create_update_flag(self):
        """
        Creates a flag so that the application knows that there is a sign update to apply on bootup
        """
        with open(self.flag_path, "w") as flag:
            logging.info("SFL: Update flag created")

    def delete_update_flag(self):
        """
        Deletes the update flag so that an update isn't attempted at the next app restart
        """
        try:
            os.remove(self.flag_path)
        except OSError:
            pass

        logging.info("SFL: Update trigger removed")


    def backup_all_things(self):
        """
        In order to be able to retry this process in the event of a failed load we need to save the firmware and some
        details about the sign.
        Although I have never seen bootloader fail before, especially not in a direct context,
        so the only way I can see issues is when power is lost or someone stops this process at the wrong time.

        Copies the existing hw_details.json and sign firmware into non volatile storage before conducting an update if all
        goes pear shaped.  As sign firmware is very rarely updated we can afford to write this to storage.
        """
        try:
            os.mkdir(self.backup_dir)
        except OSError:
            pass

        try:
            shutil.copy("/tmp/signfirmware.bin", self.backup_dir)
        except (OSError, IOError):
            logging.warning("SFL: Cannot backup sign firmware")
            return 1

        try:
            shutil.copy("/tmp/hw_details.json", self.backup_dir)
        except (OSError, IOError):
            logging.warning("SFL: Cannot backup sign firmware")
            return 1

        return 0

    def delete_backup(self):
        """
        Deletes the backup files as they probably aren't needed anymore?
        """
        try:
            shutil.rmtree(self.backup_dir, ignore_errors=True)
        except Exception:
            #Not all too bothered if this fails
            pass

if __name__ == "__main__":
    pass
