from qtpy import QtCore as QC
from qtpy import QtWidgets as QW
from qtpy import QtGui as QG

from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusData

from hydrus.client import ClientGlobals as CG
from hydrus.client.gui import ClientGUIFunctions
from hydrus.client.gui import ClientGUIShortcuts
from hydrus.client.gui import QtPorting as QP

CHILD_POSITION_PADDING = 24
FUZZY_PADDING = 10

def GetSafePosition( position: QC.QPoint, frame_key ):
    
    if CG.client_controller.new_options.GetBoolean( 'disable_get_safe_position_test' ):
        
        return ( position, None )
        
    
    # some window managers size the windows just off screen to cut off borders
    # so choose a test position that's a little more lenient
    
    fuzzy_point = QC.QPoint( FUZZY_PADDING, FUZZY_PADDING )
    
    test_position = position + fuzzy_point
    
    # note that some version of Qt cannot figure this out, they just deliver None for a particular monitor!
    # https://github.com/hydrusnetwork/hydrus/issues/1511
    
    screen = QW.QApplication.screenAt( test_position )
    
    if screen is None:
        
        try:
            
            first_display = QW.QApplication.screens()[0]
            
            rescue_position = first_display.availableGeometry().topLeft() + fuzzy_point
            
            rescue_screen = QW.QApplication.screenAt( rescue_position )
            
            if rescue_screen == first_display:
                
                message = 'A window with frame key "{}" that wanted to display at "{}" was rescued from apparent off-screen to the new location at "{}".'.format( frame_key, position, rescue_position )
                
                return ( rescue_position, message )
                
            
        except Exception as e:
            
            # user is using IceMongo Linux, a Free Libre Open Source derivation of WeasleBlue Linux, with the iJ4 5-D inverted Window Managing system, which has a holographically virtualised desktop system
            
            HydrusData.PrintException( e )
            
        
        message = 'A window with frame key "{}" that wanted to display at "{}" could not be rescued from off-screen! Please let hydrus dev know!'.format( frame_key, position )
        
        return ( None, message )
        
    else:
        
        return ( position, None )
        
    
def GetSafeSize( tlw: QW.QWidget, min_size: QC.QSize, gravity ) -> QC.QSize:
    
    min_width = min_size.width()
    min_height = min_size.height()
    
    frame_padding = tlw.frameGeometry().size() - tlw.size()
    
    parent = tlw.parentWidget()
    
    if parent is None:
        
        width = min_width
        height = min_height
        
    else:
        
        parent_window = parent.window()
        
        # when we initialise, we might not have a frame yet because we haven't done show() yet
        # so borrow main gui's
        if frame_padding.isEmpty():
            
            main_gui = CG.client_controller.gui
            
            if main_gui is not None and QP.isValid( main_gui ) and not main_gui.isFullScreen():
                
                frame_padding = main_gui.frameGeometry().size() - main_gui.size()
                
            
        
        if parent_window.isFullScreen():
            
            parent_available_size = parent_window.size()
            
        else:
            
            parent_frame_size = parent_window.frameGeometry().size()
            
            parent_available_size = parent_frame_size - frame_padding
            
        
        parent_available_width = parent_available_size.width()
        parent_available_height = parent_available_size.height()
        
        ( width_gravity, height_gravity ) = gravity
        
        if width_gravity == -1:
            
            width = min_width
            
        else:
            
            max_width = parent_available_width - ( 2 * CHILD_POSITION_PADDING )
            
            width = int( width_gravity * max_width )
            
        
        if height_gravity == -1:
            
            height = min_height
            
        else:
            
            max_height = parent_available_height - ( 2 * CHILD_POSITION_PADDING )
            
            height = int( height_gravity * max_height )
            
        
    
    display_size = ClientGUIFunctions.GetDisplaySize( tlw )
    
    display_available_size = display_size - frame_padding
    
    width = min( display_available_size.width() - 2 * CHILD_POSITION_PADDING, width )
    height = min( display_available_size.height() - 2 * CHILD_POSITION_PADDING, height )
    
    return QC.QSize( width, height )
    
def ExpandTLWIfPossible( tlw: QW.QWidget, frame_key, desired_size_delta: QC.QSize ):
    
    new_options = CG.client_controller.new_options
    
    ( remember_size, remember_position, last_size, last_position, default_gravity, default_position, maximised, fullscreen ) = new_options.GetFrameLocation( frame_key )
    
    if not tlw.isMaximized() and not tlw.isFullScreen():
        
        current_size = tlw.size()
        
        current_width = current_size.width()
        current_height = current_size.height()
        
        desired_delta_width = desired_size_delta.width()
        desired_delta_height = desired_size_delta.height()
        
        desired_width = current_width
        
        if desired_delta_width > 0:
            
            desired_width = current_width + desired_delta_width + FUZZY_PADDING
            
        
        desired_height = current_height
        
        if desired_delta_height > 0:
            
            desired_height = current_height + desired_delta_height + FUZZY_PADDING
            
        
        desired_size = QC.QSize( desired_width, desired_height )
        
        new_size = GetSafeSize( tlw, desired_size, default_gravity )
        
        if new_size.width() > current_width or new_size.height() > current_height:
            
            tlw.resize( new_size )
            
            #tlw.setMinimumSize( tlw.sizeHint() )
            
            SlideOffScreenTLWUpAndLeft( tlw )
            
        
    
def SaveTLWSizeAndPosition( tlw: QW.QWidget, frame_key ):
    
    if tlw.isMinimized():
        
        return
        
    
    new_options = CG.client_controller.new_options
    
    ( remember_size, remember_position, last_size, last_position, default_gravity, default_position, maximised, fullscreen ) = new_options.GetFrameLocation( frame_key )
    
    if remember_size:
        
        maximised = tlw.isMaximized()
        fullscreen = tlw.isFullScreen()
        
    
    if not ( maximised or fullscreen ):
        
        ( safe_position, position_message ) = GetSafePosition( tlw.pos(), frame_key )
        
        if safe_position is not None:
            
            if remember_size:
                
                last_size = ( tlw.size().width(), tlw.size().height() )
                
            
            if remember_position:
                
                last_position = ( safe_position.x(), safe_position.y() )
                
            
        
    
    new_options.SetFrameLocation( frame_key, remember_size, remember_position, last_size, last_position, default_gravity, default_position, maximised, fullscreen )
    
def SetInitialTLWSizeAndPosition( tlw: QW.QWidget, frame_key ):
    
    new_options = CG.client_controller.new_options
    
    ( remember_size, remember_position, last_size, last_position, default_gravity, default_position, maximised, fullscreen ) = new_options.GetFrameLocation( frame_key )
    
    parent = tlw.parentWidget()
    
    if parent is None:
        
        parent_window = None
        
    else:
        
        parent_window = parent.window()
        
    
    if remember_size and last_size is not None:
        
        ( width, height ) = last_size
        
        new_size = QC.QSize( width, height )
        
    else:
        
        new_size = GetSafeSize( tlw, tlw.sizeHint(), default_gravity )
        
    
    tlw.resize( new_size )
    
    min_width = min( 240, new_size.width() )
    min_height = min( 240, new_size.height() )
    
    tlw.setMinimumSize( QC.QSize( min_width, min_height ) )
    
    #
    
    child_position_point = QC.QPoint( CHILD_POSITION_PADDING, CHILD_POSITION_PADDING )
    
    desired_position = child_position_point
    
    we_care_about_off_screen_messages = True
    slide_up_and_left = False
    
    if remember_position and last_position is not None:
        
        ( x, y ) = last_position
        
        desired_position = QC.QPoint( x, y )
        
    elif default_position == 'topleft':
        
        if parent_window is None:
            
            we_care_about_off_screen_messages = False
            
            screen = ClientGUIFunctions.GetMouseScreen()
            
            if screen is not None:
                
                desired_position = screen.availableGeometry().topLeft() + QC.QPoint( CHILD_POSITION_PADDING, CHILD_POSITION_PADDING )
                
            
        else:
            
            parent_tlw = parent_window.window()
            
            desired_position = parent_tlw.pos() + QC.QPoint( CHILD_POSITION_PADDING, CHILD_POSITION_PADDING )
            
        
        slide_up_and_left = True
        
    elif default_position == 'center':
        
        if parent_window is None:
            
            we_care_about_off_screen_messages = False
            
            screen = ClientGUIFunctions.GetMouseScreen()
            
            if screen is not None:
                
                desired_position = screen.availableGeometry().center()
                
            
        else:
            
            desired_position = parent_window.frameGeometry().center() - tlw.rect().center()
            
        
    
    ( safe_position, position_message ) = GetSafePosition( desired_position, frame_key )
    
    if we_care_about_off_screen_messages and position_message is not None:
        
        HydrusData.ShowText( position_message )
        
    
    if safe_position is not None:
        
        tlw.move( safe_position )
        
    
    if slide_up_and_left:
        
        SlideOffScreenTLWUpAndLeft( tlw )
        
    
    # Comment from before the Qt port: if these aren't callafter, the size and pos calls don't stick if a restore event happens
    
    if maximised:
        
        tlw.showMaximized()
        
    
    if fullscreen and not HC.PLATFORM_MACOS:
        
        tlw.showFullScreen()
        
    
def SlideOffScreenTLWUpAndLeft( tlw ):
    
    tlw_frame_rect = tlw.frameGeometry()
    
    tlw_top_left = tlw_frame_rect.topLeft()
    tlw_bottom_right = tlw_frame_rect.bottomRight()
    
    tlw_right = tlw_bottom_right.x()
    tlw_bottom = tlw_bottom_right.y()
    
    display_size = ClientGUIFunctions.GetDisplaySize( tlw )
    display_pos = ClientGUIFunctions.GetDisplayPosition( tlw )
    
    display_right = display_pos.x() + display_size.width() - CHILD_POSITION_PADDING
    display_bottom = display_pos.y() + display_size.height() - CHILD_POSITION_PADDING
    
    move_x = tlw_right > display_right
    move_y = tlw_bottom > display_bottom
    
    if move_x or move_y:
        
        delta_x = min( display_right - tlw_right, 0 )
        delta_y = min( display_bottom - tlw_bottom, 0 )
        
        delta_point = QC.QPoint( delta_x, delta_y )
        
        safe_pos = tlw_top_left + delta_point
        
        tlw.move( safe_pos )
        
    
class NewDialog( QP.Dialog ):
    
    def __init__( self, parent, title, do_not_activate = False ):
        
        QP.Dialog.__init__( self, parent )
        
        if do_not_activate:
            
            self.setAttribute( QC.Qt.WA_ShowWithoutActivating )
            
        
        self.setWindowTitle( title )
        
        self._new_options = CG.client_controller.new_options
        
        self.setWindowIcon( QG.QIcon( CG.client_controller.frame_icon_pixmap ) )
        
        CG.client_controller.ResetIdleTimer()
        
        self._widget_event_filter = QP.WidgetEventFilter( self )
        
    
    def _DoClose( self, value ):
        
        return
        
    
    def _SaveOKPosition( self ):
        
        pass
        
    
    def _TestValidityAndPresentVetoMessage( self, value ):
        
        return True
        
    
    def _TryEndModal( self, value ):
        
        if not self.isModal(): # in some rare cases (including spammy AutoHotkey, looks like), this can be fired before the dialog can clean itself up
            
            return False
            
        
        if not self._TestValidityAndPresentVetoMessage( value ):
            
            return False
            
        
        if not self._UserIsOKToClose( value ):
            
            return False
            
        
        if value == QW.QDialog.Rejected:
            
            self.SetCancelled( True )
            
        elif value == QW.QDialog.Accepted:
            
            self._SaveOKPosition()
            
        
        self._DoClose( value )
        
        self.CleanBeforeDestroy()
        
        try:
            
            self.done( value )
            
        except Exception as e:
            
            HydrusData.ShowText( 'This dialog seems to have been unable to close for some reason. I am printing the stack to the log. The dialog may have already closed, or may attempt to close now. Please inform hydrus dev of this situation. I recommend you restart the client if you can. If the UI is locked, you will have to kill it via task manager.' )
            
            HydrusData.PrintException( e )
            
            import traceback
            
            HydrusData.DebugPrint( ''.join( traceback.format_stack() ) )
            
            try:
                
                self.close()
                
            except:
                
                HydrusData.ShowText( 'The dialog would not close on command.' )
                
            
            try:
                
                self.deleteLater()
                
            except:
                
                HydrusData.ShowText( 'The dialog would not destroy on command.' )
                
            
        
        return True
        
    
    def _UserIsOKToClose( self, value ):
        
        return True
        
    
    def CleanBeforeDestroy( self ):
        
        pass
        
    
    def DoOK( self ):
        
        self._TryEndModal( QW.QDialog.Accepted )
        
    
    def closeEvent( self, event ):
        
        if not self or not QP.isValid( self ):
            
            return
            
        
        was_ended = self._TryEndModal( QW.QDialog.Rejected )
        
        if was_ended:
            
            event.accept()
            
        else:
            
            event.ignore()
            
        
    
    def EventDialogButtonApply( self ):
        
        if not self or not QP.isValid( self ):
            
            return
            
        
        event_object = self.sender()
        
        if event_object is not None:
            
            tlw = event_object.window()
            
            if tlw != self:
                
                return
                
            
        
        self._TryEndModal( QW.QDialog.Accepted )
        

    def EventDialogButtonCancel( self ):

        if not self or not QP.isValid( self ):
            
            return
            

        event_object = self.sender()

        if event_object is not None:

            tlw = event_object.window()

            if tlw != self:
                
                return
                
            

        self._TryEndModal( QW.QDialog.Rejected )
        
    
    def keyPressEvent( self, event ):
        
        current_focus = QW.QApplication.focusWidget()
        
        event_from_us = current_focus is not None and ClientGUIFunctions.IsQtAncestor( current_focus, self )
        
        shortcut = ClientGUIShortcuts.ConvertKeyEventToShortcut( event )
        
        escape_shortcut = ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_ESCAPE, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] )
        command_w_shortcut = ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'W' ), ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [ ClientGUIShortcuts.SHORTCUT_MODIFIER_CTRL ] )
        
        if event_from_us and ( shortcut == escape_shortcut or ( HC.PLATFORM_MACOS and shortcut == command_w_shortcut ) ):
            
            self._TryEndModal( QW.QDialog.Rejected )
            
        else:
            
            QP.Dialog.keyPressEvent( self, event )
            
        
    
class DialogThatResizes( NewDialog ):
    
    def __init__( self, parent, title, frame_key, do_not_activate = False ):
        
        self._frame_key = frame_key
        
        NewDialog.__init__( self, parent, title, do_not_activate = do_not_activate )
        
    
    def _SaveOKPosition( self ):
        
        SaveTLWSizeAndPosition( self, self._frame_key )
        
    
class Frame( QW.QWidget ):
    
    def __init__( self, parent, title ):
        
        QW.QWidget.__init__( self, parent )
        
        self.setWindowTitle( title )
        
        self.setWindowFlags( QC.Qt.Window )
        self.setWindowFlag( QC.Qt.WindowContextHelpButtonHint, on = False )
        
        self.setAttribute( QC.Qt.WA_DeleteOnClose )
        
        self._new_options = CG.client_controller.new_options
        
        self.setWindowIcon( QG.QIcon( CG.client_controller.frame_icon_pixmap ) )
        
        CG.client_controller.ResetIdleTimer()
        
        self._widget_event_filter = QP.WidgetEventFilter( self )
        
    
    def CleanBeforeDestroy( self ):
        
        pass
        
    
    def closeEvent( self, event ):
        
        self.CleanBeforeDestroy()
        
    
class MainFrame( QW.QMainWindow ):
    
    def __init__( self, parent, title ):
        
        QW.QMainWindow.__init__( self, parent )
        
        self.setWindowTitle( title )
        
        self._new_options = CG.client_controller.new_options
        
        self.setWindowIcon( QG.QIcon( CG.client_controller.frame_icon_pixmap ) )
        
        self._widget_event_filter = QP.WidgetEventFilter( self )
        
        CG.client_controller.ResetIdleTimer()
        
    
    def CleanBeforeDestroy( self ):
        
        pass
        
    
    def closeEvent( self, event ):
        
        self.CleanBeforeDestroy()
        
    
class FrameThatResizes( Frame ):
    
    def __init__( self, parent, title, frame_key ):
        
        self._frame_key = frame_key
        
        Frame.__init__( self, parent, title )
        
        self._widget_event_filter.EVT_SIZE( self.EventSizeAndPositionChanged )
        self._widget_event_filter.EVT_MOVE_END( self.EventSizeAndPositionChanged )
        self._widget_event_filter.EVT_MAXIMIZE( self.EventSizeAndPositionChanged )
        
    
    def CleanBeforeDestroy( self ):
        
        MainFrame.CleanBeforeDestroy( self )
        
        # maximise sends a pre-maximise size event that poisons last_size if this is immediate
        CG.client_controller.CallLaterQtSafe( self, 0.1, 'save frame size and position: {}'.format( self._frame_key ), SaveTLWSizeAndPosition, self, self._frame_key )
        
    
    def EventSizeAndPositionChanged( self, event ):
        
        # maximise sends a pre-maximise size event that poisons last_size if this is immediate
        CG.client_controller.CallLaterQtSafe( self, 0.1, 'save frame size and position: {}'.format( self._frame_key ), SaveTLWSizeAndPosition, self, self._frame_key )
        
        return True # was: event.ignore()
        
    
class FrameThatResizesWithHovers( FrameThatResizes ): pass

class MainFrameThatResizes( MainFrame ):

    def __init__( self, parent, title, frame_key ):
        
        self._frame_key = frame_key

        MainFrame.__init__( self, parent, title )

        self._widget_event_filter.EVT_SIZE( self.EventSizeAndPositionChanged )
        self._widget_event_filter.EVT_MOVE_END( self.EventSizeAndPositionChanged )
        self._widget_event_filter.EVT_MAXIMIZE( self.EventSizeAndPositionChanged )
        

    def CleanBeforeDestroy( self ):
        
        MainFrame.CleanBeforeDestroy( self )
        
        # maximise sends a pre-maximise size event that poisons last_size if this is immediate
        CG.client_controller.CallLaterQtSafe( self, 0.1, 'save frame size and position: {}'.format( self._frame_key ), SaveTLWSizeAndPosition, self, self._frame_key )
        
    
    def EventSizeAndPositionChanged( self, event ):
        
        # maximise sends a pre-maximise size event that poisons last_size if this is immediate
        CG.client_controller.CallLaterQtSafe( self, 0.1, 'save frame size and position: {}'.format( self._frame_key ), SaveTLWSizeAndPosition, self, self._frame_key )

        return True # was: event.ignore()
        
    
