
from PySide6.QtCore import QSize, QPoint, QEvent
from PySide6.QtGui import QIcon, Qt, QMouseEvent
from PySide6.QtWidgets import QMainWindow, QToolBar, QStatusBar, QGroupBox, QButtonGroup
from PySide6.QtWidgets import QWidget,  QHBoxLayout, QVBoxLayout,  QGridLayout, QTabWidget, QMenu
from PySide6.QtWidgets import QDialog, QFileDialog

from .utils import *
from .widgets import *

__all__ = [
    'Window', 'Box', 'Grid', 'fieldset', 'Column', 'column', 'Group', 'group', 'Toolbar', 'toolbar',
    'Menubar', 'menubar', 'Statusbar', 'statusbar', 'GET', 'get', 'Button', 'button', 'Input',
    'input', 'Text', 'text', 'Image', 'image', 'CheckBox', 'checkbox', 'RadioButton', 'radiobutton',
    'Textarea', 'textarea', 'item', 'empty', 'separator', 'Exit', 'exit', 'Copy', 'copy', 'Cut', 'cut',
    'Paste', 'paste', 'Undo', 'undo', 'Redo', 'redo', 'link', 'ListWidget', 'listwidget', 'ListItem',
    'tab', 'TabWidget', 'tabwidget', 'Select', 'select', 'option', 'ProgressBar', 'progressbar',
    'Slider', 'slider', 'Dial', 'dial', 'Popup', 'popup', 'call', 'Titlebar', 'titlebar', 'File', 'file',
    'Folder', 'folder', 'ScrollArea', 'scrollarea', 'minimize', 'maximize', 'close', 'title'
]


app = start()

DEFAULT_WINDOW_LAYOUT = QWidget()


def init_window_css(css_string):
    custom, builtins = console_css(css_string)

    for items in custom:
        name, property = items

        if name in WIDGET_ID_SAFE:
            WIDGET_ID_SAFE[name].setStyleSheet(property)

    app.setStyleSheet(builtins)


def link(href: None = None):
    if href != None:
        LINK_ITEMS_INCLUDED.append(href)


def init_linked_files():
    for LINK_ITEM in LINK_ITEMS_INCLUDED:
        file_path = path.abspath(LINK_ITEM)

        is_file = path.isfile(file_path)
        is_valid_path = path.exists(file_path)

        if is_file != False and is_valid_path != False:
            with open(file_path, 'r') as file:
                file_content = file.read()

            init_window_css(file_content)


class MainWindow(QMainWindow):
    def __init__(self, title, icon, size, fixed_size, geometry, style, frame, movable, top):
        super().__init__()

        self._qwidget = DEFAULT_WINDOW_LAYOUT
        self._title = title
        self._size = size
        self._fixed_size = fixed_size
        self._icon = icon
        self._geometry = geometry
        self._movable = movable
        self._frame = frame

        self._query_fixed_size = False
        self._custom_title_bar = False

        width, height = self._size
        if width != None and height != None:
            self.resize(int(width), int(height))

        if self._geometry != None:
            if len(self._geometry) == 4:
                x, y, w, h = self._geometry
                self.setGeometry(x, y, w, h)

        self.setWindowTitle(self._title)
        if top == True:
            self.setWindowFlags(Qt.WindowStaysOnTopHint)

        app.setStyle(style)

        fixed_width, fixed_height = self._fixed_size
        if fixed_width != None and fixed_height != None:
            self._query_fixed_size = True
            self.setFixedSize(QSize(int(fixed_width), int(fixed_height)))

        global layout
        layout = QVBoxLayout()

        self._qwidget.setLayout(layout)

        if self._frame == False:
            self.setWindowFlag(Qt.FramelessWindowHint)

        self.setWindowIcon(QIcon(get_path(self._icon)))

    def WindowMenubar(self, menu_item, actions: list | None = None):
        if menu_item != None:
            menu_bar = self.menuBar()
            Parent = menu_bar.addMenu(menu_item)

            if actions != None:
                for item in actions:

                    item_type = item[0]

                    if item_type == ELEM_TYPE_ITEM:

                        item_label = item[1]

                        if item_label != DEFAULT_SEPARATOR_MARKER:
                            Child = Parent.addAction(INIT_ITEM(self, item))
                        else:
                            Parent.addSeparator()
                    elif item_type == ELEM_TYPE_SEPARATOR:
                        Parent.addSeparator()

                    else:
                        pass
                        # DO NOTHING FOR NOW. SETUP LATER

    def WindowToolbar(self, name, tool_items, movable, position, ID, size, border, orientation, newline):

        toolbar = INIT_WIDGET(ID, QToolBar(name))

        if newline == True:
            self.addToolBarBreak()

        width, height = size
        if width != None or height != None:
            toolbar.setIconSize(QSize(width, height))

        toolbar_position = position.lower()

        if toolbar_position == 'top':
            self.addToolBar(Qt.TopToolBarArea, toolbar)
        elif toolbar_position == 'bottom':
            self.addToolBar(Qt.BottomToolBarArea, toolbar)
        elif toolbar_position == 'left':
            self.addToolBar(Qt.LeftToolBarArea, toolbar)
        elif toolbar_position == 'right':
            self.addToolBar(Qt.RightToolBarArea, toolbar)
        else:
            raise ValueError(position)

        if tool_items != None:
            for item in tool_items:
                if type(item) != type(list()):
                    widget_type, widget_items = check_func(item)
                    widget = LayItOut(GRAB_WIDGET, widget_type, widget_items)
                    toolbar.addWidget(widget)
                else:

                    item_type = item[0]

                    if item_type == ELEM_TYPE_ITEM:

                        item_label = item[1]

                        if item_label != DEFAULT_SEPARATOR_MARKER:
                            toolbar.addAction(INIT_ITEM(self, item))
                        else:
                            toolbar.addSeparator()
                    elif item_type == ELEM_TYPE_SEPARATOR:
                        toolbar.addSeparator()

                    else:
                        pass
                        # DO NOTHING FOR NOW. SETUP LATER

        toolbar.setMovable(movable)

        if orientation.lower() in DEFAULT_VERTICAL_TYPES:
            toolbar.setOrientation(Qt.Vertical)
        elif orientation.lower() in DEFAULT_HORIZONTAL_TYPES:
            toolbar.setOrientation(Qt.Horizontal)
        else:
            raise ValueError(orientation)

        if border == False:
            toolbar.setStyleSheet('border-bottom: 0px')

    # Status bar functions

    def init_status(self):
        self.setStatusBar(QStatusBar(self))

    def ADD_PERMANENT_WIDGET(self, addwidget):
        self.statusBar().addPermanentWidget(addwidget)

    def ADD_NORMAL_WIDGET(self, addwidget):
        self.statusBar().addWidget(addwidget)

    def SHOW_STATUSBAR_MESSAGE(self, text, time):
        if time != None:
            time = time * 1000
            self.statusBar().showMessage(text, time)
        else:
            self.statusBar().showMessage(text)

    def CLEAR_STATUSBAR_MESSAGE(self):
        self.statusBar().clearMessage()

    def REMOVE_STATUSBAR_WIDGET(self, ID):
        if ID in WIDGET_ID_SAFE:
            self.statusBar().removeWidget(WIDGET_ID_SAFE[ID])
        else:
            raise IdError(f'id "{ID}" does not exist')

    def mousePressEvent(self, event: QMouseEvent):
        if self._movable == True:
            try:
                self.old_position = event.globalPos()
            except:
                ...

    def mouseMoveEvent(self, event: QMouseEvent):
        if self._movable == True:
            try:
                omega = QPoint(event.globalPos() - self.old_position)
                self.move(self.x() + omega.x(), self.y() + omega.y())
                self.old_position = event.globalPos()
            except:
                ...

    def WindowTitlebar(self, icon, title, widgets, alignment, backgound_color,  text_color, height,
                       button_style):
        current_window_style = window.style().name().lower()

        if current_window_style == SYSTEM_PLATFORM_FUSION:
            CONTROL_MINIMIZED, CONTROL_RESTORE, CONTROL_MAXIMIZED = FUSION_CONTROLS()
        elif current_window_style == SYSTEM_PLATFORM_WINDOWS:
            CONTROL_MINIMIZED, CONTROL_RESTORE, CONTROL_MAXIMIZED = WINDOWS_CONTROLS()
        else:
            CONTROL_MINIMIZED, CONTROL_RESTORE, CONTROL_MAXIMIZED = CLERA_CONTROLS()

        if self._custom_title_bar == False and self._frame == False:
            if alignment != None:
                if alignment.lower() == 'center':
                    align = 'center'
                else:
                    align = None
            else:
                align = None

            def window_state():
                state = str(self.windowState())
                state = state.split('.')
                state = state[1].lower()
                return state

            def control_action(key):
                state = window_state()
                max = GET(MAXIMIZE_BUTTON_ID)

                if key == 'max':
                    if state == 'windowmaximized':
                        self.setWindowState(Qt.WindowNoState)
                        max.value(CONTROL_MAXIMIZED)
                    elif state == 'windownostate':
                        self.setWindowState(Qt.WindowMaximized)
                        max.value(CONTROL_RESTORE)
                elif key == 'min':
                    self.setWindowState(Qt.WindowMinimized)
                    try:
                        max.value(CONTROL_MAXIMIZED)
                    except:
                        ...
            if icon != None:
                titlebar_icon = Image(icon, id=ICON_IMAGE_ID, size=20)
            else:
                titlebar_icon = empty()

            if current_window_style == SYSTEM_PLATFORM_FUSION or current_window_style == SYSTEM_PLATFORM_WINDOWS:
                title_items = [
                    titlebar_icon,
                    Text(title, id=TITLE_TEXT_ID,
                         sizepolicy=('expand', 'fixed'), alignment=align),
                    Button(CONTROL_MINIMIZED, id=MINIMIZE_BUTTON_ID,
                           func=call(control_action, 'min'), focus=False),
                    Button(CONTROL_MAXIMIZED, id=MAXIMIZE_BUTTON_ID,
                           func=call(control_action, 'max'), focus=False),
                    Button(CONTROL_CLOSE, id=CLOSE_BUTTON_ID,
                           func=Window.quit, focus=False)
                ]
            else:
                if icon != None:
                    align = 'center'
                else:
                    align = 'right'

                title_items = [
                    Button(CONTROL_CLOSE, id=CLOSE_BUTTON_ID,
                           func=Window.quit, focus=False),
                    Button(CONTROL_MINIMIZED, id=MINIMIZE_BUTTON_ID,
                           func=call(control_action, 'min'), focus=False),
                    Button(CONTROL_MAXIMIZED, id=MAXIMIZE_BUTTON_ID,
                           func=call(control_action, 'max'), focus=False),
                    Text(title, id=TITLE_TEXT_ID,
                         sizepolicy=('expand', 'fixed'), alignment=align),
                    titlebar_icon
                ]

            self._movable = True
            titlebar = INIT_WIDGET('titlebar', QToolBar('Titlebar'))

            titlebar.toggleViewAction().setVisible(False)

            titlebar.setIconSize(QSize(20, 20))

            self.addToolBar(Qt.TopToolBarArea, titlebar)

            self.addToolBarBreak()

            if widgets != None:
                if align == 'center':
                    for item in title_items:
                        append_items(widgets, item)
                    title_items = widgets
                else:
                    window_title = [title_items[0]]
                    controls = title_items[1:]

                    for item in widgets:
                        append_items(window_title, item)
                    else:
                        for item in controls:
                            append_items(window_title, item)

                    title_items = window_title

            for item in title_items:
                if type(item) != type(list()):
                    widget_type, widget_items = check_func(item)
                    widget = LayItOut(GRAB_WIDGET, widget_type, widget_items)
                    titlebar.addWidget(widget)

            titlebar.setMovable(False)

            titlebar.setOrientation(Qt.Horizontal)

            button_style_query = button_style.lower()

            if button_style_query == 'circle':
                control_button_style = CONTROL_BUTTON_CIRCLE
            elif button_style_query == 'square':
                control_button_style = CONTROL_BUTTON_SQUARE
            else:
                ...

            title = GET(TITLE_TEXT_ID)
            minimize = GET(MINIMIZE_BUTTON_ID)
            maximize = GET(MAXIMIZE_BUTTON_ID)
            close = GET(CLOSE_BUTTON_ID)

            def init_title_icon(icon, value: str = 'left'):
                if icon != None:
                    icon = GET(ICON_IMAGE_ID)
                    icon.style(f'margin-{value}: 10px;')

            init_title_icon(icon)
            if current_window_style == SYSTEM_PLATFORM_FUSION:
                fusion_style(minimize, maximize, close, control_button_style)
            elif current_window_style == SYSTEM_PLATFORM_WINDOWS:
                windows_style(minimize, maximize, close)
            else:
                init_title_icon(icon, 'right')
                clera_style(minimize, maximize, close)

            title.style(
                f'color: {text_color}; padding: 6px 0; margin: 0 {height}px')
            titlebar.setStyleSheet(
                f'border-bottom: 0px; background: {backgound_color};')
            self._custom_title_bar = True


# ------------------------------------------------------------------------#

###########################################################################
############################## WINDOWS CLASS ##############################
###########################################################################

# ------------------------------------------------------------------------#


class Window:
    def __init__(self, title: str = DEFAULT_WINDOW_TITLE, icon: str = DEFAULT_WINDOW_ICON, size: tuple = DEFAULT_WINDOW_SIZE,
                 geometry: tuple = DEFAULT_WINDOW_GEOMETRY, style: str = DEFAULT_WINDOW_STYLE, fixed_size: tuple = DEFAULT_WINDOW_FIXED_SIZE, frame: bool = True,
                 movable: bool = False, top: bool = False):
        '''
        :param title:
        :param icon:
        :param size:
        :param geometry:
        :param style:
        :param fixed_size:
        :param frame:
        :param movable:
        '''
        global window

        self.window_title = title
        self.window_icon = icon
        self.window_size = size
        self.window_fixed_size = fixed_size
        self.window_geometry = geometry
        self.window_style = style
        self.frame = frame
        self.movable = movable
        self.top = top

        window = MainWindow(self.window_title, self.window_icon,
                            self.window_size, self.window_fixed_size, self.window_geometry, self.window_style, self.frame,
                            self.movable, self.top)

    def run(self, css: None = None):
        window.show()
        if css != None:
            init_window_css(css)

        if len(LINK_ITEMS_INCLUDED) != 0:
            init_linked_files()

        def window_state():
            state = str(window.windowState())
            state = state.split('.')
            state = state[1].lower()
            return state

        state = window_state()
        try:
            max = GET(MAXIMIZE_BUTTON_ID)

            if state == 'windowmaximized':
                max.value(CONTROL_RESTORE)

            if window._query_fixed_size == True:
                max.delete()

            if window._custom_title_bar == True:
                menu_bar = window.menuBar()
                menu_bar.clear()

        except:
            ...

        app.exec()

    def quit(self):
        app.quit()

    def update(self, remove_id, widget):
        replace_widget = WIDGET_ID_SAFE[remove_id]

        WidgetType, property = check_func(widget)

        try:
            layout.indexOf(replace_widget)
            temp_widget = LayItOut(GRAB_WIDGET, WidgetType, property)
            layout.replaceWidget(replace_widget, temp_widget)
            layout.removeWidget(replace_widget)
            replace_widget.deleteLater()
            replace_widget = None
        except:
            pass

    def normal(self):
        window.setWindowState(Qt.WindowNoState)

    def minimize(self):
        window.setWindowState(Qt.WindowMinimized)

    def maximize(self):
        window.setWindowState(Qt.WindowMaximized)


# ------------------------------------------------------------------------#

###########################################################################
############################## CORE HANDLERS ##############################
###########################################################################

# ------------------------------------------------------------------------#


def handle_group(widgets, grouplayout):
    groupcontent = QButtonGroup(grouplayout)
    for items in widgets:

        WidgetType, items = check_func(items)
        groupcontent.addButton(
            LayItOut(grouplayout, WidgetType, items))

    groupcontent.setExclusive(True)


def init_column(hbox, vbox, property):
    hbox.addLayout(vbox)

    for properties in property[1]:
        col_hbox = QHBoxLayout()
        vbox.addLayout(col_hbox)
        for property in properties:
            WidgetType, property = check_func(property)
            if WidgetType != ELEM_TYPE_COLUMN:
                LayItOut(col_hbox, WidgetType, property)
            else:
                hbox.addLayout(vbox)
                for properties in property[1]:
                    col_hbox = QHBoxLayout()
                    vbox.addLayout(col_hbox)
                    for property in properties:
                        WidgetType, property = check_func(property)
                        LayItOut(col_hbox, WidgetType, property)


def init_menubar(menu_items):
    if menu_items != None:
        for items in menu_items:
            if len(items) == 2:
                name, actions = items
            else:
                if type(items[0]) == type(str()):
                    name = items[0]
                    actions = None
                else:
                    name = None
                    actions = items[0]

            window.WindowMenubar(name, actions)


# ------------------------------------------------------------------------#

###########################################################################
############################# LAYOUT PROCESSOR ############################
###########################################################################

# ------------------------------------------------------------------------#


def Box(widgets):
    containers = [ELEM_TYPE_COLUMN, ELEM_TYPE_FIELDSET,
                  ELEM_TYPE_GROUP]
    box_layout = QVBoxLayout()
    box_widget = QWidget()

    if ELEM_TYPE_TAB not in get_parent() and BOX_IDENTIFIER not in get_parent() and ELEM_TYPE_SCROLL_AREA not in get_parent():
        box_widget = DEFAULT_WINDOW_LAYOUT
        box_layout = layout
        window.setCentralWidget(box_widget)

    box_widget.setLayout(box_layout)

    for items in widgets:
        vbox = QVBoxLayout()
        hbox = QHBoxLayout()
        box_layout.addLayout(hbox)

        for item in items:
            widget_type, widget_items = check_func(item)

            if widget_type not in containers:
                LayItOut(hbox, widget_type, widget_items)
            elif widget_type == ELEM_TYPE_COLUMN:
                init_column(hbox, vbox, widget_items)
            elif widget_type == ELEM_TYPE_FIELDSET:

                Notrequired, name, widgets, id, fieldset_layout = widget_items
                if widgets != None:

                    groupbox = INIT_WIDGET(id, QGroupBox(name))
                    hbox.addWidget(groupbox)

                    if fieldset_layout.lower() in DEFAULT_VERTICAL_TYPES:
                        groupbox_layout = QVBoxLayout()
                    elif fieldset_layout.lower() in DEFAULT_HORIZONTAL_TYPES:
                        groupbox_layout = QHBoxLayout()
                    else:
                        raise ValueError(fieldset_layout)

                    groupbox.setLayout(groupbox_layout)

                    for items in widgets:
                        widget_type, widget_items = check_func(items)
                        if widget_type == ELEM_TYPE_GROUP:

                            NotRequired, widgets, group_layout = items
                            if widgets != None:
                                if group_layout.lower() in DEFAULT_HORIZONTAL_TYPES:
                                    group_element_layout = QHBoxLayout()
                                elif group_layout.lower() in DEFAULT_VERTICAL_TYPES:
                                    group_element_layout = QVBoxLayout()
                                else:
                                    raise ValueError(group_layout)

                                groupbox_layout.addLayout(group_element_layout)
                                handle_group(widgets, group_element_layout)
                        else:
                            LayItOut(groupbox_layout,
                                     widget_type, widget_items)

            elif widget_type == ELEM_TYPE_GROUP:

                Notrequired, widgets, group_layout = widget_items

                if widgets != None:

                    if group_layout.lower() in DEFAULT_VERTICAL_TYPES:
                        group_element_layout = QVBoxLayout()
                    elif group_layout.lower() in DEFAULT_HORIZONTAL_TYPES:
                        group_element_layout = QHBoxLayout()
                    else:
                        raise ValueError(group_layout)
                    layout.addLayout(group_element_layout)
                    handle_group(widgets, group_element_layout)

    return [box_widget, box_layout]


def Grid(widgets):
    grid_widget = QWidget()
    grid = QGridLayout()

    if ELEM_TYPE_TAB not in get_parent() and GRID_IDENTIFIER not in get_parent():
        grid_widget = DEFAULT_WINDOW_LAYOUT
        grid_layout = layout

        window.setCentralWidget(grid_widget)
        grid_layout.addLayout(grid)

    grid_widget.setLayout(grid)

    grid_pos_x = 0
    grid_pos_y = 0

    for items in widgets:
        for item in items:
            widget_type, widget_items = check_func(item)
            LayItOut(grid, widget_type, widget_items, grid_pos_x, grid_pos_y)
            grid_pos_y += 1
        grid_pos_y = 0
        grid_pos_x += 1

    return grid_widget


class ScrollArea:
    def __init__(self, widgets: None = None, id: None = None, contain: bool = True):
        '''
        ScrollArea

        :param widgets:
        :param id:
        :param contain:
        '''
        elem_type = ELEM_TYPE_SCROLL_AREA
        widgets = widgets[0]

        self.scroll_widget = QWidget()
        scroll_layout = QVBoxLayout()

        scroll_area = INIT_WIDGET(id, QScrollArea())
        scroll_layout.addWidget(scroll_area)
        self.scroll_widget.setLayout(scroll_layout)

        scroll_area.setWidget(widgets)
        scroll_area.setAlignment(Qt.AlignBottom)
        scroll_area.setWidgetResizable(contain)

        if ELEM_TYPE_TAB not in get_parent() and SCROLL_AREA_IDENTIFIER not in get_parent():
            layout.addWidget(scroll_area)

    def __repr__(self):
        return [self.scroll_widget]

    def __call__(self):
        return [self.scroll_widget]


class scrollarea(ScrollArea):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)


# ------------------------------------------------------------------------#

###########################################################################
############################## CORE ELEMENTS ##############################
###########################################################################

# ------------------------------------------------------------------------#


class Menubar:
    def __init__(self, menu_items: list | None = None):
        '''
        Menu Bar

        :param menu_items:
        '''

        self.menu_items = menu_items

        init_menubar(self.menu_items)


class menubar(Menubar):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)


class Titlebar:
    def __init__(self, icon: str = None, title: str = path.basename(sys.argv[0]), widgets: list = None, alignment: None = None, text_color: str = 'white',
                 background_color: str = 'rgb(32, 32, 32)', height: any = CUSTOM_TITLEBAR_HEIGHT, button_style: None = DEFAULT_BUTTON_STYLE):
        '''
        Titlebar

        :param title:
        :param widgets:
        :param alignment:
        :param text_color:
        :param background_color:
        :param height:
        :param button_style:
        '''
        window.WindowTitlebar(icon, title, widgets, alignment,
                              background_color, text_color, height, button_style)


class titlebar(Titlebar):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)


class Toolbar:
    def __init__(self, name, tool_items: list | None = None, movable: bool = False,
                 align: str = 'TOP', id: None = None, iconsize: tuple = (None, None),
                 border: bool = True, orientation: str = SET_HORIZONTAL, newline: bool = False):
        '''
        Toolbar Element

        :param name:
        :param tool_items:
        :param movable:
        :param align:
        :param id:
        :param iconsize:
        :param border:
        :param orientation:
        :param newline:
        '''

        window.WindowToolbar(name,  tool_items, movable,  align,  id,  iconsize,  border,
                             orientation, newline)


class toolbar(Toolbar):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)


class Statusbar:
    def __init__(self):
        window.init_status()

    def message(self, text: str = '', time: None = None):
        '''
        Display temporary status message

        :param text:
        :param time: in seconds.
        '''
        window.SHOW_STATUSBAR_MESSAGE(text, time)

    def clear(self):
        window.CLEAR_STATUSBAR_MESSAGE()

    def add(self, widget, type: str = SET_NORMAL):
        type = type.upper()
        widget_type, widget_items = check_func(widget)
        if type == SET_NORMAL:
            addwidget = LayItOut(GRAB_WIDGET, widget_type, widget_items)
            window.ADD_NORMAL_WIDGET(addwidget)
        elif type == SET_STATIC:
            addwidget = LayItOut(GRAB_WIDGET, widget_type, widget_items)
            window.ADD_PERMANENT_WIDGET(addwidget)
        else:
            raise ValueError(f'"{type}" is an invalid type value')

    def remove(self, id):
        window.REMOVE_STATUSBAR_WIDGET(id)


class statusbar(Statusbar):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)


class File(QFileDialog):
    def __init__(self):
        ...

    def open(self: None = QFileDialog, caption: str = None, filter: str = '(All Files: *)', directory: str = None, type: str = DEFAULT_OPEN_FILE_TYPE):
        '''
        Open File Dialog

        :param caption:
        :param filter:
        :param directory:
        :param type:
        '''
        open_type = type.lower()

        if type == OPEN_FILE_TYPE_SINGLE:
            file = self.getOpenFileName(
                caption=caption,
                dir=directory,
                filter=get_filter(filter))

            # file_names = file[0]
            return file

        elif open_type == OPEN_FILE_TYPE_MULTI:
            files = self.getOpenFileNames(
                caption=caption,
                dir=directory,
                filter=get_filter(filter))

            return files

    def save(self: None = QFileDialog, filter: str = '(All Files: *)'):
        '''
        Save File Dialog

        :param filter:
        '''

        file = self.getSaveFileName(filter=get_filter(filter))

        return file


class file(File):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)


class Folder(QFileDialog):
    def __init__(self, caption: str = None, directory: str = None):
        '''
        Folder Dialog

        :param caption:
        :param directory:
        '''
        self.folder = self.getExistingDirectory(caption=caption,
                                                dir=directory)

    def __repr__(self):
        return self.folder


class folder(Folder):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)


class Popup:
    def __init__(self, title: None = None, widgets: None = None, size: tuple = (None, None), id: None = None,
                 modal: bool = False, frame: bool = True):
        '''
        Popup Window

        :param title:
        :param widgets:
        :param size:
        :param id:
        :param modal:
        :param frame:
        '''

        elem_type = ELEM_TYPE_POPUP
        popup = INIT_WIDGET(id, QDialog())
        self.popup = popup

        popup.setWindowTitle(title)

        if widgets != None:
            popup_layout = Box(widgets)

            popup.setLayout(popup_layout[1])

        popup.setModal(modal)

        width, height = size
        if width != None and height != None:
            popup.resize(int(width), int(height))

        # popup.resize(200, 120)

        if frame == False:
            popup.setWindowFlag(Qt.FramelessWindowHint)

        popup.exec()

        self.rtn = [elem_type, id, popup]

    def __call__(self):
        return self.rtn

    def result(self):
        return self.popup.result()


class popup(Popup):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)


class TabWidget:
    def __init__(self, tabs: None = None, id: None = None, movable: bool = False, closable: bool = False,
                 close_requested: None = None, clicked: None = None):
        '''
        Tab Widget

        :param tabs:
        :param id:
        :param movable:
        :param closable:
        :param close_requested:
        :param clicked:
        '''

        elem_type = ELEM_TYPE_TAB

        tab_widget = INIT_WIDGET(id, QTabWidget())

        if tabs != None:
            add_tabs(tab_widget, tabs)

        tab_widget.setMovable(movable)
        tab_widget.setTabsClosable(closable)

        tab_widget.tabCloseRequested.connect(close_requested)
        tab_widget.tabBarClicked.connect(clicked)

        layout.addWidget(tab_widget)
        window.setCentralWidget(DEFAULT_WINDOW_LAYOUT)

        self.rtn = [elem_type, id]

    def __call__(self):
        return self.rtn


class tabwidget(TabWidget):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)


class GET:
    def __init__(self, id):
        self.id = id
        self.widget = WIDGET_ID_SAFE[self.id]
        self.widget_type = str(type(self.widget))

    def __repr__(self):
        allowed = [
            [
                GET_ELEM_TYPE_PROGRESS_BAR,
                GET_ELEM_TYPE_SLIDER,
                GET_ELEM_TYPE_DIAL
            ],

            GET_ELEM_TYPE_TEXTAREA
        ]

        if process_request(allowed[0], self.widget_type) == True:
            return str(self.widget.value())
        elif process_request(allowed[1], self.widget_type):
            return self.widget.toPlainText()
        else:
            return self.widget.text()

    def value(self, value: str | None = None):
        allowed = [
            GET_ELEM_TYPE_PROGRESS_BAR,
            GET_ELEM_TYPE_SLIDER,
            GET_ELEM_TYPE_DIAL
        ]

        if process_request(allowed, self.widget_type) == True:
            self.widget.setValue(int(value))
        else:
            self.widget.setText(str(value))

    def update(self, widget):
        widget_type, widget_items = check_func(widget)

        try:
            layout.indexOf(self.widget)
            temp_widget = LayItOut(GRAB_WIDGET, widget_type, widget_items)
            layout.replaceWidget(self.widget, temp_widget)
            layout.removeWidget(self.widget)
            self.widget.deleteLater()
            self.widget = None
        except:
            ...

        # WIDGET_ID_SAFE.pop(self.id)

    def delete(self):
        layout.removeWidget(self.widget)
        self.widget.deleteLater()
        self.widget = None

    def append(self, value: str = ''):
        allowed = [
            GET_ELEM_TYPE_TEXTAREA
        ]

        if process_request(allowed, self.widget_type) == True:
            self.widget.append(value)

    def html(self, value):
        allowed = [
            GET_ELEM_TYPE_TEXTAREA
        ]

        if process_request(allowed, self.widget_type) == True:
            self.widget.setHtml(value)

    def insert_html(self, value):
        allowed = [
            GET_ELEM_TYPE_TEXTAREA
        ]

        if process_request(allowed, self.widget_type) == True:
            self.widget.insertHtml(value)

    def plain_text(self, value: None = None):
        allowed = [
            GET_ELEM_TYPE_TEXTAREA
        ]

        if process_request(allowed, self.widget_type) == True:
            if value != None:
                self.widget.setPlainText(value)
            else:
                return self.widget.toPlainText()

    def alignmnet(self, value: None = None):
        if value != None:
            make_alignment(self.widget, value)
        else:
            return self.widget.alignment()

    def is_default(self):
        return self.widget.isDefault()

    def is_readonly(self):
        return self.widget.isReadonly()

    def style(self, css: None = None):
        if css != None:
            css = get_style_check(css)  # check for css errors
            self.widget.setStyleSheet(css)

    def is_checked(self):
        return self.widget.isChecked()

    def hidden(self, value: bool | None = None):
        if value != None:
            self.widget.setHidden(value)

    def hide(self):
        self.widget.setHidden(True)

    def is_hidden(self):
        return self.widget.isHidden()

    def select_all(self):
        allowed = [
            GET_ELEM_TYPE_TEXTAREA
        ]

        if process_request(allowed, self.widget_type) == True:
            self.widget.selectAll()

    def copy(self):
        allowed = [
            GET_ELEM_TYPE_TEXTAREA
        ]

        if process_request(allowed, self.widget_type) == True:
            self.widget.copy()

    def cut(self):
        allowed = [
            GET_ELEM_TYPE_TEXTAREA
        ]

        if process_request(allowed, self.widget_type) == True:
            self.widget.cut()

    def undo(self):
        allowed = [
            GET_ELEM_TYPE_TEXTAREA
        ]

        if process_request(allowed, self.widget_type) == True:
            self.widget.undo()

    def redo(self):
        allowed = [
            GET_ELEM_TYPE_TEXTAREA
        ]

        if process_request(allowed, self.widget_type) == True:
            self.widget.redo()

    def paste(self):
        allowed = [
            GET_ELEM_TYPE_TEXTAREA
        ]

        if process_request(allowed, self.widget_type) == True:
            self.widget.paste()

    def clear(self):
        allowed = [
            GET_ELEM_TYPE_TEXTAREA,
            GET_ELEM_TYPE_LISTWIDGET,
            GET_ELEM_TYPE_SELECT,
            GET_ELEM_TYPE_TAB_WIDGET
        ]

        if process_request(allowed, self.widget_type) == True:
            self.widget.clear()

    def add(self, items):
        allowed = [
            GET_ELEM_TYPE_LISTWIDGET,
            GET_ELEM_TYPE_SELECT,
            GET_ELEM_TYPE_TAB_WIDGET
        ]

        if process_request(allowed[0], self.widget_type) == True:
            add_list_items(self.widget, items)
        elif process_request(allowed[1], self.widget_type) == True:
            if type(items[0]) == sample_list:
                pass
            elif type(items[0]) == sample_string:
                items = [items]
        elif process_request(allowed[2], self.widget_type) == True:
            add_tabs(self.widget, items)

    def remove(self, items):
        allowed = [
            GET_ELEM_TYPE_LISTWIDGET,
            GET_ELEM_TYPE_SELECT,
            GET_ELEM_TYPE_TAB_WIDGET
        ]

        if process_request(allowed[0], self.widget_type) == True:
            self.widget.takeItem(items)
        elif process_request(allowed[1], self.widget_type) == True:
            self.widget.removeItem(items)
        elif process_request(allowed[2], self.widget_type) == True:
            self.widget.removeTab(items)

    def current(self):
        allowed = [
            GET_ELEM_TYPE_LISTWIDGET,
            GET_ELEM_TYPE_SELECT,
            GET_ELEM_TYPE_TAB_WIDGET
        ]

        if process_request(allowed[0], self.widget_type) == True:
            return self.widget.currentRow()
        elif process_request(allowed[1], self.widget_type) == True:
            return self.widget.currentText()
        elif process_request(allowed[2], self.widget_type) == True:
            return self.widget.currentIndex()

    def count(self):
        allowed = [
            GET_ELEM_TYPE_LISTWIDGET,
            GET_ELEM_TYPE_SELECT,
            GET_ELEM_TYPE_TAB_WIDGET
        ]

        if process_request(allowed, self.widget_type) == True:
            return self.widget.count()

    def selected_items(self):
        allowed = [
            GET_ELEM_TYPE_LISTWIDGET,
        ]

        if process_request(allowed, self.widget_type) == True:
            selected_items = [items.text()
                              for items in self.widget.selectedItems()]
            return selected_items

    def set(self, value):
        allowed = [
            GET_ELEM_TYPE_SELECT,
        ]

        if process_request(allowed, self.widget_type) == True:
            self.widget.setCurrentIndex(value)

    def index(self):
        allowed = [
            GET_ELEM_TYPE_SELECT,
        ]

        if process_request(allowed, self.widget_type) == True:
            return self.widget.currentIndex()

    def reset(self):
        allowed = [
            GET_ELEM_TYPE_PROGRESS_BAR,
        ]

        if process_request(allowed, self.widget_type) == True:
            self.widget.reset()

    def minimum(self):
        allowed = [
            GET_ELEM_TYPE_PROGRESS_BAR,
        ]

        if process_request(allowed, self.widget_type) == True:
            return self.widget.minimum()

    def maximum(self):
        allowed = [
            GET_ELEM_TYPE_PROGRESS_BAR,
        ]

        if process_request(allowed, self.widget_type) == True:
            return self.widget.maximum()

    def is_text_visible(self):
        allowed = [
            GET_ELEM_TYPE_PROGRESS_BAR,
        ]

        if process_request(allowed, self.widget_type) == True:
            return self.widget.isTextVisible()

    def reject(self, result: int = None):
        allowed = [
            GET_ELEM_TYPE_POPUP,
        ]

        if process_request(allowed, self.widget_type) == True:
            self.widget.reject()
            if result != None:
                self.widget.setResult(result)

    def accept(self, result: int = None):
        allowed = [
            GET_ELEM_TYPE_POPUP,
        ]

        if process_request(allowed, self.widget_type) == True:
            self.widget.accept()
            if result != None:
                self.widget.setResult(result)


class get(GET):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)


# ------------------------------------------------------------------------#

###########################################################################
############################## EASE OF ACCESS #############################
###########################################################################

# ------------------------------------------------------------------------#


class Exit(Button):
    def __init__(self, label: str = 'Exit', icon: None = None, id: None = None,
                 disabled: bool = False, default: bool = False, grid: tuple = (None, None),
                 sizepolicy: tuple = (None, None), checkable: bool = False, checked: bool = False, hidden: bool = False, focus: bool = True):

        elem_type = ELEM_TYPE_BUTTON

        self.rtn = [elem_type, label, Window.quit, icon, id,
                    disabled, default, grid, sizepolicy, checkable, checked, hidden, focus]

    def __call__(self):
        return self.rtn


class exit(Exit):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)


class Copy(Button):
    def __init__(self, Target_ID, button_text: str = 'Copy', icon: None = None,
                 id: None = None, disabled: bool = False, default: bool = False,
                 grid: tuple = (None, None), sizePolicy: tuple = (None, None), checked: bool = False, hidden: bool = False, focus: bool = True):

        elem_type = elem_type, id = pre_widget_process('copy')
        METHOD_COPY.append(f'{id}:{Target_ID}')

        self.rtn = [elem_type, button_text, action_copy, icon, id,
                    disabled, default, grid, sizePolicy, True, checked, hidden, focus]

    def __call__(self):
        return self.rtn


class copy(Copy):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)


class Cut(Button):
    def __init__(self, Target_ID, button_text: str = 'Cut', icon: None = None,
                 id: None = None, disabled: bool = False, default: bool = False,
                 grid: tuple = (None, None), sizePolicy: tuple = (None, None), checked: bool = False, hidden: bool = False, focus: bool = True):

        elem_type, id = pre_widget_process('cut')
        METHOD_CUT.append(f'{id}:{Target_ID}')

        self.rtn = [elem_type, button_text, action_cut, icon, id,
                    disabled, default, grid, sizePolicy, True, checked, hidden, focus]

    def __call__(self):
        return self.rtn


class cut(Cut):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)


class Paste(Button):
    def __init__(self, Target_ID, button_text: str = 'Paste', icon: None = None,
                 id: None = None, disabled: bool = False, default: bool = False,
                 grid: tuple = (None, None), sizePolicy: tuple = (None, None), checked: bool = False, hidden: bool = False, focus: bool = True):

        elem_type, id = pre_widget_process('paste')
        METHOD_PASTE.append(f'{id}:{Target_ID}')

        self.rtn = [elem_type, button_text, action_paste, icon, id,
                    disabled, default, grid, sizePolicy, True, checked, hidden, focus]

    def __call__(self):
        return self.rtn


class paste(Paste):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)


class Undo(Button):
    def __init__(self, Target_ID, button_text: str = 'Undo', icon: None = None,
                 id: None = None, disabled: bool = False, default: bool = False,
                 grid: tuple = (None, None), sizePolicy: tuple = (None, None), checked: bool = False, hidden: bool = False, focus: bool = True):

        elem_type = elem_type, id = pre_widget_process('undo')
        METHOD_UNDO.append(f'{id}:{Target_ID}')

        self.rtn = [elem_type, button_text, action_undo, icon, id,
                    disabled, default, grid, sizePolicy, True, checked, hidden, focus]

    def __call__(self):
        return self.rtn


class undo(Undo):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)


class Redo(Button):
    def __init__(self, Target_ID, button_text: str = 'Redo', icon: None = None,
                 id: None = None, disabled: bool = False, default: bool = False,
                 grid: tuple = (None, None), sizePolicy: tuple = (None, None), checked: bool = False, hidden: bool = False, focus: bool = True):

        elem_type = elem_type, id = pre_widget_process('redo')
        METHOD_REDO.append(f'{id}:{Target_ID}')

        self.rtn = [elem_type, button_text, action_redo, icon, id,
                    disabled, default, grid, sizePolicy, True, checked, hidden, focus]

    def __call__(self):
        return self.rtn


class redo(Redo):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)


# ------------------------------------------------------------------------#

###########################################################################
############################## CONTROL BUTTONS ############################
###########################################################################

# ------------------------------------------------------------------------#


def minimize():
    return GET(MINIMIZE_BUTTON_ID)


def maximize():
    return GET(MAXIMIZE_BUTTON_ID)


def close():
    return GET(CLOSE_BUTTON_ID)


def title():
    return GET(TITLE_TEXT_ID)


# ------------------------------------------------------------------------#

###########################################################################
############################### MANUAL WINDOW #############################
###########################################################################

# ------------------------------------------------------------------------#


# def main():
#     window = Window()
#     Box([[Button('UNAVAILABLE')]])
#     window.run()
