class Application(metaclass=Singleton):

    def __init__(self):
        # Create the mainWindow
        self.mainWindow = MainWindow()
        # Create a layout 'reference' and set to None
        self.layout = None
        # Create an empty configuration
        self.app_conf = {}

        # Initialize modules
        failed = modules.init_modules()
        for err in failed:
            msg = 'Module "' + err[0] + '" loading failed'
            QDetailedMessageBox.dcritical('Module error', msg, str(err[1]))

        # Connect mainWindow actions
        self.mainWindow.new_session.connect(self._startup)
        self.mainWindow.save_session.connect(self._save_to_file)
        self.mainWindow.open_session.connect(self._load_from_file)

        # Register general settings widget
        AppSettings.register_settings_widget(General)

        # Show the mainWindow maximized
        self.mainWindow.showMaximized()

    def start(self, filepath=''):
        if exists(filepath):
            # Load the file
            self.mainWindow.file = filepath
            self._load_from_file(filepath)
        else:
            # Create the layout
            self._startup(first=True)

    def finalize(self):
        self.layout.destroy_layout()

        # Terminate the loaded modules
        modules.terminate_modules()

    def _create_layout(self, layout):
        '''
            Clear ActionHandler session;
            Reset plugins;
            Creates a new layout;
            Init plugins.
        '''

        ActionsHandler().clear()
        plugins.reset_plugins()

        if self.layout is not None:
            self.layout.destroy_layout()
            self.layout = None

        try:
            self.layout = layout(self)
            self.mainWindow.set_layout(self.layout)
            self.app_conf['layout'] = layout.NAME
            self._init_plugins()
        except Exception:
            QMessageBox.critical(None, 'Error', 'Layout init failed')
            print(traceback.format_exc(), file=sys.stderr)

    def _layout_dialog(self):
        ''' Show the layout-selection dialog '''
        try:
            select = LayoutSelect()
            select.exec_()
        except Exception as e:
            QMessageBox.critical(None, 'Fatal error', str(e))
            qApp.quit()
            exit(-1)

        if select.result() != QDialog.Accepted:
            qApp.quit()
            exit()

        if exists(select.filepath):
            self._load_from_file(select.filepath)
        else:
            self._create_layout(select.slected())

    def _startup(self, first=False):
        ''' Initializes the basic components '''
        self.mainWindow.file = ''
        self.app_conf = {}

        if first and cfg.config['Layout']['Default'].lower() != 'nodefault':
            layout = layouts.get_layout(cfg.config['Layout']['Default'])
            self._create_layout(layout)
        else:
            self._layout_dialog()

    def _save_to_file(self, filepath):
        ''' Save the current program into "filepath" '''

        # Empty structure
        program = {"cues": [], "plugins": {}, "application": []}

        # Add the cues
        for cue in self.layout.get_cues():
            if cue is not None:
                program['cues'].append(cue.properties())

        # Add the plugins
        failed = program['plugins'] = plugins.get_plugin_settings()
        for err in failed:
            msg = 'Plugin "' + err[0] + '" saving failed'
            QDetailedMessageBox.dcritical('Plugin error', msg, str(err[1]))

        # Add the app settings
        program['application'] = self.app_conf

        # Write to a file the json-encoded dictionary
        with open(filepath, mode='w', encoding='utf-8') as file:
            file.write(json.dumps(program, sort_keys=True, indent=4))

        ActionsHandler().set_saved()
        self.mainWindow.update_window_title()

    def _load_from_file(self, filepath):
        ''' Loads a saved program from "filepath" '''
        try:
            # Read the file
            with open(filepath, mode='r', encoding='utf-8') as file:
                program = json.load(file)

            # Get the application settings
            self.app_conf = program['application']

            # Create the layout
            self._create_layout(layouts.get_layout(self.app_conf['layout']))

            # Load cues
            for cue_conf in program['cues']:
                cue = CueFactory.create_cue(cue_conf)
                if cue is not None:
                    self.layout.add_cue(cue, cue['index'])

            ActionsHandler().set_saved()
            self.mainWindow.update_window_title()

            # Load plugins settings
            self._load_plugins_settings(program['plugins'])

            # Update the main-window
            self.mainWindow.file = filepath
            self.mainWindow.update()
        except Exception:
            QMessageBox.critical(None, 'Error', 'Error during file reading')
            print(traceback.format_exc(), file=sys.stderr)

            self._startup()

    def _init_plugins(self):
        ''' Initialize all the plugins '''
        failed = plugins.init_plugins()

        for err in failed:
            msg = 'Plugin "' + err[0] + '" initialization failed'
            QDetailedMessageBox.dcritical('Plugin error', msg, str(err[1]))

    def _load_plugins_settings(self, settings):
        ''' Loads all the plugins settings '''

        failed = plugins.set_plugins_settings(settings)

        for err in failed:
            msg = 'Plugin "' + err[0] + '" loading failed'
            QDetailedMessageBox.dcritical('Plugin error', msg, str(err[1]))
class Application(metaclass=Singleton):
    def __init__(self):
        self._mainWindow = MainWindow()
        self._app_conf = {}
        self._layout = None
        self._memento_model = None
        self._cue_model = CueModel()

        # Connect mainWindow actions
        self._mainWindow.new_session.connect(self.new_session_dialog)
        self._mainWindow.save_session.connect(self._save_to_file)
        self._mainWindow.open_session.connect(self._load_from_file)

        # Register general settings widget
        AppSettings.register_settings_widget(AppGeneral)
        AppSettings.register_settings_widget(CueAppSettings)

        # Show the mainWindow maximized
        self._mainWindow.showMaximized()

    @property
    def layout(self):
        """:rtype: lisp.layouts.cue_layout.CueLayout"""
        return self._layout

    @property
    def cue_model(self):
        """:rtype: lisp.cues.cue_model.CueModel"""
        return self._cue_model

    def start(self, session_file=''):
        if exists(session_file):
            self._load_from_file(session_file)
        elif cfg.config['Layout']['Default'].lower() != 'nodefault':
            layout = layouts.get_layout(cfg.config['Layout']['Default'])
            self._new_session(layout)
        else:
            self.new_session_dialog()

    def new_session_dialog(self):
        """Show the layout-selection dialog"""
        try:
            # Prompt the user for a new layout
            dialog = LayoutSelect()
            if dialog.exec_() != QDialog.Accepted:
                if self._layout is None:
                    # If the user close the dialog, and no layout exists
                    # the application is closed
                    self.finalize()
                    qApp.quit()
                    exit()
                else:
                    return

            # If a valid file is selected load it, otherwise load the layout
            if exists(dialog.filepath):
                self._load_from_file(dialog.filepath)
            else:
                self._new_session(dialog.selected())

        except Exception as e:
            elogging.exception('Startup error', e)
            qApp.quit()
            exit(-1)

    def _new_session(self, layout):
        self._delete_session()

        self._layout = layout(self._cue_model)
        self._memento_model = AdapterMementoModel(self.layout.model_adapter)
        self._mainWindow.set_layout(self._layout)
        self._app_conf['layout'] = layout.NAME

        plugins.init_plugins()

    def _delete_session(self):
        if self._layout is not None:
            MainActionsHandler.clear()
            plugins.reset_plugins()

            self._app_conf.clear()
            self._cue_model.reset()

            self._layout.finalize()
            self._layout = None
            self._memento_model = None
            self._cue_model.reset()

    def finalize(self):
        modules.terminate_modules()

        self._delete_session()
        self._mainWindow.deleteLater()

    def _save_to_file(self, session_file):
        """Save the current session into a file."""
        session = {"cues": [], "plugins": {}, "application": []}

        # Add the cues
        for cue in self._cue_model:
            session['cues'].append(cue.properties(only_changed=True))
        # Sort cues by index, allow sorted-models to load properly
        session['cues'].sort(key=lambda cue: cue['index'])

        session['plugins'] = plugins.get_plugin_settings()
        session['application'] = self._app_conf

        # Write to a file the json-encoded dictionary
        with open(session_file, mode='w', encoding='utf-8') as file:
            file.write(json.dumps(session, sort_keys=True, indent=4))

        MainActionsHandler.set_saved()
        self._mainWindow.update_window_title()

    def _load_from_file(self, session_file):
        """ Load a saved session from file """
        try:
            with open(session_file, mode='r', encoding='utf-8') as file:
                session = json.load(file)

            # New session
            self._new_session(
                layouts.get_layout(session['application']['layout']))
            # Get the application settings
            self._app_conf = session['application']

            # Load cues
            for cue_conf in session['cues']:
                cue_type = cue_conf.pop('_type_', 'Undefined')
                cue_id = cue_conf.pop('id')
                try:
                    cue = CueFactory.create_cue(cue_type, cue_id=cue_id)
                    cue.update_properties(cue_conf)
                    self._cue_model.add(cue)
                except Exception as e:
                    elogging.exception('Unable to create the cue', e)

            MainActionsHandler.set_saved()
            self._mainWindow.update_window_title()

            # Load plugins settings
            plugins.set_plugins_settings(session['plugins'])

            # Update the main-window
            self._mainWindow.filename = session_file
            self._mainWindow.update()
        except Exception as e:
            elogging.exception('Error during file reading', e)
            self.new_session_dialog()
class Application(metaclass=Singleton):
    def __init__(self):
        self._mainWindow = MainWindow()
        self._app_conf = {}
        self._layout = None
        self._memento_model = None
        self._cue_model = CueModel()

        # Connect mainWindow actions
        self._mainWindow.new_session.connect(self.new_session_dialog)
        self._mainWindow.save_session.connect(self._save_to_file)
        self._mainWindow.open_session.connect(self._load_from_file)

        # Register general settings widget
        AppSettings.register_settings_widget(General)

        # Show the mainWindow maximized
        self._mainWindow.showMaximized()

    @property
    def layout(self):
        """:rtype: lisp.layouts.cue_layout.CueLayout"""
        return self._layout

    @property
    def cue_model(self):
        """:rtype: lisp.model_view.cue_model.CueModel"""
        return self._cue_model

    def start(self, session_file=''):
        if exists(session_file):
            self._load_from_file(session_file)
        elif cfg.config['Layout']['Default'].lower() != 'nodefault':
            layout = layouts.get_layout(cfg.config['Layout']['Default'])
            self._new_session(layout)
        else:
            self.new_session_dialog()

    def new_session_dialog(self):
        """Show the layout-selection dialog"""
        try:
            # Prompt the user for a new layout
            dialog = LayoutSelect()
            if dialog.exec_() != QDialog.Accepted:
                if self._layout is None:
                    # If the user close the dialog, and no layout exists
                    # the application is closed
                    self.finalize()
                    qApp.quit()
                    exit()
                else:
                    return

            # If a valid file is selected load it, otherwise load the layout
            if exists(dialog.filepath):
                self._load_from_file(dialog.filepath)
            else:
                self._new_session(dialog.selected())

        except Exception as e:
            logging.exception('Startup error', e)
            qApp.quit()
            exit(-1)

    def _new_session(self, layout):
        self._delete_session()

        self._layout = layout(self._cue_model)
        self._memento_model = AdapterMementoModel(self.layout.model_adapter)
        self._mainWindow.set_layout(self._layout)
        self._app_conf['layout'] = layout.NAME

        plugins.init_plugins()

    def _delete_session(self):
        if self._layout is not None:
            MainActionsHandler().clear()
            plugins.reset_plugins()

            self._app_conf.clear()
            self._cue_model.reset()

            self._layout.finalize()
            self._layout = None
            self._memento_model = None

    def finalize(self):
        self._delete_session()
        modules.terminate_modules()

    def _save_to_file(self, session_file):
        """ Save the current session into a file """
        session = {"cues": [], "plugins": {}, "application": []}

        # Add the cues
        for cue in self._cue_model:
            session['cues'].append(cue.properties())
        # Sort cues by index, allow sorted-models to load properly
        session['cues'].sort(key=lambda cue: cue['index'])

        session['plugins'] = plugins.get_plugin_settings()
        session['application'] = self._app_conf

        # Write to a file the json-encoded dictionary
        with open(session_file, mode='w', encoding='utf-8') as file:
            file.write(json.dumps(session, sort_keys=True, indent=4))

        MainActionsHandler().set_saved()
        self._mainWindow.update_window_title()

    def _load_from_file(self, session_file):
        """ Load a saved session from file """
        try:
            with open(session_file, mode='r', encoding='utf-8') as file:
                session = json.load(file)

            # New session
            self._new_session(
                layouts.get_layout(session['application']['layout']))
            # Get the application settings
            self._app_conf = session['application']

            # Load cues
            for cue_conf in session['cues']:
                cue_type = cue_conf.pop('_type_', 'Undefined')
                try:
                    cue = CueFactory.create_cue(cue_type)
                    cue.update_properties(cue_conf)
                    self._cue_model.add(cue)
                except Exception as e:
                    logging.exception('Unable to create the cue', e)

            MainActionsHandler().set_saved()
            self._mainWindow.update_window_title()

            # Load plugins settings
            plugins.set_plugins_settings(session['plugins'])

            # Update the main-window
            self._mainWindow.filename = session_file
            self._mainWindow.update()
        except Exception as e:
            logging.exception('Error during file reading', e)
            self.new_session_dialog()