"""
====================== (C) COPYRIGHT 2025 OPTIX ===============================
 File Name          : ctdcalib.py
 Authors            : OPTIX Department "Thermovision"
                    : Ioan Gochev (i.gochev@optixco.com)
 Date               : 2025-03-13
 Description        : Tool for calibrating cooled cameras, using black bodies,
                        climate chamber and motor rail.

===============================================================================

 Copyright (C) 2025 OPTIX JSC - All Rights Reserved
 Unauthorized copying of this library, via any medium is strictly prohibited
 Proprietary and confidential
 
===============================================================================
 Depends on:
 python3 version >= 3.10

==============================================================================
 Summary:
 The purpose of this tool is to provide the user with the ability to execute
 recipes used to calibrate cameras that have cooled thermal sensors.
===============================================================================
 """
import argparse
import os
from functools import partial
import subprocess
import sys
import traceback

from modules.daemon import Daemon
from modules.builtins import *
from modules.bidentifier_mwir import BidentifierMwir
from modules.motor_rail import MotorRail
from modules.blackbody import BlackBody
from modules.climate_chamber import ClimateChamber

DAEMON_PATH = "WorkerDaemon.exe"
APP_VERSION = "1.0.7"

def run_recipe(recipe_path: str, folder_path:str):
    # Read the recipe file
    try:
        with open(recipe_path, "r") as f:
            recipe_code = f.read()
    except FileNotFoundError:
        print("File % not found." % recipe_path)
        exit(1)
    
    if not os.path.isdir(folder_path):
        print("Folder % not found." % folder_path)
        exit(1)

    log_file = folder_path + "\log.txt"
    
    # Run the daemon program
    try:
        daemon_log_file = folder_path + "\daemon-log.txt"
        daemon = Daemon(DAEMON_PATH, daemon_log_file)
        daemon.start()
    except subprocess.SubprocessError:
        print("Failed to start the daemon backend!")
        exit(1)

    # Prepare a globals dictionary with the API
    recipe_globals = {
        "daemon": daemon, 
        "__builtins__": __builtins__,
        "LOG": partial(log, log_file),
        "SLEEP": partial(sleep, log_file),
        "BidentifierMwir": partial(BidentifierMwir, daemon),
        "MotorRail": partial(MotorRail, daemon),
        "BlackBody": partial(BlackBody, daemon),
        "ClimateChamber": partial(ClimateChamber, daemon),
        "SESSION_DIR": folder_path
    }

    # Execute the recipe with access to the API
    try:
        exec(recipe_code, recipe_globals)
    except KeyboardInterrupt:
        log(log_file,"\nUser interrupted recipe execution!")
        log(log_file,"Exiting prematurely..")
    except Exception as e:
        tb = traceback.extract_tb(e.__traceback__)
        for frame in reversed(tb):
            if frame.filename == "<string>":  # Topmost exec() code
                log(log_file, f"Error on line {frame.lineno}: {e}")
                break

        #err_msg =  f"{type(e).__name__}: {e}"
        #log(log_file, err_msg)
        #log(log_file, traceback.format_exc())

    # close all devices
    try:
        daemon.send_command("close_all", [], 180)
    except Exception as e:
        tb = traceback.extract_tb(e.__traceback__)
        log("Failed to properly close daemon! Make sure manually that all heater/cooler/moving devices are stopped.")
        log(log_file, traceback.format_exc())

    daemon.kill()

if __name__ == "__main__":
    # parse the input params
    parser = argparse.ArgumentParser(
        description="Tool for calibrating cooled cameras"
    )

    parser.add_argument(
        "-V", "--version", action="version", version=f"{sys.argv[0]} {APP_VERSION}"
    )

    parser.add_argument("recipe_path", help="path to a recipe file to execute")
    parser.add_argument("out_folder", help="folder, where to store execution artefacts")
    args = parser.parse_args()

    # prepare and run the recipe
    run_recipe(args.recipe_path, args.out_folder)
