import os
import typing

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

from hydrus.core import HydrusText

from hydrus.client import ClientConstants as CC
from hydrus.client import ClientParsing
from hydrus.client import ClientStrings
from hydrus.client.gui import ClientGUIDialogs
from hydrus.client.gui import ClientGUIScrolledPanels
from hydrus.client.gui import ClientGUIStringPanels
from hydrus.client.gui import ClientGUITopLevelWindowsPanels
from hydrus.client.gui import QtPorting as QP
from hydrus.client.gui.lists import ClientGUIListConstants as CGLC
from hydrus.client.gui.lists import ClientGUIListCtrl
from hydrus.client.gui.widgets import ClientGUICommon

class StringConverterButton( ClientGUICommon.BetterButton ):
    
    valueChanged = QC.Signal()
    
    def __init__( self, parent, string_converter: ClientStrings.StringConverter ):
        
        ClientGUICommon.BetterButton.__init__( self, parent, 'edit string converter', self._Edit )
        
        self._string_converter = string_converter
        
        self._example_string_override = None
        
        self._UpdateLabel()
        
    
    def _Edit( self ):
        
        with ClientGUITopLevelWindowsPanels.DialogEdit( self, 'edit string converter', frame_key = 'deeply_nested_dialog' ) as dlg:
            
            panel = ClientGUIStringPanels.EditStringConverterPanel( dlg, self._string_converter, example_string_override = self._example_string_override )
            
            dlg.SetPanel( panel )
            
            if dlg.exec() == QW.QDialog.Accepted:
                
                self._string_converter = panel.GetValue()
                
                self._UpdateLabel()
                
                self.valueChanged.emit()
                
            
        
    
    def _UpdateLabel( self ):
        
        label = self._string_converter.ToString()
        
        self.setToolTip( label )
        
        elided_label = HydrusText.ElideText( label, 64 )
        
        self.setText( elided_label )
        
    
    def GetValue( self ) -> ClientStrings.StringConverter:
        
        return self._string_converter
        
    
    def SetExampleString( self, example_string: str ):
        
        self._example_string_override = example_string
        
    
    def SetValue( self, string_converter: ClientStrings.StringConverter ):
        
        self._string_converter = string_converter
        
        self._UpdateLabel()
        
        self.valueChanged.emit()
        
    
class StringMatchButton( ClientGUICommon.BetterButton ):
    
    valueChanged = QC.Signal()
    
    def __init__( self, parent, string_match: ClientStrings.StringMatch ):
        
        ClientGUICommon.BetterButton.__init__( self, parent, 'edit string match', self._Edit )
        
        self._string_match = string_match
        
        self._UpdateLabel()
        
    
    def _Edit( self ):
        
        with ClientGUITopLevelWindowsPanels.DialogEdit( self, 'edit string match', frame_key = 'deeply_nested_dialog' ) as dlg:
            
            panel = ClientGUIStringPanels.EditStringMatchPanel( dlg, self._string_match )
            
            dlg.SetPanel( panel )
            
            if dlg.exec() == QW.QDialog.Accepted:
                
                self._string_match = panel.GetValue()
                
                self._UpdateLabel()
                
                self.valueChanged.emit()
                
            
        
    
    def _UpdateLabel( self ):
        
        label = self._string_match.ToString()
        
        self.setText( label )
        
    
    def GetValue( self ) -> ClientStrings.StringMatch:
        
        return self._string_match
        
    
    def SetValue( self, string_match: ClientStrings.StringMatch ):
        
        self._string_match = string_match
        
        self._UpdateLabel()
        
    
class StringProcessorButton( ClientGUICommon.BetterButton ):
    
    def __init__( self, parent, string_processor: ClientStrings.StringProcessor, test_data_callable: typing.Callable[ [], ClientParsing.ParsingTestData ] ):
        
        ClientGUICommon.BetterButton.__init__( self, parent, 'edit string processor', self._Edit )
        
        self._string_processor = string_processor
        self._test_data_callable = test_data_callable
        
        self._UpdateLabel()
        
    
    def _Edit( self ):
        
        test_data = self._test_data_callable()
        
        with ClientGUITopLevelWindowsPanels.DialogEdit( self, 'edit string processor', frame_key = 'deeply_nested_dialog' ) as dlg:
            
            panel = ClientGUIStringPanels.EditStringProcessorPanel( dlg, self._string_processor, test_data )
            
            dlg.SetPanel( panel )
            
            if dlg.exec() == QW.QDialog.Accepted:
                
                self._string_processor = panel.GetValue()
                
                self._UpdateLabel()
                
            
        
    
    def _UpdateLabel( self ):
        
        statements = self._string_processor.GetProcessingStrings()
        
        if len( statements ) == 0:
            
            label = self._string_processor.ToString()
            
        else:
            
            statements = [ HydrusText.ElideText( statement, 64 ) for statement in statements ]
            
            label = os.linesep.join( statements )
            
        
        self.setText( label )
        
    
    def GetValue( self ) -> ClientStrings.StringProcessor:
        
        return self._string_processor
        
    
    def SetValue( self, string_processor: ClientStrings.StringProcessor ):
        
        self._string_processor = string_processor
        
        self._UpdateLabel()
        
    
class StringMatchToStringMatchDictControl( QW.QWidget ):
    
    def __init__( self, parent, initial_dict: typing.Dict[ ClientStrings.StringMatch, ClientStrings.StringMatch ], min_height = 10, key_name = 'key' ):
        
        QW.QWidget.__init__( self, parent )
        
        self._key_name = key_name
        
        listctrl_panel = ClientGUIListCtrl.BetterListCtrlPanel( self )
        
        column_types_to_name_overrides = { CGLC.COLUMN_LIST_KEY_TO_STRING_MATCH.KEY : self._key_name }
        
        self._listctrl = ClientGUIListCtrl.BetterListCtrl( listctrl_panel, CGLC.COLUMN_LIST_KEY_TO_STRING_MATCH.ID, min_height, self._ConvertDataToListCtrlTuples, use_simple_delete = True, activation_callback = self._Edit, column_types_to_name_overrides = column_types_to_name_overrides )
        
        listctrl_panel.SetListCtrl( self._listctrl )
        
        listctrl_panel.AddButton( 'add', self._Add )
        listctrl_panel.AddButton( 'edit', self._Edit, enabled_only_on_selection = True )
        listctrl_panel.AddDeleteButton()
        
        #
        
        self._listctrl.AddDatas( list(initial_dict.items()) )
        
        self._listctrl.Sort()
        
        #
        
        vbox = QP.VBoxLayout()
        
        QP.AddToLayout( vbox, listctrl_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
        
        self.setLayout( vbox )
        
    
    def _ConvertDataToListCtrlTuples( self, data ):
        
        ( key_string_match, value_string_match ) = data
        
        pretty_key = key_string_match.ToString()
        pretty_value = value_string_match.ToString()
        
        display_tuple = ( pretty_key, pretty_value )
        sort_tuple = ( pretty_key, pretty_value )
        
        return ( display_tuple, sort_tuple )
        
    
    def _Add( self ):
        
        with ClientGUITopLevelWindowsPanels.DialogEdit( self, 'edit ' + self._key_name ) as dlg:
            
            string_match = ClientStrings.StringMatch()
            
            panel = ClientGUIStringPanels.EditStringMatchPanel( dlg, string_match )
            
            dlg.SetPanel( panel )
            
            if dlg.exec() == QW.QDialog.Accepted:
                
                key_string_match = panel.GetValue()
                
            else:
                
                return
                
            
        
        with ClientGUITopLevelWindowsPanels.DialogEdit( self, 'edit match' ) as dlg:
            
            string_match = ClientStrings.StringMatch()
            
            panel = ClientGUIStringPanels.EditStringMatchPanel( dlg, string_match )
            
            dlg.SetPanel( panel )
            
            if dlg.exec() == QW.QDialog.Accepted:
                
                value_string_match = panel.GetValue()
                
                data = ( key_string_match, value_string_match )
                
                self._listctrl.AddDatas( ( data, ) )
                
            
        
    
    def _Edit( self ):
        
        for data in self._listctrl.GetData( only_selected = True ):
            
            ( key_string_match, value_string_match ) = data
            
            with ClientGUITopLevelWindowsPanels.DialogEdit( self, 'edit ' + self._key_name ) as dlg:
                
                panel = ClientGUIStringPanels.EditStringMatchPanel( dlg, key_string_match )
                
                dlg.SetPanel( panel )
                
                if dlg.exec() == QW.QDialog.Accepted:
                    
                    key_string_match = panel.GetValue()
                    
                else:
                    
                    break
                    
                
            
            with ClientGUITopLevelWindowsPanels.DialogEdit( self, 'edit match' ) as dlg:
                
                panel = ClientGUIStringPanels.EditStringMatchPanel( dlg, value_string_match )
                
                dlg.SetPanel( panel )
                
                if dlg.exec() == QW.QDialog.Accepted:
                    
                    value_string_match = panel.GetValue()
                    
                else:
                    
                    break
                    
                
            
            self._listctrl.DeleteDatas( ( data, ) )
            
            edited_data = ( key_string_match, value_string_match )
            
            self._listctrl.AddDatas( ( edited_data, ) )
            
        
        self._listctrl.Sort()
        
    
    def GetValue( self ) -> typing.Dict[ str, ClientStrings.StringMatch ]:
        
        value_dict = dict( self._listctrl.GetData() )
        
        return value_dict
        
    
class StringToStringDictButton( ClientGUICommon.BetterButton ):
    
    def __init__( self, parent, label ):
        
        ClientGUICommon.BetterButton.__init__( self, parent, label, self._Edit )
        
        self._value = {}
        
    
    def _Edit( self ):
        
        with ClientGUITopLevelWindowsPanels.DialogEdit( self, 'edit string dictionary' ) as dlg:
            
            panel = ClientGUIScrolledPanels.EditSingleCtrlPanel( dlg )
            
            control = StringToStringDictControl( panel, self._value )
            
            panel.SetControl( control )
            
            dlg.SetPanel( panel )
            
            if dlg.exec() == QW.QDialog.Accepted:
                
                self._value = control.GetValue()
                
            
        
    
    def GetValue( self ) -> typing.Dict[ str, str ]:
        
        return self._value
        
    
    def SetValue( self, value ):
        
        self._value = value
        
    
class StringToStringDictControl( QW.QWidget ):
    
    columnListContentsChanged = QC.Signal()
    
    def __init__( self, parent, initial_dict: typing.Dict[ str, str ], min_height = 10, key_name = 'key', value_name = 'value', allow_add_delete = True, edit_keys = True ):
        
        QW.QWidget.__init__( self, parent )
        
        self._key_name = key_name
        self._value_name = value_name
        
        self._edit_keys = edit_keys
        
        listctrl_panel = ClientGUIListCtrl.BetterListCtrlPanel( self )
        
        use_simple_delete = allow_add_delete
        
        column_types_to_name_overrides = { CGLC.COLUMN_LIST_KEY_TO_VALUE.KEY : self._key_name }
        
        self._listctrl = ClientGUIListCtrl.BetterListCtrl( listctrl_panel, CGLC.COLUMN_LIST_KEY_TO_VALUE.ID, min_height, self._ConvertDataToListCtrlTuples, use_simple_delete = use_simple_delete, activation_callback = self._Edit, column_types_to_name_overrides = column_types_to_name_overrides )
        self._listctrl.columnListContentsChanged.connect( self.columnListContentsChanged )
        
        listctrl_panel.SetListCtrl( self._listctrl )
        
        if allow_add_delete:
            
            listctrl_panel.AddButton( 'add', self._Add )
            
        
        listctrl_panel.AddButton( 'edit', self._Edit, enabled_only_on_selection = True )
        
        if allow_add_delete:
            
            listctrl_panel.AddDeleteButton()
            
        
        #
        
        self._listctrl.AddDatas( list(initial_dict.items()) )
        
        self._listctrl.Sort()
        
        #
        
        vbox = QP.VBoxLayout()
        
        QP.AddToLayout( vbox, listctrl_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
        
        self.setLayout( vbox )
        
    
    def _ConvertDataToListCtrlTuples( self, data ):
        
        ( key, value ) = data
        
        display_tuple = ( key, value )
        sort_tuple = ( key, value )
        
        return ( display_tuple, sort_tuple )
        
    
    def _Add( self ):
        
        with ClientGUIDialogs.DialogTextEntry( self, 'enter the ' + self._key_name, allow_blank = False ) as dlg:
            
            if dlg.exec() == QW.QDialog.Accepted:
                
                key = dlg.GetValue()
                
                if key in self._GetExistingKeys():
                    
                    QW.QMessageBox.warning( self, 'Warning', 'That {} already exists!'.format( self._key_name ) )
                    
                    return
                    
                
                with ClientGUIDialogs.DialogTextEntry( self, 'enter the ' + self._value_name, allow_blank = True ) as dlg:
                    
                    if dlg.exec() == QW.QDialog.Accepted:
                        
                        value = dlg.GetValue()
                        
                        data = ( key, value )
                        
                        self._listctrl.AddDatas( ( data, ) )
                        
                    
                
            
        
    
    def _Edit( self ):
        
        for data in self._listctrl.GetData( only_selected = True ):
            
            ( key, value ) = data
            
            if self._edit_keys:
                
                with ClientGUIDialogs.DialogTextEntry( self, 'edit the ' + self._key_name, default = key, allow_blank = False ) as dlg:
                    
                    if dlg.exec() == QW.QDialog.Accepted:
                        
                        edited_key = dlg.GetValue()
                        
                        if edited_key != key and edited_key in self._GetExistingKeys():
                            
                            QW.QMessageBox.warning( self, 'Warning', 'That {} already exists!'.format( self._key_name ) )
                            
                            break
                            
                        
                    else:
                        
                        break
                        
                    
                
            else:
                
                edited_key = key
                
            
            with ClientGUIDialogs.DialogTextEntry( self, 'edit the ' + self._value_name, default = value, allow_blank = True ) as dlg:
                
                if dlg.exec() == QW.QDialog.Accepted:
                    
                    edited_value = dlg.GetValue()
                    
                else:
                    
                    break
                    
                
            
            self._listctrl.DeleteDatas( ( data, ) )
            
            edited_data = ( edited_key, edited_value )
            
            self._listctrl.AddDatas( ( edited_data, ) )
            
        
        self._listctrl.Sort()
        
    
    def _GetExistingKeys( self ):
        
        return { key for ( key, value ) in self._listctrl.GetData() }
        
    
    def GetValue( self ) -> typing.Dict[ str, str ]:
        
        value_dict = dict( self._listctrl.GetData() )
        
        return value_dict
        
    
class StringToStringMatchDictControl( QW.QWidget ):
    
    def __init__( self, parent, initial_dict: typing.Dict[ str, ClientStrings.StringMatch ], min_height = 10, key_name = 'key' ):
        
        QW.QWidget.__init__( self, parent )
        
        self._key_name = key_name
        
        listctrl_panel = ClientGUIListCtrl.BetterListCtrlPanel( self )
        
        column_types_to_name_overrides = { CGLC.COLUMN_LIST_KEY_TO_STRING_MATCH.KEY : self._key_name }
        
        self._listctrl = ClientGUIListCtrl.BetterListCtrl( listctrl_panel, CGLC.COLUMN_LIST_KEY_TO_STRING_MATCH.ID, min_height, self._ConvertDataToListCtrlTuples, use_simple_delete = True, activation_callback = self._Edit, column_types_to_name_overrides = column_types_to_name_overrides )
        
        listctrl_panel.SetListCtrl( self._listctrl )
        
        listctrl_panel.AddButton( 'add', self._Add )
        listctrl_panel.AddButton( 'edit', self._Edit, enabled_only_on_selection = True )
        listctrl_panel.AddDeleteButton()
        
        #
        
        self._listctrl.AddDatas( initial_dict.items() )
        
        self._listctrl.Sort()
        
        #
        
        vbox = QP.VBoxLayout()
        
        QP.AddToLayout( vbox, listctrl_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
        
        self.setLayout( vbox )
        
    
    def _ConvertDataToListCtrlTuples( self, data ):
        
        ( key, string_match ) = data
        
        pretty_string_match = string_match.ToString()
        
        display_tuple = ( key, pretty_string_match )
        sort_tuple = ( key, pretty_string_match )
        
        return ( display_tuple, sort_tuple )
        
    
    def _Add( self ):
        
        with ClientGUIDialogs.DialogTextEntry( self, 'enter the ' + self._key_name, allow_blank = False ) as dlg:
            
            if dlg.exec() == QW.QDialog.Accepted:
                
                key = dlg.GetValue()
                
                if key in self._GetExistingKeys():
                    
                    QW.QMessageBox.warning( self, 'Warning', 'That {} already exists!'.format( self._key_name ) )
                    
                    return
                    
                
                with ClientGUITopLevelWindowsPanels.DialogEdit( self, 'edit match' ) as dlg:
                    
                    string_match = ClientStrings.StringMatch()
                    
                    panel = ClientGUIStringPanels.EditStringMatchPanel( dlg, string_match )
                    
                    dlg.SetPanel( panel )
                    
                    if dlg.exec() == QW.QDialog.Accepted:
                        
                        string_match = panel.GetValue()
                        
                        data = ( key, string_match )
                        
                        self._listctrl.AddDatas( ( data, ) )
                        
                    
                
            
        
    
    def _Edit( self ):
        
        for data in self._listctrl.GetData( only_selected = True ):
            
            ( key, string_match ) = data
            
            with ClientGUIDialogs.DialogTextEntry( self, 'edit the ' + self._key_name, default = key, allow_blank = False ) as dlg:
                
                if dlg.exec() == QW.QDialog.Accepted:
                    
                    edited_key = dlg.GetValue()
                    
                    if edited_key != key and edited_key in self._GetExistingKeys():
                        
                        QW.QMessageBox.warning( self, 'Warning', 'That {} already exists!'.format( self._key_name ) )
                        
                        break
                        
                    
                else:
                    
                    break
                    
                
            
            with ClientGUITopLevelWindowsPanels.DialogEdit( self, 'edit match' ) as dlg:
                
                string_match = ClientStrings.StringMatch()
                
                panel = ClientGUIStringPanels.EditStringMatchPanel( dlg, string_match )
                
                dlg.SetPanel( panel )
                
                if dlg.exec() == QW.QDialog.Accepted:
                    
                    edited_string_match = panel.GetValue()
                    
                else:
                    
                    break
                    
                
            
            self._listctrl.DeleteDatas( ( data, ) )
            
            edited_data = ( edited_key, edited_string_match )
            
            self._listctrl.AddDatas( ( edited_data, ) )
            
        
        self._listctrl.Sort()
        
    
    def _GetExistingKeys( self ):
        
        return { key for ( key, value ) in self._listctrl.GetData() }
        
    
    def GetValue( self ) -> typing.Dict[ str, ClientStrings.StringMatch ]:
        
        value_dict = dict( self._listctrl.GetData() )
        
        return value_dict
        
    

