#! /usr/bin/env python
# -*- coding: utf-8 -*-

"""
Module that contains implementation for custom PySide/PyQt windows
"""

from __future__ import print_function, division, absolute_import

import os
import uuid
from collections import defaultdict

from Qt.QtCore import *
from Qt.QtWidgets import *
from Qt.QtGui import *

import appdirs

from tpDcc.libs import qt
import tpDcc as tp
from tpDcc import register
from tpDcc.libs.python import path, folder
from tpDcc.libs.qt.core import qtutils, animation, theme, statusbar, dragger, resizers, settings as qt_settings
from tpDcc.libs.qt.widgets import layouts


class WindowContents(QFrame, object):
    """
    Widget that defines the core contents of frameless window
    Can be used to custom CSS for frameless windows contents
    """

    def __init__(self, parent=None):
        super(WindowContents, self).__init__(parent=parent)


class BaseWindow(QMainWindow, object):

    closed = Signal()

    WindowName = 'New Window'

    def __init__(self, parent=None, **kwargs):

        main_window = tp.Dcc.get_main_window()
        if parent is None:
            parent = main_window

        window_id = kwargs.get('id', None)
        self._theme = None
        self._docks = list()
        self._toolbars = dict()
        self._dpi = kwargs.get('dpi', 1.0)
        self._fixed_size = kwargs.get('fixed_size', False)
        self._init_width = kwargs.get('width', 600)
        self._init_height = kwargs.get('height', 800)
        self._has_main_menu = False
        self._show_status_bar = kwargs.pop('show_statusbar', True)
        self._init_menubar = kwargs.pop('init_menubar', False)
        self._settings = kwargs.pop('settings', None)
        self._prefs_settings = kwargs.pop('preferences_settings', None)
        self._enable_save_position = True
        self._initial_pos_override = None
        self._signals = defaultdict(list)
        self._force_disable_saving = False
        win_settings = kwargs.pop('settings', None)
        prefs_settings = kwargs.pop('preferences_settings', None)
        auto_load = kwargs.get('auto_load', True)

        if not hasattr(self, 'WindowId'):
            if window_id:
                self.WindowId = window_id
            else:
                self._force_disable_saving = True
                self.WindowId = str(uuid.uuid4())

        super(BaseWindow, self).__init__(parent)

        self.setObjectName(str(self.WindowId))
        self.setWindowTitle(kwargs.get('title', self.WindowName))
        self.setWindowIcon(kwargs.get('icon', tp.ResourcesMgr().icon('tpdcc')))

        self.resize(self._init_width, self._init_height)
        self.center(self._init_width, self._init_height)

        # Load base generic window UI
        self._base_ui()

        # Load custom window UI
        self.ui()
        self.setup_signals()

        self.load_settings(settings=win_settings)

        if auto_load:
            self.load_theme()
        else:
            self.reload_stylesheet()

    # ============================================================================================================
    # OVERRIDES
    # ============================================================================================================

    def menuBar(self):
        return self._menubar

    def show(self, *args, **kwargs):
        """
        Shows the window and load its position
        :param self:
        :param args:
        :param kwargs:
        """

        super(BaseWindow, self).show()
        self.load_window_position()

        return self

    def closeEvent(self, event):
        self.save_settings()
        self.closed.emit()
        for child in self.findChildren(QWidget):
            child.close()
        self.setParent(None)
        self.deleteLater()

    def addDockWidget(self, area, dock_widget, orientation=Qt.Horizontal, tabify=True):
        """
        Overrides base QMainWindow addDockWidet function
        :param QDockWidgetArea area: area where dock will be added
        :param QDockWidget dock_widget: dock widget to add
        :param Qt.Orientation orientation: orientation fo the dock widget
        :param bool tabify: Whether or not dock widget can be tabbed
        """

        self._docks.append(dock_widget)
        if self._has_main_menu:
            self._view_menu.addAction(dock_widget.toggleViewAction())

        if tabify:
            for current_dock in self._docks:
                if self.dockWidgetArea(current_dock) == area:
                    self.tabifyDockWidget(current_dock, dock_widget)
                    dock_widget.setVisible(True)
                    dock_widget.setFocus()
                    dock_widget.raise_()
                    return

        super(BaseWindow, self).addDockWidget(area, dock_widget, orientation)

    # ============================================================================================================
    # BASE
    # ============================================================================================================

    def process_events(self):
        """
        Forces Qt application to update GUI between calculations
        """

        QApplication.processEvents()

    def center(self, width=None, height=None):
        """
        Centers window to the center of the desktop
        :param width: int
        :param height: int
        """

        geometry = self.frameGeometry()
        if width:
            geometry.setWidth(width)
        if height:
            geometry.setHeight(height)

        desktop = QApplication.desktop()
        pos = desktop.cursor().pos()
        screen = desktop.screenNumber(pos)
        center_point = desktop.screenGeometry(screen).center()
        geometry.moveCenter(center_point)
        self.window().setGeometry(geometry)

    def center_to_parent(self, parent_geometry=None, child_geometry=None):
        """
        Centers current window to its parent
        :param parent_geometry:
        :param child_geometry:
        """

        if parent_geometry is None or child_geometry is None:
            base_window = self
            if parent_geometry is None:
                try:
                    parent_geometry = base_window.parent().frameGeometry()
                except AttributeError:
                    parent_geometry = QApplication.desktop().screenGeometry()
            if child_geometry is None:
                child_geometry = base_window.frameGeometry()

        self.move(
            parent_geometry.x() + (parent_geometry.width() - child_geometry.width()) / 2,
            parent_geometry.y() + (parent_geometry.height() - child_geometry.height()) / 2
        )

    def fade_close(self):
        """
        Closes the window with a fade animation
        """

        animation.fade_window(start=1, end=0, duration=400, object=self, on_finished=self.close)

    def show_ok_message(self, message, msecs=None):
        """
        Set an ok message to be displayed in the status bar
        :param message: str
        :param msecs: int
        """

        self._status_bar.show_ok_message(message=message, msecs=msecs)

    def show_info_message(self, message, msecs=None):
        """
        Set an info message to be displayed in the status bar
        :param message: str
        :param msecs: int
        """

        self._status_bar.show_info_message(message=message, msecs=msecs)

    def show_warning_message(self, message, msecs=None):
        """
       Set a warning message to be displayed in the status widget
       :param message: str
       :param msecs: int
       """

        self._status_bar.show_warning_message(message=message, msecs=msecs)

    def show_error_message(self, message, msecs=None):
        """
       Set an error message to be displayed in the status widget
       :param message: str
       :param msecs: int
       """

        self._status_bar.show_error_message(message=message, msecs=msecs)

    # ============================================================================================================
    # UI
    # ============================================================================================================

    def get_main_layout(self):
        """
        Returns the main layout being used by the window
        :return: QLayout
        """

        main_layout = QVBoxLayout()
        main_layout.setContentsMargins(2, 2, 2, 2)
        main_layout.setSpacing(2)

        return main_layout

    def ui(self):
        """
        Function used to define UI of the window
        """

        pass

    def _base_ui(self):
        """
        Internal function that setup basic window UI
        """

        self.setDockNestingEnabled(True)
        self.setDocumentMode(True)
        self.setDockOptions(QMainWindow.AllowNestedDocks | QMainWindow.AnimatedDocks | QMainWindow.AllowTabbedDocks)
        self.setTabPosition(Qt.AllDockWidgetAreas, QTabWidget.North)

        # Central Widget

        central_widget = QWidget(parent=self)
        self.setCentralWidget(central_widget)
        self._central_layout = layouts.VerticalLayout(margins=(0, 0, 0, 0), spacing=0)
        central_widget.setLayout(self._central_layout)

        self._top_widget = QWidget()
        self._top_layout = layouts.VerticalLayout(margins=(0, 0, 0, 0), spacing=0)
        self._top_widget.setLayout(self._top_layout)

        # Status Bar
        #
        # self.statusBar().showMessage('')
        # self.statusBar().setSizeGripEnabled(not self._fixed_size)
        # self._status_bar = self.STATUS_BAR_WIDGET(self)
        # self.statusBar().addWidget(self._status_bar)
        # self.statusBar().setVisible(self._show_status_bar)

        # MenuBar
        self._menubar = QMenuBar()
        if self._init_menubar:
            self._has_main_menu = True
            self._file_menu = self.menuBar().addMenu('File')
            self._view_menu = self.menuBar().addMenu('View')
            self._exit_action = QAction(self)
            self._exit_action.setText('Close')
            self._exit_action.setShortcut('Ctrl + Q')
            self._exit_action.setIcon(tp.ResourcesMgr().resource.icon('close_window'))
            self._exit_action.setToolTip('Close application')
            self._file_menu.addAction(self._exit_action)
            self._exit_action.triggered.connect(self.fade_close)
            for i in self._docks:
                self._view_menu.addAction(i.toggleViewAction())
        self._top_layout.addWidget(self._menubar)

        self.main_widget = WindowContents()
        self.main_layout = self.get_main_layout()
        self.main_widget.setLayout(self.main_layout)

        self._central_layout.addWidget(self._top_widget)
        self._central_layout.addWidget(self.main_widget)

    # ============================================================================================================
    # SIGNALS
    # ============================================================================================================

    def setup_signals(self):
        """
        Override in derived class to setup signals
        This function is called after ui() function is called
        """

        pass

    def signal_connect(self, signal, fn, group=None):
        """
        Adds a new signal for the given group
        :param signal:
        :param fn:
        :param group:
        """

        self._signals[group].append((signal, fn))
        signal.connect(fn)

        return fn

    def signal_disconnect(self, group):
        """
        Disconnects and returns all functions for a current group
        :param group:
        :return: list
        """

        signals = list()
        for (signal, fn) in self._signals.pop(group, list()):
            try:
                signal.disconnect(fn)
            except RuntimeError:
                pass
            else:
                signals.append((signal, fn))

        return signals

    def signal_pause(self, *groups):
        """
        Pauses a certain set of signals during execution
        :param groups: list
        """

        if not groups:
            groups = self._signals

        signal_cache = dict()
        for group in groups:
            signal_cache[group] = self.signal_disconnect(group)

        yield

        for group in groups:
            for signal, fn in signal_cache[group]:
                self.signal_connect(signal, fn, group=group)

    # ============================================================================================================
    # SETTINGS
    # ============================================================================================================

    def settings(self):
        """
        Returns window settings
        :return: QtSettings
        """

        return self._settings

    def default_settings(self):
        """
        Returns default settings values
        :return: dict
        """

        return {}

    def set_settings(self, settings):
        """
        Set window settings
        :param settings:
        """

        self._settings = settings

        def_settings = self.default_settings()

        def_geometry = self.settings().get_default_value('geometry', self.objectName().upper())
        geometry = self.settings().getw('geometry', def_geometry)
        if geometry:
            self.restoreGeometry(geometry)

            # Reposition window in the center of the screen if the window is outside of the screen
            geometry = self.geometry()
            x = geometry.x()
            y = geometry.y()
            width = self._init_width or geometry.width()
            height = self._init_height or geometry.height()
            screen_geo = QApplication.desktop().screenGeometry()
            screen_width = screen_geo.width()
            screen_height = screen_geo.height()
            if x <= 0 or y <= 0 or x >= screen_width or y >= screen_height:
                self.center(width, height)

        def_window_state = self.settings().get_default_value('windowState', self.objectName().upper())
        window_state = self.settings().getw('windowState', def_window_state)
        if window_state:
            self.restoreState(window_state)

    def load_settings(self, settings=None):
        """
        Loads window settings from disk
        """

        settings = settings or self.settings()
        if not settings:
            settings = self._settings
            if not settings:
                self._settings = qt_settings.QtSettings(filename=self.get_settings_file(), window=self)
                self._settings.setFallbacksEnabled(False)
                if not self._prefs_settings:
                    self._prefs_settings = self._settings
            return self.set_settings(self._settings)

        return self.set_settings(settings)

    def save_settings(self, settings=None):
        """
        Saves window settings
        """

        settings = settings or self.settings()
        if not settings:
            return

        settings.setw('geometry', self.saveGeometry())
        settings.setw('saveState', self.saveState())
        settings.setw('windowState', self.saveState())

        return settings

    def get_settings_path(self):
        """
        Returns path where window settings are stored
        :return: str
        """

        return path.clean_path(os.path.join(appdirs.user_data_dir(), self.WindowId))

    def get_settings_file(self):
        """
        Returns file path of the window settings file
        :return: str
        """

        return path.clean_path(os.path.expandvars(os.path.join(self.get_settings_path(), 'settings.cfg')))

    def enable_save_window_position(self, enable):
        """
        Enables or disables the storage of window position in settings
        :param enable: bool
        """

        self._enable_save_position = enable

    def load_window_position(self):
        pass
        # if self._initial_pos_override is not None:
        #     x, y = self._initial_pos_override()
        #     x, y =

    def save_window_position(self):
        print('Saving window position ...')

    # ============================================================================================================
    # DPI
    # ============================================================================================================

    def dpi(self):
        """
        Return the current dpi for the window
        :return: float
        """

        return float(self._dpi)

    def set_dpi(self, dpi):
        """
        Sets current dpi for the window
        :param dpi: float
        """

        self._dpi = dpi

    # ============================================================================================================
    # THEME
    # ============================================================================================================

    def load_theme(self):
        """
        Loads window theme
        """

        def_settings = self.default_settings()
        def_theme_settings = def_settings.get('theme', dict())
        accent_color = self.settings().getw('theme/accent_color') or def_theme_settings.get('accent_color')
        background_color = self.settings().getw('theme/background_color') or def_theme_settings.get('background_color')
        accent_color = 'rgb(%d, %d, %d, %d)' % accent_color.getRgb() if isinstance(
            accent_color, QColor) else accent_color
        background_color = 'rgb(%d, %d, %d, %d)' % background_color.getRgb() if isinstance(
            background_color, QColor) else background_color

        theme_settings = dict()
        if accent_color:
            theme_settings['accent_color'] = accent_color
        if background_color:
            theme_settings['background_color'] = background_color

        self.set_theme_settings(theme_settings)

    def theme(self):
        """
        Returns the current theme
        :return: Theme
        """

        if not self._theme:
            return None

        return self._theme

    def set_theme(self, theme):
        """
        Sets current window theme
        :param theme: Theme
        """

        self._theme = theme
        self._theme.updated.connect(self.reload_stylesheet)
        self.reload_stylesheet()

    def set_theme_settings(self, settings):
        """
        Sets the theme settings from the given settings
        :param settings: dict
        """

        current_theme = self._settings.get('theme', 'default')
        new_theme = tp.ResourcesMgr().theme(current_theme)
        if not new_theme:
            new_theme = theme.Theme()
        new_theme.set_settings(settings)
        self.set_theme(new_theme)

    def reload_stylesheet(self):
        """
        Reloads the stylesheet to the current theme
        """

        current_theme = self.theme()
        current_theme.set_dpi(self.dpi())
        stylesheet = current_theme.stylesheet()
        self.setStyleSheet(stylesheet)

        # TODO: This operation is VERY heavy. Find a better way of doing this
        # all_widgets = qtutils.iterate_children(self.main_widget, qobj_class=QObject)
        # for w in all_widgets:
        #     if hasattr(w, 'setStyleSheet'):
        #         w.setStyleSheet(stylesheet)
        #         w.update()

    # ============================================================================================================
    # TOOLBAR
    # ============================================================================================================

    def add_toolbar(self, name, area=Qt.TopToolBarArea):
        """
        Adds a new toolbar to the window
        :return:  QToolBar
        """

        new_toolbar = QToolBar(name)
        self.addToolBar(area, new_toolbar)

        return new_toolbar

    # ============================================================================================================
    # DOCK
    # ============================================================================================================

    def add_dock(self, name, widget=None, pos=Qt.LeftDockWidgetArea, tabify=True):
        """
        Adds a new dockable widget to the window
        :param name: str, name of the dock widget
        :param widget: QWidget, widget to add to the dock
        :param pos: Qt.WidgetArea
        :param tabify: bool, Wheter the new widget should be tabbed to existing docks
        :return: QDockWidget
        """

        if widget:
            dock_name = ''.join([widget.objectName(), 'Dock'])
        else:
            dock_name = name + 'Dock'

        existing_dock = self.find_dock(dock_name)
        if existing_dock:
            existing_dock.raise_()

        dock = DockWidget(title=name, parent=self, floating=False)
        dock.setObjectName(dock_name)
        if widget is not None:
            dock.setWidget(widget)
        self.addDockWidget(pos, dock, tabify=tabify)

        return dock

    def set_active_dock_tab(self, dock_widget):
        """
        Sets the current active dock tab depending on the given dock widget
        :param dock_widget: DockWidget
        """

        tab_bars = self.findChildren(QTabBar)
        for bar in tab_bars:
            count = bar.count()
            for i in range(count):
                data = bar.tabData(i)
                widget = qtutils.to_qt_object(data, qobj=type(dock_widget))
                if widget == dock_widget:
                    bar.setCurrentIndex(i)

    def find_dock(self, dock_name):
        """
        Returns the dock widget based on the object name passed
        :param str dock_name: dock objectName to find
        :return: QDockWidget or None
        """

        for dock in self._docks:
            if dock.objectName() == dock_name:
                return dock

        return None

    def _parent_override(self):
        """
        Internal function that overrides parent functionality to make sure that proper parent attributes are used
        in dockable windows
        """

        # Make sure this function is inherited
        return super(MainWindow, self)

    # ============================================================================================================
    # INTERNAL
    # ============================================================================================================

    def _load_ui_from_file(self, ui_file):
        """
        Internal function that loads given UI file
        :param ui_file: str
        :return: QWidget or None
        """

        if not os.path.isfile(ui_file):
            return None

        loaded_ui = qtutils.load_ui(ui_file=ui_file)

        return loaded_ui


class MainWindow(BaseWindow, object):
    """
    Main class to create windows
    """

    dockChanged = Signal(object)
    windowResizedFinished = Signal()
    framelessChanged = Signal(object)
    windowReady = Signal()
    clearedInstance = Signal()

    STATUS_BAR_WIDGET = statusbar.StatusWidget
    DRAGGER_CLASS = dragger.WindowDragger

    _WINDOW_INSTANCES = dict()

    def __init__(self, parent=None, **kwargs):

        self._setup_resizers()

        self._preference_widgets_classes = list()
        self._toolset = kwargs.get('toolset', None)
        self._transparent = kwargs.get('transparent', False)
        self._config = kwargs.pop('config', None)
        self._dockable = getattr(self, 'WindowDockable', False)
        self._was_docked = False
        self._window_loaded = False
        self._window_closed = False
        self._current_docked = None

        super(MainWindow, self).__init__(parent=parent, **kwargs)

        frameless = kwargs.get('frameless', True)
        self.set_frameless(frameless)
        if not frameless:
            self.set_resizer_active(False)
            self._dragger.set_dragging_enabled(False)
            self._dragger.set_window_buttons_state(False)
        else:
            self._dragger.set_dragging_enabled(True)
            self._dragger.set_window_buttons_state(True)
        self._dragger._toggle_frameless.setChecked(self.is_frameless())

        # We set the window title after UI is created
        self.setWindowTitle(kwargs.get('title', 'tpDcc'))
        self.setWindowIcon(kwargs.get('icon', tp.ResourcesMgr().icon('tpdcc')))

        MainWindow._WINDOW_INSTANCES[self.WindowId] = {
            'window': self
        }

        self.windowReady.connect(lambda: setattr(self, '_window_loaded', True))

    # ============================================================================================================
    # PROPERTIES
    # ============================================================================================================

    @property
    def widget(self):
        """
        Returns widget
        """

        return self._widget

    # ============================================================================================================
    # CLASS METHODS
    # ============================================================================================================

    @classmethod
    def instance(cls, parent=None, **kwargs):
        pass

    @classmethod
    def clear_window_instance(cls, window_id):
        """
        Closes the last class instance
        :param window_id:
        :return:
        """

        inst = cls._WINDOW_INSTANCES.pop(window_id, None)
        if inst is not None:
            try:
                inst['window'].clearedInstance.emit()
            except RuntimeError as exc:
                tp.logger.error('Error while clearing window instance: {} | {}'.format(window_id, exc))

        return inst

    @classmethod
    def clear_window_instances(cls):
        """
        Closes every loaded window
        """

        for window_id in tuple(cls._WINDOW_INSTANCES):
            cls.clear_window_instance(window_id)

    # ============================================================================================================
    # OVERRIDES
    # ============================================================================================================

    def showEvent(self, event):
        if self.docked() != self._current_docked:
            self._current_docked = self.docked()
            self.dockChanged.emit(self._current_docked)

        super(MainWindow, self).showEvent(event)

    def closeEvent(self, event):
        self._window_closed = True
        self.unregister_callbacks()
        self.clear_window_instance(self.WindowId)
        super(MainWindow, self).closeEvent(event)

    def setWindowIcon(self, icon):
        if self.is_frameless() or (hasattr(self, '_dragger') and self._dragger):
            self._dragger.set_icon(icon)
        super(MainWindow, self).setWindowIcon(icon)

    def setWindowTitle(self, title):
        if self.is_frameless() or (hasattr(self, '_dragger') and self._dragger):
            self._dragger.set_title(title)
        super(MainWindow, self).setWindowTitle(title)

    def show(self, *args, **kwargs):
        """
        Shows the window and load its position
        :param self:
        :param args:
        :param kwargs:
        """

        super(MainWindow, self).show()
        self.windowReady.emit()

        return self

    # ============================================================================================================
    # UI
    # ============================================================================================================

    def ui(self):
        """
        Function used to define UI of the window
        """

        super(MainWindow, self).ui()

        for r in self._resizers:
            r.setParent(self)

        # Dragger
        self._dragger = self.DRAGGER_CLASS(window=self)
        self._top_layout.insertWidget(0, self._dragger)

        for r in self._resizers:
            r.windowResizedFinished.connect(self.windowResizedFinished)
        self.set_resize_directions()

        grid_layout = layouts.GridLayout()
        grid_layout.setHorizontalSpacing(0)
        grid_layout.setVerticalSpacing(0)
        grid_layout.setContentsMargins(0, 0, 0, 0)
        grid_layout.addWidget(self._top_widget, 1, 1, 1, 1)
        grid_layout.addWidget(self.main_widget, 2, 1, 1, 1)
        grid_layout.addWidget(self._top_left_resizer, 0, 0, 1, 1)
        grid_layout.addWidget(self._top_resizer, 0, 1, 1, 1)
        grid_layout.addWidget(self._top_right_resizer, 0, 2, 1, 1)
        grid_layout.addWidget(self._left_resizer, 1, 0, 2, 1)
        grid_layout.addWidget(self._right_resizer, 1, 2, 2, 1)
        grid_layout.addWidget(self._bottom_left_resizer, 3, 0, 1, 1)
        grid_layout.addWidget(self._bottom_resizer, 3, 1, 1, 1)
        grid_layout.addWidget(self._bottom_right_resizer, 3, 2, 1, 1)
        grid_layout.setColumnStretch(1, 1)
        grid_layout.setRowStretch(2, 1)

        self._central_layout.addLayout(grid_layout)

        # Shadow effect for window
        # BUG: This causes some rendering problems when using other shadow effects in child widgets of the window
        # BUG: Also detected problems when updating wigets (tree views, web browsers, etc)
        # https://bugreports.qt.io/browse/QTBUG-35196
        # shadow_effect = QGraphicsDropShadowEffect(self)
        # shadow_effect.setBlurRadius(qtutils.dpi_scale(15))
        # shadow_effect.setColor(QColor(0, 0, 0, 150))
        # shadow_effect.setOffset(qtutils.dpi_scale(0))
        # self.setGraphicsEffect(shadow_effect)

        for r in self._resizers:
            r.windowResizedFinished.connect(self.windowResizedFinished)

        if self._toolset:
            self.main_layout.addWidget(self._toolset)

    # ============================================================================================================
    # SIGNALS
    # ============================================================================================================

    def register_callback(self, callback_type, fn):
        """
        Registers the given callback with the given function
        :param callback_type: tpDcc.DccCallbacks
        :param fn: Python function to be called when callback is emitted
        """

        if type(callback_type) in [list, tuple]:
            callback_type = callback_type[0]

        if callback_type not in tp.callbacks():
            tp.logger.warning('Callback Type: "{}" is not valid! Aborting callback creation ...'.format(callback_type))
            return

        from tpDcc.managers import callbacks
        return callbacks.CallbacksManager.register(callback_type=callback_type, fn=fn, owner=self)

    def unregister_callbacks(self):
        """
        Unregisters all callbacks registered by this window
        """

        from tpDcc.managers import callbacks
        callbacks.CallbacksManager.unregister_owner_callbacks(owner=self)

    # ============================================================================================================
    # BASE
    # ============================================================================================================

    def exists(self):
        """
        Returns whether or not this window exists
        :return: bool
        """

        return True

    def is_loaded(self):
        """
        Returns whether or not this window has been already loaded
        :return: bool
        """

        return self._window_loaded and not self.is_closed()

    def is_closed(self):
        """
        Returns whether or not this window has been closed
        """

        return self._window_closed

    def is_frameless(self):
        """
        Returns whether or not frameless functionality for this window is enable or not
        :return: bool
        """

        return self.window().windowFlags() & Qt.FramelessWindowHint == Qt.FramelessWindowHint

    def set_frameless(self, flag):
        """
        Sets whether frameless functionality is enabled or not
        :param flag: bool
        :param show: bool
        """

        window = self.window()

        if flag and not self.is_frameless():
            window.setAttribute(Qt.WA_TranslucentBackground)
            if qtutils.is_pyside2() or qtutils.is_pyqt5():
                window.setWindowFlags(window.windowFlags() | Qt.FramelessWindowHint | Qt.NoDropShadowWindowHint)
                window.setWindowFlags(window.windowFlags() ^ Qt.WindowMinMaxButtonsHint)
            else:
                window.setWindowFlags(Qt.Window | Qt.FramelessWindowHint)
                window.setWindowFlags(window.windowFlags() ^ Qt.WindowMinMaxButtonsHint)
            self.set_resizer_active(True)
        elif not flag and self.is_frameless():
            window.setAttribute(Qt.WA_TranslucentBackground)
            if qtutils.is_pyside2() or qtutils.is_pyqt5():
                window.setWindowFlags(window.windowFlags() | Qt.FramelessWindowHint | Qt.NoDropShadowWindowHint)
            else:
                self.setWindowFlags(Qt.Window | Qt.FramelessWindowHint)
            self.set_resizer_active(False)

        window.show()

    # ============================================================================================================
    # RESIZERS
    # ============================================================================================================

    def set_resizer_active(self, flag):
        """
        Sets whether resizers are enable or not
        :param flag: bool
        """

        if flag:
            for r in self._resizers:
                r.show()
        else:
            for r in self._resizers:
                r.hide()

    def set_resize_directions(self):
        """
        Sets the resize directions for the resizer widget of this window
        """

        self._top_resizer.set_resize_direction(resizers.ResizeDirection.Top)
        self._bottom_resizer.set_resize_direction(resizers.ResizeDirection.Bottom)
        self._right_resizer.set_resize_direction(resizers.ResizeDirection.Right)
        self._left_resizer.set_resize_direction(resizers.ResizeDirection.Left)
        self._top_left_resizer.set_resize_direction(resizers.ResizeDirection.Left | resizers.ResizeDirection.Top)
        self._top_right_resizer.set_resize_direction(resizers.ResizeDirection.Right | resizers.ResizeDirection.Top)
        self._bottom_left_resizer.set_resize_direction(resizers.ResizeDirection.Left | resizers.ResizeDirection.Bottom)
        self._bottom_right_resizer.set_resize_direction(
            resizers.ResizeDirection.Right | resizers.ResizeDirection.Bottom)

    def get_resizers_height(self):
        """
        Returns the total height of the vertical resizers
        :return: float
        """

        resizers = [self._top_resizer, self._bottom_resizer]
        total_height = 0
        for r in resizers:
            if not r.isHidden():
                total_height += r.minimumSize().height()

        return total_height

    def get_resizers_width(self):
        """
        Returns the total widht of the horizontal resizers
        :return: float
        """

        resizers = [self._left_resizer, self._right_resizer]
        total_width = 0
        for r in resizers:
            if not r.isHidden():
                total_width += r.minimumSize().width()

        return total_width

    def _setup_resizers(self):
        """
        Internal function that setup window resizers
        """

        self._top_resizer = resizers.VerticalResizer()
        self._bottom_resizer = resizers.VerticalResizer()
        self._right_resizer = resizers.HorizontalResizer()
        self._left_resizer = resizers.HorizontalResizer()
        self._top_left_resizer = resizers.CornerResizer()
        self._top_right_resizer = resizers.CornerResizer()
        self._bottom_left_resizer = resizers.CornerResizer()
        self._bottom_right_resizer = resizers.CornerResizer()

        self._resizers = [
            self._top_resizer, self._top_right_resizer, self._right_resizer, self._bottom_right_resizer,
            self._bottom_resizer, self._bottom_left_resizer, self._left_resizer, self._top_left_resizer
        ]

    # ============================================================================================================
    # PREFERENCES SETTINGS
    # ============================================================================================================

    def preferences_settings(self):
        """
        Returns window preferences settings
        :return: QtSettings
        """

        return self._prefs_settings

    def set_preferences_settings(self, prefs_settings):
        """
        Sets window preference settings
        :param prefs_settings:
        """

        self._prefs_settings = prefs_settings

    def register_preference_widget_class(self, widget_class):
        """
        Function used to registere preference widgets
        """

        if not hasattr(widget_class, 'CATEGORY'):
            qt.logger.warning(
                'Impossible to register Category Wigdet Class "{}" because it does not '
                'defines a CATEGORY attribute'.format(widget_class))
            return

        registered_prefs_categories = [pref.CATEGORY for pref in self._preference_widgets_classes]
        if widget_class.CATEGORY in registered_prefs_categories:
            qt.logger.warning(
                'Impossible to register Category Widget Class "{}" because its CATEGORY "{}" its '
                'already registered!'.format(widget_class, widget_class.CATEGORY))
            return

        self._preference_widgets_classes.append(widget_class)

    # ============================================================================================================
    # DOCK
    # ============================================================================================================

    def dockable(self, raw=False):
        """
        Returns whether or not the window is dockable
        :param raw: bool, If True, get current state of the window, otherwise get current setting
        :return: bool
        """

        if not raw and self._was_docked is not None:
            return self._was_docked

        return self._dockable

    def set_dockable(self, dockable, override=False):
        """
        Sets whether or not this window is dockable
        :param dockable: bool
        :param override: bool, If the dockable raw value should be set.
            Only should be used if the dock state has changed
        """

        if override:
            self._was_docked = self._dockable = dockable
        else:
            self._was_docked = self._dockable
            self._dockable = dockable
            self.save_window_position()

    def docked(self):
        """
        Returns whether or not this window is currently docked
        :return: bool
        """

        if not self.dockable():
            return False

        raise NotImplementedError('docked function is not implemented!')

    def is_floating(self):
        """
        Returns whether or not this window is floating
        :return: bool
        """

        return tp.Dcc.is_window_floating(self.WindowId)

    # ============================================================================================================
    # INTERNAL
    # ============================================================================================================

    def _settings_validator(self, **kwargs):
        """
        Validator used for the settings dialog
        :param kwargs: dict
        """

        fields = list()

        clr = kwargs.get("accentColor")
        if clr and self.theme().accent_color().to_string() != clr:
            self.theme().set_accent_color(clr)

        clr = kwargs.get("backgroundColor")
        if clr and self.theme().background_color().to_string() != clr:
            self.theme().set_background_color(clr)

        return fields

    def _settings_accepted(self, **kwargs):
        """
        Function that is called when window settings dialog are accepted
        :param kwargs: dict
        """

        if not self.settings():
            return

        theme_name = self.theme().name()
        accent_color = kwargs.get('accentColor', self.theme().accent_color().to_string())
        background_color = kwargs.get('backgroundColor', self.theme().background_color().to_string())
        if theme_name:
            self.settings().setw('theme/name', theme_name)
        self.settings().setw('theme/accentColor', accent_color)
        self.settings().setw('theme/backgroundColor', background_color)
        self.settings().sync()

        self.load_theme()

    def _setup_theme_preferences(self):

        from tpDcc.libs.qt.core import preferences
        from tpDcc.libs.qt.widgets import formwidget

        accent_color = self.theme().accent_color().to_string()
        background_color = self.theme().background_color().to_string()
        settings_validator = self._settings_validator
        settings_accepted = self._settings_accepted

        class ThemeCategoryWidget(preferences.CategoryWidgetBase, object):

            CATEGORY = 'Theme'

            def __init__(self, parent=None):
                super(ThemeCategoryWidget, self).__init__(parent=parent)

                self.main_layout = QVBoxLayout()
                self.main_layout.setContentsMargins(2, 2, 2, 2)
                self.main_layout.setSpacing(2)
                self.setLayout(self.main_layout)

                form = {
                    "title": "Theme",
                    "description": "Theme Colors",
                    "layout": "vertical",
                    "schema": [
                        {
                            "name": "accentColor",
                            "type": "color",
                            "value": accent_color,
                            "colors": [
                                "rgb(230, 80, 80, 255)",
                                "rgb(230, 125, 100, 255)",
                                "rgb(230, 120, 40)",
                                "rgb(240, 180, 0, 255)",
                                "rgb(80, 200, 140, 255)",
                                "rgb(50, 180, 240, 255)",
                                "rgb(110, 110, 240, 255)",
                            ]
                        },
                        {
                            "name": "backgroundColor",
                            "type": "color",
                            "value": background_color,
                            "colors": [
                                "rgb(40, 40, 40)",
                                "rgb(68, 68, 68)",
                                "rgb(80, 60, 80)",
                                "rgb(85, 60, 60)",
                                "rgb(60, 75, 75)",
                                "rgb(60, 64, 79)",
                                "rgb(245, 245, 255)",
                            ]
                        },
                    ],
                    "validator": settings_validator,
                    "accepted": settings_accepted
                }

                self._dlg = formwidget.FormDialog(parent=parent, form=form)
                self._dlg.setMinimumWidth(300)
                self._dlg.setMinimumHeight(300)
                self._dlg.setMaximumWidth(400)
                self._dlg.setMaximumHeight(400)
                self._dlg.accept_button().setText('Save')
                self._dlg.accept_button().setVisible(False)
                self._dlg.reject_button().setVisible(False)
                self._dlg.show()
                self.main_layout.addWidget(self._dlg)

        theme_prefs_widget = ThemeCategoryWidget(parent=self._preferences_window)

        return theme_prefs_widget


class DetachedWindow(QMainWindow):
    """
    Class that incorporates functionality to create detached windows
    """

    windowClosed = Signal(object)

    class DetachPanel(QWidget, object):
        widgetVisible = Signal(QWidget, bool)

        def __init__(self, parent=None):
            super(DetachedWindow.DetachPanel, self).__init__(parent=parent)

            self.main_layout = QVBoxLayout()
            self.setLayout(self.main_layout)

        def set_widget_visible(self, widget, visible):
            self.setVisible(visible)
            self.widgetVisible.emit(widget, visible)

        def set_widget(self, widget):
            qtutils.clear_layout(self.main_layout)
            self.main_layout.addWidget(widget)
            widget.show()

    class SettingGroup(object):
        global_group = ''

        def __init__(self, name):
            self.name = name
            self.settings = QSettings()

        def __enter__(self):
            if self.global_group:
                self.settings.beginGroup(self.global_group)
            self.settings.beginGroup(self.name)
            return self.settings

        def __exit__(self, *args):
            if self.global_group:
                self.settings.endGroup()
            self.settings.endGroup()
            self.settings.sync()

        @staticmethod
        def load_basic_window_settings(window, window_settings):
            window.restoreGeometry(window_settings.value('geometry', QByteArray()))
            window.restoreState(window_settings.value('windowstate', QByteArray()))
            try:
                window.split_state = window_settings.value('splitstate', '')
            except TypeError:
                window.split_state = ''

    def __init__(self, title, parent):
        self.tab_idx = -1
        super(DetachedWindow, self).__init__(parent=parent)

        self.main_widget = self.DetachPanel()
        self.setCentralWidget(self.main_widget)

        self.setWindowTitle(title)
        self.setWindowModality(Qt.NonModal)
        self.sgroup = self.SettingGroup(title)
        with self.sgroup as config:
            self.SettingGroup.load_basic_window_settings(self, config)

        self.statusBar().hide()

    def closeEvent(self, event):
        with self.sgroup as config:
            config.setValue('detached', False)
        self.windowClosed.emit(self)
        self.deleteLater()

    def moveEvent(self, event):
        super(DetachedWindow, self).moveEvent(event)
        self.save_settings()

    def resizeEvent(self, event):
        super(DetachedWindow, self).resizeEvent(event)
        self.save_settings()

    def set_widget_visible(self, widget, visible):
        self.setVisible(visible)

    def set_widget(self, widget):
        self.main_widget.set_widget(widget=widget)

    def save_settings(self, detached=True):
        with self.sgroup as config:
            config.setValue('detached', detached)
            config.setValue('geometry', self.saveGeometry())
            config.setValue('windowstate', self.saveState())


class DockWindow(QMainWindow, object):
    """
    Class that with dock functionality. It's not intended to use as main window (use MainWindow for that) but for
    being inserted inside a window and have a widget with dock functionality in the main layout of that window
    """

    class DockWidget(QDockWidget, object):
        def __init__(self, name, parent=None, window=None):
            super(DockWindow.DockWidget, self).__init__(name, parent)

            self.setWidget(window)

        # region Override Functions
        def setWidget(self, widget):
            """
            Sets the window instance of the dockable main window
            """

            super(DockWindow.DockWidget, self).setWidget(widget)

            if widget and issubclass(widget.__class__, MainWindow):
                # self.setFloating(True)
                self.setWindowTitle(widget.windowTitle())
                self.visibilityChanged.connect(self._visibility_changed)

                widget.setWindowFlags(Qt.Widget)
                widget.setParent(self)
                widget.windowTitleChanged.connect(self._window_title_changed)

        # endregion

        # region Private Functions
        def _visibility_changed(self, state):
            """
            Process QDockWidget's visibilityChanged signal
            """

            # TODO: Implement export widget properties functionality
            # widget = self.widget()
            # if widget:
            #     widget.export_settings()

        def _window_title_changed(self, title):
            """
            Process BaseWindow's windowTitleChanged signal
            :param title: str, new title
            """

            self.setWindowTitle(title)

    _last_instance = None

    def __init__(self, name='BaseWindow', title='DockWindow', use_scrollbar=False, parent=None):
        self.main_layout = self.get_main_layout()
        self.__class__._last_instance = self
        super(DockWindow, self).__init__(parent)

        self.docks = list()
        self.connect_tab_change = True
        self.use_scrollbar = use_scrollbar

        self.setObjectName(name)
        self.setWindowTitle(title)
        self.statusBar().setSizeGripEnabled(False)
        self.statusBar().hide()

        self.ui()

        self.tab_change_hide_show = True

    def keyPressEvent(self, event):
        return

    def get_main_layout(self):
        """
        Function that generates the main layout used by the widget
        Override if necessary on new widgets
        :return: QLayout
        """

        return QVBoxLayout()

    def ui(self):
        """
        Function that sets up the ui of the widget
        Override it on new widgets (but always call super)
        """

        main_widget = QWidget()
        if self.use_scrollbar:
            scroll = QScrollArea()
            scroll.setWidgetResizable(True)
            scroll.setWidget(main_widget)
            self._scroll_widget = scroll
            main_widget.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding))
            self.setCentralWidget(scroll)
        else:
            self.setCentralWidget(main_widget)

        main_widget.setLayout(self.main_layout)
        self.main_widget = main_widget

        self.main_layout.expandingDirections()
        self.main_layout.setContentsMargins(0, 0, 0, 0)
        self.main_layout.setSpacing(0)

        # ==========================================================================================

        # TODO: Check if we should put this on constructor
        # self.main_widget.setSizePolicy(QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum))
        # self.centralWidget().hide()

        self.setTabPosition(Qt.TopDockWidgetArea, QTabWidget.West)
        self.setDockOptions(self.AnimatedDocks | self.AllowTabbedDocks | self.AllowNestedDocks)

    def set_active_dock_tab(self, dock_widget):
        """
        Sets the current active dock tab depending on the given dock widget
        :param dock_widget: DockWidget
        """

        tab_bars = self.findChildren(QTabBar)
        for bar in tab_bars:
            count = bar.count()
            for i in range(count):
                data = bar.tabData(i)
                widget = qtutils.to_qt_object(data, qobj=type(dock_widget))
                if widget == dock_widget:
                    bar.setCurrentIndex(i)

    def add_dock(self, widget, name, pos=Qt.TopDockWidgetArea, tabify=True):
        docks = self._get_dock_widgets()
        for dock in docks:
            if dock.windowTitle() == name:
                dock.deleteLater()
                dock.close()
        dock_widget = self.DockWidget(name=name, parent=self)
        # dock_widget.setSizePolicy(QSizePolicy(QSizePolicy.Maximum, QSizePolicy.Minimum))
        dock_widget.setAllowedAreas(pos)
        dock_widget.setWidget(widget)

        self.addDockWidget(pos, dock_widget)

        if docks and tabify:
            self.tabifyDockWidget(docks[-1], dock_widget)

        dock_widget.show()
        dock_widget.raise_()

        tab_bar = self._get_tab_bar()
        if tab_bar:
            if self.connect_tab_change:
                tab_bar.currentChanged.connect(self._on_tab_changed)
                self.connect_tab_change = False

        return dock_widget

    def _get_tab_bar(self):
        children = self.children()
        for child in children:
            if isinstance(child, QTabBar):
                return child

    def _get_dock_widgets(self):
        found = list()
        for child in self.children():
            if isinstance(child, QDockWidget):
                found.append(child)

        return found

    def _on_tab_changed(self, index):
        if not self.tab_change_hide_show:
            return

        docks = self._get_dock_widgets()

        docks[index].hide()
        docks[index].show()


class SubWindow(MainWindow, object):
    """
    Class to create sub windows
    """

    def __init__(self, parent=None, **kwargs):
        super(SubWindow, self).__init__(parent=parent, frameless=False, **kwargs)


class DirectoryWindow(MainWindow, object):
    """
    Window that stores variable to store current working directory
    """

    def __init__(self, parent=None, **kwargs):
        self.directory = None
        super(DirectoryWindow, self).__init__(parent=parent, frameless=False, **kwargs)

    def set_directory(self, directory):
        """
        Sets the directory of the window. If the given folder does not exists, it will created automatically
        :param directory: str, new directory of the window
        """

        self.directory = directory

        if not path.is_dir(directory=directory):
            folder.create_folder(name=None, directory=directory)


class DockWidget(QDockWidget, object):
    """
    Base docked widget
    """

    def __init__(self, title, parent=None, floating=False):
        super(DockWidget, self).__init__(title, parent)

        self.setFloating(floating)
        self.setFeatures(
            QDockWidget.DockWidgetMovable | QDockWidget.DockWidgetFloatable | QDockWidget.DockWidgetClosable)


class DockWindowContainer(DockWidget, object):
    """
    Docked Widget used to dock windows inside other windows
    """

    def __init__(self, title):
        super(DockWindowContainer, self).__init__(title)

    def closeEvent(self, event):
        if self.widget():
            self.widget().close()
        super(DockWindowContainer, self).closeEvent(event)


register.register_class('Window', MainWindow)
