from utils import Logger, LivePlotter, import_configuration, export_configuration
from nmotion_transport import Actuator, IMU, Driver, NCoder, USBInterface, getCurrentLibraryVersion, CANBaudRate
from device_manager import DeviceManager
import IPython
import gc

CLI_VERSION = "0.1.7-beta"

def __print_banner():
    print(f'NMotion CLI Tool v{CLI_VERSION} ({getCurrentLibraryVersion()})')
    print('Please connect your NMotion Component via NLink device.')
    print('You can also type help() or quit().')

def __on_new_interface_callback(interface_identifier: str, interface: USBInterface) -> None:
    globals()[f'NLink_{interface_identifier}'] = interface
    __logger.notify(f"Connected to NLink device at {interface_identifier}. NLink_{interface_identifier} object created.")

def __on_remove_interface_callback(interface_identifier: str) -> None:
    global __console
    obj_to_delete = f'NLink_{interface_identifier}'
    del globals()[obj_to_delete]
    __console.user_ns.pop(obj_to_delete, None)


def __on_new_device_callback(dev_identifier: str, interface: USBInterface) -> None:
    dev_id, dev_type, stripped_iface_name = dev_identifier.split("::")
    dev_id = int(dev_id)
    dev_type = int(dev_type)

    if(not interface.isDevice()):
        if(dev_type == 1):
            __logger.notify(f"New Device with ID: {dev_id} got connected. Actuator_{dev_id}_{stripped_iface_name} object created.")
            globals()[f'Actuator_{dev_id}_{stripped_iface_name}'] = Actuator(dev_id, interface) 
        elif(dev_type == 2):
            __logger.notify(f"New Device with ID: {dev_id} got connected. Driver_{dev_id}_{stripped_iface_name} object created.")
            globals()[f'Driver_{dev_id}_{stripped_iface_name}'] = Driver(dev_id, interface)     
        elif(dev_type == 3):
            __logger.notify(f"New Device with ID: {dev_id} got connected. IMU_{dev_id}_{stripped_iface_name} object created.")
            globals()[f'IMU_{dev_id}_{stripped_iface_name}'] = IMU(dev_id, interface)     
        elif (dev_type == 4):
            __logger.notify(f"New Device with ID: {dev_id} got connected. NCoder_{dev_id}_{stripped_iface_name} object created.")
            globals()[f'NCoder_{dev_id}_{stripped_iface_name}'] =  NCoder(dev_id, interface) 
        else:
            __logger.warn(f'Unknown Device of type {dev_type} with ID: {dev_id} got connected. No object will be created.')
    else:
        if(dev_type == 2):
            globals()[f'Driver_{dev_id}_{stripped_iface_name}'] = Driver(interface=interface)
            __logger.notify(f"New Device with ID: {dev_id} got connected. Driver_{dev_id}_{stripped_iface_name} object created.")
        elif(dev_type == 3):
            globals()[f'IMU_{dev_id}_{stripped_iface_name}'] = IMU(interface=interface)
            __logger.notify(f"New Device with ID: {dev_id} got connected. IMU_{dev_id}_{stripped_iface_name} object created.")
        elif(dev_type == 4):
            globals()[f'NCoder_{dev_id}_{stripped_iface_name}'] =  NCoder(interface=interface)
            __logger.notify(f"New Device with ID: {dev_id} got connected. NCoder_{dev_id}_{stripped_iface_name} object created.")
        else:
            __logger.warn(f'Unknown Device of type {dev_type} with ID: {dev_id} got connected. No object will be created.')


def __on_remove_device_callback(dev_identifier: str) -> None:
    global __console
    dev_id, dev_type, stripped_iface_name = dev_identifier.split("::")
    dev_id = int(dev_id)
    dev_type = int(dev_type)

    obj_to_delete = None

    if(dev_type == 1):
        obj_to_delete = f'Actuator_{dev_id}_{stripped_iface_name}'
    elif(dev_type == 2):
        obj_to_delete = f'Driver_{dev_id}_{stripped_iface_name}'
    elif(dev_type == 3):
        obj_to_delete = f'IMU_{dev_id}_{stripped_iface_name}'
    elif(dev_type == 4):
        obj_to_delete = f'NCoder_{dev_id}_{stripped_iface_name}'
    
    if(obj_to_delete):
        # Delete object from both globals and userspace
        del globals()[obj_to_delete]
        __console.user_ns.pop(obj_to_delete, None)
        __logger.warn(f"Device with ID: {dev_id} is removed.")
    
    # Delete all objects with 0 reference counts
    gc.collect()

def __after_cell_callback(result):
    cmd = result.info.raw_cell.strip()
    res = result.result

    if ".getErrorCode()" in cmd:
        if isinstance(res, tuple):
            for item in res:
                item_type = type(item)

                if item_type.__name__ == "driver_error_t":
                    if str(item) != "driver_error_t.NONE":
                        __logger.warn("Check out https://docs.nmotion.in/ndrive-z1-config-and-operation#error-handling for troubleshooting the driver errors")


if __name__ == '__main__':
    __print_banner()
    
    # Create required global variables
    __logger = Logger()
    __devManager = DeviceManager(__on_new_device_callback, __on_remove_device_callback, __on_new_interface_callback, __on_remove_interface_callback)
    plotter = LivePlotter()
      
    # Create and Start shell
    __console = IPython.terminal.embed.InteractiveShellEmbed(banner1='')
    __console.events.register('post_run_cell', __after_cell_callback)    
    __console()
    
    # Stop all threads and exit
    __devManager.stopThreads()
    
    