import HydrusConstants as HC
import ClientConstants as CC
import ClientData
import ClientGUICommon
import ClientGUIDialogs
import ClientGUIManagement
import ClientGUIMedia
import ClientGUIMenus
import ClientGUICanvas
import ClientDownloading
import ClientSearch
import ClientThreading
import hashlib
import HydrusData
import HydrusExceptions
import HydrusSerialisable
import HydrusThreading
import inspect
import os
import sys
import time
import traceback
import wx
import HydrusGlobals as HG

USER_PAGE_NAME_PREFIX = '[USER]'

class DialogPageChooser( ClientGUIDialogs.Dialog ):

    def __init__( self, parent, controller ):

        ClientGUIDialogs.Dialog.__init__( self, parent, 'new page', position = 'center' )

        self._controller = controller

        self._result = None

        # spawn in this order, so focus precipitates from the graphical top

        self._button_7 = wx.Button( self, label = '', id = 7 )
        self._button_8 = wx.Button( self, label = '', id = 8 )
        self._button_9 = wx.Button( self, label = '', id = 9 )
        self._button_4 = wx.Button( self, label = '', id = 4 )
        self._button_5 = wx.Button( self, label = '', id = 5 )
        self._button_6 = wx.Button( self, label = '', id = 6 )
        self._button_1 = wx.Button( self, label = '', id = 1 )
        self._button_2 = wx.Button( self, label = '', id = 2 )
        self._button_3 = wx.Button( self, label = '', id = 3 )

        gridbox = wx.GridSizer( 3 )

        gridbox.Add( self._button_7, CC.FLAGS_EXPAND_BOTH_WAYS )
        gridbox.Add( self._button_8, CC.FLAGS_EXPAND_BOTH_WAYS )
        gridbox.Add( self._button_9, CC.FLAGS_EXPAND_BOTH_WAYS )
        gridbox.Add( self._button_4, CC.FLAGS_EXPAND_BOTH_WAYS )
        gridbox.Add( self._button_5, CC.FLAGS_EXPAND_BOTH_WAYS )
        gridbox.Add( self._button_6, CC.FLAGS_EXPAND_BOTH_WAYS )
        gridbox.Add( self._button_1, CC.FLAGS_EXPAND_BOTH_WAYS )
        gridbox.Add( self._button_2, CC.FLAGS_EXPAND_BOTH_WAYS )
        gridbox.Add( self._button_3, CC.FLAGS_EXPAND_BOTH_WAYS )

        self.SetSizer( gridbox )

        self.SetInitialSize( ( 420, 210 ) )

        self._services = HG.client_controller.services_manager.GetServices()

        repository_petition_permissions = [ ( content_type, HC.PERMISSION_ACTION_OVERRULE ) for content_type in HC.REPOSITORY_CONTENT_TYPES ]

        self._petition_service_keys = [ service.GetServiceKey() for service in self._services if service.GetServiceType() in HC.REPOSITORIES and True in ( service.HasPermission( content_type, action ) for ( content_type, action ) in repository_petition_permissions ) ]

        self._InitButtons( 'home' )

        self.Bind( wx.EVT_CHAR_HOOK, self.EventCharHook )
        self.Bind( wx.EVT_BUTTON, self.EventButton )

        #

        self.Show( True )

    def _AddEntry( self, button, entry ):

        id = button.GetId()

        self._command_dict[ id ] = entry

        ( entry_type, obj ) = entry

        if entry_type == 'menu':

            button.SetLabelText( obj )

        elif entry_type == 'page_duplicate_filter':

            button.SetLabelText( 'duplicates processing' )

        elif entry_type == 'pages_notebook':

            button.SetLabelText( 'page of pages' )

        elif entry_type in ( 'page_query', 'page_petitions' ):

            name = HG.client_controller.services_manager.GetService( obj ).GetName()

            button.SetLabelText( name )

        elif entry_type == 'page_import_booru':

            button.SetLabelText( 'booru' )

        elif entry_type == 'page_import_gallery':

            site_type = obj

            text = HC.site_type_string_lookup[ site_type ]

            button.SetLabelText( text )

        elif entry_type == 'page_import_page_of_images':

            button.SetLabelText( 'page of images' )

        elif entry_type == 'page_import_thread_watcher':

            button.SetLabelText( 'thread watcher' )

        elif entry_type == 'page_import_urls':

            button.SetLabelText( 'raw urls' )

        button.Show()

    def _HitButton( self, id ):

        if id in self._command_dict:

            ( entry_type, obj ) = self._command_dict[ id ]

            if entry_type == 'menu':

                self._InitButtons( obj )

            else:

                if entry_type == 'page_query':

                    file_service_key = obj

                    page_name = 'files'

                    search_enabled = True

                    new_options = self._controller.new_options

                    tag_service_key = new_options.GetKey( 'default_tag_service_search_page' )

                    if not self._controller.services_manager.ServiceExists( tag_service_key ):

                        tag_service_key = CC.COMBINED_TAG_SERVICE_KEY

                    file_search_context = ClientSearch.FileSearchContext( file_service_key = file_service_key, tag_service_key = tag_service_key )

                    self._result = ( 'page', ClientGUIManagement.CreateManagementControllerQuery( page_name, file_service_key, file_search_context, search_enabled ) )

                elif entry_type == 'page_duplicate_filter':

                    self._result = ( 'page', ClientGUIManagement.CreateManagementControllerDuplicateFilter() )

                elif entry_type == 'pages_notebook':

                    self._result = ( 'pages', None )

                elif entry_type == 'page_import_booru':

                    with ClientGUIDialogs.DialogSelectBooru( self ) as dlg:

                        if dlg.ShowModal() == wx.ID_OK:

                            gallery_identifier = dlg.GetGalleryIdentifier()

                            self._result = ( 'page', ClientGUIManagement.CreateManagementControllerImportGallery( gallery_identifier ) )

                        else:

                            self.EndModal( wx.ID_CANCEL )

                            return

                elif entry_type == 'page_import_gallery':

                    site_type = obj

                    gallery_identifier = ClientDownloading.GalleryIdentifier( site_type )

                    self._result = ( 'page', ClientGUIManagement.CreateManagementControllerImportGallery( gallery_identifier ) )

                elif entry_type == 'page_import_page_of_images':

                    self._result = ( 'page', ClientGUIManagement.CreateManagementControllerImportPageOfImages() )

                elif entry_type == 'page_import_thread_watcher':

                    self._result = ( 'page', ClientGUIManagement.CreateManagementControllerImportThreadWatcher() )

                elif entry_type == 'page_import_urls':

                    self._result = ( 'page', ClientGUIManagement.CreateManagementControllerImportURLs() )

                elif entry_type == 'page_petitions':

                    petition_service_key = obj

                    self._result = ( 'page', ClientGUIManagement.CreateManagementControllerPetitions( petition_service_key ) )

                self.EndModal( wx.ID_OK )

    def _InitButtons( self, menu_keyword ):

        self._command_dict = {}

        entries = []

        if menu_keyword == 'home':

            entries.append( ( 'menu', 'files' ) )
            entries.append( ( 'menu', 'download' ) )

            if len( self._petition_service_keys ) > 0:

                entries.append( ( 'menu', 'petitions' ) )

            entries.append( ( 'menu', 'special' ) )

        elif menu_keyword == 'files':

            entries.append( ( 'page_query', CC.LOCAL_FILE_SERVICE_KEY ) )
            entries.append( ( 'page_query', CC.TRASH_SERVICE_KEY ) )
            entries.append( ( 'page_query', CC.COMBINED_LOCAL_FILE_SERVICE_KEY ) )

            for service in self._services:

                if service.GetServiceType() == HC.FILE_REPOSITORY:

                    entries.append( ( 'page_query', service.GetServiceKey() ) )

        elif menu_keyword == 'download':

            entries.append( ( 'page_import_urls', None ) )
            entries.append( ( 'page_import_thread_watcher', None ) )
            entries.append( ( 'menu', 'gallery' ) )
            entries.append( ( 'page_import_page_of_images', None ) )

        elif menu_keyword == 'gallery':

            entries.append( ( 'page_import_booru', None ) )
            entries.append( ( 'page_import_gallery', HC.SITE_TYPE_DEVIANT_ART ) )
            entries.append( ( 'menu', 'hentai foundry' ) )
            entries.append( ( 'page_import_gallery', HC.SITE_TYPE_NEWGROUNDS ) )

            result = HG.client_controller.Read( 'serialisable_simple', 'pixiv_account' )

            if result is not None:

                entries.append( ( 'menu', 'pixiv' ) )

            entries.append( ( 'page_import_gallery', HC.SITE_TYPE_TUMBLR ) )

        elif menu_keyword == 'hentai foundry':

            entries.append( ( 'page_import_gallery', HC.SITE_TYPE_HENTAI_FOUNDRY_ARTIST ) )
            entries.append( ( 'page_import_gallery', HC.SITE_TYPE_HENTAI_FOUNDRY_TAGS ) )

        elif menu_keyword == 'pixiv':

            entries.append( ( 'page_import_gallery', HC.SITE_TYPE_PIXIV_ARTIST_ID ) )
            #entries.append( ( 'page_import_gallery', HC.SITE_TYPE_PIXIV_TAG ) )

        elif menu_keyword == 'petitions':

            entries = [ ( 'page_petitions', service_key ) for service_key in self._petition_service_keys ]

        elif menu_keyword == 'special':

            entries.append( ( 'pages_notebook', None ) )
            entries.append( ( 'page_duplicate_filter', None ) )

        if len( entries ) <= 4:

            self._button_1.Hide()
            self._button_3.Hide()
            self._button_5.Hide()
            self._button_7.Hide()
            self._button_9.Hide()

            potential_buttons = [ self._button_8, self._button_4, self._button_6, self._button_2 ]

        elif len( entries ) <= 9:

            potential_buttons = [ self._button_7, self._button_8, self._button_9, self._button_4, self._button_5, self._button_6, self._button_1, self._button_2, self._button_3 ]

        else:

            # sort out a multi-page solution? maybe only if this becomes a big thing; the person can always select from the menus, yeah?

            potential_buttons = [ self._button_7, self._button_8, self._button_9, self._button_4, self._button_5, self._button_6, self._button_1, self._button_2, self._button_3 ]
            entries = entries[:9]

        for entry in entries: self._AddEntry( potential_buttons.pop( 0 ), entry )

        unused_buttons = potential_buttons

        for button in unused_buttons: button.Hide()

    def EventButton( self, event ):

        id = event.GetId()

        if id == wx.ID_CANCEL:

            self.EndModal( wx.ID_CANCEL )

        else:

            self._HitButton( id )

    def EventCharHook( self, event ):

        id = None

        ( modifier, key ) = ClientData.ConvertKeyEventToSimpleTuple( event )

        if key == wx.WXK_UP: id = 8
        elif key == wx.WXK_LEFT: id = 4
        elif key == wx.WXK_RIGHT: id = 6
        elif key == wx.WXK_DOWN: id = 2
        elif key == wx.WXK_NUMPAD1: id = 1
        elif key == wx.WXK_NUMPAD2: id = 2
        elif key == wx.WXK_NUMPAD3: id = 3
        elif key == wx.WXK_NUMPAD4: id = 4
        elif key == wx.WXK_NUMPAD5: id = 5
        elif key == wx.WXK_NUMPAD6: id = 6
        elif key == wx.WXK_NUMPAD7: id = 7
        elif key == wx.WXK_NUMPAD8: id = 8
        elif key == wx.WXK_NUMPAD9: id = 9
        elif key == wx.WXK_ESCAPE:

            self.EndModal( wx.ID_CANCEL )

            return

        else:

            event.Skip()

        if id is not None:

            self._HitButton( id )

    def GetValue( self ):

        return self._result

class Page( wx.SplitterWindow ):

    def __init__( self, parent, controller, management_controller, initial_hashes ):

        wx.SplitterWindow.__init__( self, parent )

        self._page_key = HydrusData.GenerateKey()

        self._controller = controller

        self._management_controller = management_controller

        self._initial_hashes = initial_hashes

        self._management_controller.SetKey( 'page', self._page_key )

        self._initialised = False

        self._pretty_status = ''

        self.SetMinimumPaneSize( 120 )
        self.SetSashGravity( 0.0 )

        self.Bind( wx.EVT_SPLITTER_DCLICK, self.EventUnsplit )

        self._search_preview_split = wx.SplitterWindow( self, style = wx.SP_NOBORDER )

        self._search_preview_split.SetMinimumPaneSize( 180 )
        self._search_preview_split.SetSashGravity( 1.0 )

        self._search_preview_split.Bind( wx.EVT_SPLITTER_DCLICK, self.EventPreviewUnsplit )

        self._management_panel = ClientGUIManagement.CreateManagementPanel( self._search_preview_split, self, self._controller, self._management_controller )

        file_service_key = self._management_controller.GetKey( 'file_service' )

        self._preview_panel = ClientGUICanvas.CanvasPanel( self._search_preview_split, self._page_key )

        self._media_panel = ClientGUIMedia.MediaPanelThumbnails( self, self._page_key, file_service_key, [] )

        self._search_preview_split.SplitHorizontally( self._management_panel, self._preview_panel, HC.options[ 'vpos' ] )

        self.SplitVertically( self._search_preview_split, self._media_panel, HC.options[ 'hpos' ] )

        if HC.options[ 'hide_preview' ]:

            wx.CallAfter( self._search_preview_split.Unsplit, self._preview_panel )

        self._controller.sub( self, 'SetPrettyStatus', 'new_page_status' )
        self._controller.sub( self, 'SetSplitterPositions', 'set_splitter_positions' )

    def _SetPrettyStatus( self, status ):

        self._pretty_status = status

        self._controller.gui.SetStatusBarDirty()

    def _SwapMediaPanel( self, new_panel ):

        # if a new media page comes in while its menu is open, we can enter program instability.
        # so let's just put it off.

        if self._controller.MenuIsOpen():

            self._controller.CallLaterWXSafe( self, 0.5, self._SwapMediaPanel, new_panel )

            return

        self._preview_panel.SetMedia( None )

        self._media_panel.ClearPageKey()

        self.ReplaceWindow( self._media_panel, new_panel )

        self._media_panel.Hide()

        # If this is a CallAfter, OS X segfaults on refresh jej
        self._controller.CallLaterWXSafe( self._media_panel, 0.5, self._media_panel.Destroy )

        self._media_panel = new_panel

        self._controller.pub( 'refresh_page_name', self._page_key )

    def CleanBeforeDestroy( self ):

        self._management_panel.CleanBeforeDestroy()

    def EventPreviewUnsplit( self, event ):

        self._search_preview_split.Unsplit( self._preview_panel )

        self._controller.pub( 'set_focus', self._page_key, None )

    def EventUnsplit( self, event ):

        self.Unsplit( self._search_preview_split )

        self._controller.pub( 'set_focus', self._page_key, None )

    def GetDisplayName( self ):

        name = self._management_controller.GetPageName()

        if name.startswith( USER_PAGE_NAME_PREFIX ):

            return name.replace( USER_PAGE_NAME_PREFIX, '', 1 )

        else:

            return name

    def GetHashes( self ):

        if self._initialised:

            media = self.GetMedia()

            hashes = []

            for m in media:

                hashes.extend( m.GetHashes() )

            return hashes

        else:

            return self._initial_hashes

    def GetManagementController( self ):

        return self._management_controller

    # used by autocomplete
    def GetMedia( self ):

        return self._media_panel.GetSortedMedia()

    def GetMediaPanel( self ):

        return self._media_panel

    def GetName( self ):

        return self._management_controller.GetPageName()

    def GetNumFiles( self ):

        if self._initialised:

            return self._media_panel.GetNumFiles()

        else:

            return len( self._initial_hashes )

    def GetPageKey( self ):

        return self._page_key

    def GetPrettyStatus( self ):

        return self._pretty_status

    def GetSashPositions( self ):

        if self.IsSplit():

            x = self.GetSashPosition()

        else:

            x = HC.options[ 'hpos' ]

        if self._search_preview_split.IsSplit():

            # I used to do:
            # y = -1 * self._preview_panel.GetSize()[1]
            # but that crept 4 pixels smaller every time, I assume due to sash caret height

            ( sps_x, sps_y ) = self._search_preview_split.GetClientSize()

            sash_y = self._search_preview_split.GetSashPosition()

            y = -1 * ( sps_y - sash_y )

        else:

            y = HC.options[ 'vpos' ]

        return ( x, y )

    def IsImporter( self ):

        return self._management_controller.IsImporter()

    def IsURLImportPage( self ):

        return self._management_controller.GetType() == ClientGUIManagement.MANAGEMENT_TYPE_IMPORT_URLS

    def PageHidden( self ):

        self._management_panel.PageHidden()
        self._media_panel.PageHidden()

    def PageShown( self ):

        self._management_panel.PageShown()
        self._media_panel.PageShown()

    def PrepareToHide( self ):

        self._controller.pub( 'set_focus', self._page_key, None )

    def RefreshQuery( self ):

        if self._initialised:

            self._controller.pub( 'refresh_query', self._page_key )

    def ShowHideSplit( self ):

        if self.IsSplit():

            self.Unsplit( self._search_preview_split )

            self._controller.pub( 'set_focus', self._page_key, None )

        else:

            self.SplitVertically( self._search_preview_split, self._media_panel, HC.options[ 'hpos' ] )

            self._search_preview_split.SplitHorizontally( self._management_panel, self._preview_panel, HC.options[ 'vpos' ] )

    def SetMediaFocus( self ):

        self._media_panel.SetFocus()

    def SetMediaResults( self, media_results ):

        file_service_key = self._management_controller.GetKey( 'file_service' )

        media_panel = ClientGUIMedia.MediaPanelThumbnails( self, self._page_key, file_service_key, media_results )

        self._SwapMediaPanel( media_panel )

        self._initialised = True
        self._initial_hashes = []

        wx.CallAfter( self._management_panel.Start ) # importand this is callafter, so it happens after a heavy session load is done

    def SetName( self, name, from_user = False ):

        if from_user:

            name = USER_PAGE_NAME_PREFIX + name

        return self._management_controller.SetPageName( name )

    def SetPrettyStatus( self, page_key, status ):

        if page_key == self._page_key:

            if self._initialised:

                self._SetPrettyStatus( status )

    def SetSearchFocus( self ):

        self._controller.pub( 'set_search_focus', self._page_key )

    def SetSplitterPositions( self, hpos, vpos ):

        if self._search_preview_split.IsSplit():

            self._search_preview_split.SetSashPosition( vpos )

        else:

            self._search_preview_split.SplitHorizontally( self._management_panel, self._preview_panel, vpos )

        if self.IsSplit():

            self.SetSashPosition( hpos )

        else:

            self.SplitVertically( self._search_preview_split, self._media_panel, hpos )

        if HC.options[ 'hide_preview' ]:

            wx.CallAfter( self._search_preview_split.Unsplit, self._preview_panel )

    def SetSynchronisedWait( self ):

        self._controller.pub( 'synchronised_wait_switch', self._page_key )

    def Start( self ):

        if self._initial_hashes is not None and len( self._initial_hashes ) > 0:

            self._controller.CallToThread( self.THREADLoadInitialMediaResults, self._controller, self._initial_hashes )

        else:

            self._initialised = True

            wx.CallAfter( self._management_panel.Start ) # importand this is callafter, so it happens after a heavy session load is done

    def SwapMediaPanel( self, new_panel ):

        self._SwapMediaPanel( new_panel )

    def TestAbleToClose( self ):

        self._management_panel.TestAbleToClose()

    def THREADLoadInitialMediaResults( self, controller, initial_hashes ):

        def wx_code_status( status ):

            if not self:

                return

            self._SetPrettyStatus( status )

        def wx_code_publish( media_results ):

            if not self:

                return

            self.SetMediaResults( media_results )

        initial_media_results = []

        for group_of_initial_hashes in HydrusData.SplitListIntoChunks( initial_hashes, 256 ):

            more_media_results = controller.Read( 'media_results', group_of_initial_hashes )

            initial_media_results.extend( more_media_results )

            status = u'Loading initial files\u2026 ' + HydrusData.ConvertValueRangeToPrettyString( len( initial_media_results ), len( initial_hashes ) )

            wx.CallAfter( wx_code_status, status )

        hashes_to_media_results = { media_result.GetHash() : media_result for media_result in initial_media_results }

        sorted_initial_media_results = [ hashes_to_media_results[ hash ] for hash in initial_hashes ]

        wx.CallAfter( wx_code_publish, sorted_initial_media_results )

    def TIMERPageUpdate( self ):

        self._management_panel.TIMERPageUpdate()

class PagesNotebook( wx.Notebook ):

    def __init__( self, parent, controller, name ):

        wx.Notebook.__init__( self, parent )

        self._controller = controller

        self._name = name

        self._next_new_page_index = None

        self._potential_drag_page = None

        self._closed_pages = []

        self._page_key = HydrusData.GenerateKey()

        self._last_last_session_hash = None

        self._controller.sub( self, 'RefreshPageName', 'refresh_page_name' )
        self._controller.sub( self, 'NotifyPageUnclosed', 'notify_page_unclosed' )

        self.Bind( wx.EVT_MOTION, self.EventDrag )
        self.Bind( wx.EVT_LEFT_DOWN, self.EventLeftDown )
        self.Bind( wx.EVT_LEFT_UP, self.EventLeftUp )
        self.Bind( wx.EVT_LEFT_DCLICK, self.EventLeftDoubleClick )
        self.Bind( wx.EVT_MIDDLE_DOWN, self.EventMiddleClick )
        self.Bind( wx.EVT_RIGHT_DOWN, self.EventMenu )
        self.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGED, self.EventPageChanged )

    def _ChooseNewPage( self, insertion_index = None ):

        self._next_new_page_index = insertion_index

        with DialogPageChooser( self, self._controller ) as dlg:

            if dlg.ShowModal() == wx.ID_OK:

                ( page_type, page_data ) = dlg.GetValue()

                if page_type == 'pages':

                    self.NewPagesNotebook()

                elif page_type == 'page':

                    management_controller = page_data

                    self.NewPage( management_controller )

    def _CloseAllPages( self, polite = True ):

        closees = [ index for index in range( self.GetPageCount() ) ]

        self._ClosePages( closees, polite )

    def _CloseLeftPages( self, from_index ):

        message = 'Close all pages to the left?'

        with ClientGUIDialogs.DialogYesNo( self, message ) as dlg:

            if dlg.ShowModal() == wx.ID_YES:

                closees = [ index for index in range( self.GetPageCount() ) if index < from_index ]

                self._ClosePages( closees )

    def _CloseOtherPages( self, except_index ):

        message = 'Close all other pages?'

        with ClientGUIDialogs.DialogYesNo( self, message ) as dlg:

            if dlg.ShowModal() == wx.ID_YES:

                closees = [ index for index in range( self.GetPageCount() ) if index != except_index ]

                self._ClosePages( closees )

    def _ClosePage( self, index, polite = True ):

        self._controller.ResetIdleTimer()
        self._controller.ResetPageChangeTimer()

        if index == -1 or index > self.GetPageCount() - 1:

            return False

        page = self.GetPage( index )

        if polite:

            try:

                page.TestAbleToClose()

            except HydrusExceptions.PermissionException:

                return False

        page.PrepareToHide()

        page_key = page.GetPageKey()

        self._closed_pages.append( ( index, page_key ) )

        self.RemovePage( index )

        self._controller.pub( 'refresh_page_name', self._page_key )

        self._controller.pub( 'notify_closed_page', page )
        self._controller.pub( 'notify_new_undo' )

        return True

    def _ClosePages( self, indices, polite = True ):

        indices = list( indices )

        indices.sort( reverse = True ) # so we are closing from the end first

        for index in indices:

            successful = self._ClosePage( index, polite )

            if not successful:

                break

    def _CloseRightPages( self, from_index ):

        message = 'Close all pages to the right?'

        with ClientGUIDialogs.DialogYesNo( self, message ) as dlg:

            if dlg.ShowModal() == wx.ID_YES:

                closees = [ index for index in range( self.GetPageCount() ) if index > from_index ]

                self._ClosePages( closees )

    def _GetDefaultPageInsertionIndex( self ):

        new_options = self._controller.new_options

        new_page_goes = new_options.GetInteger( 'default_new_page_goes' )

        current_index = self.GetSelection()

        if current_index == wx.NOT_FOUND:

            new_page_goes = CC.NEW_PAGE_GOES_FAR_LEFT

        if new_page_goes == CC.NEW_PAGE_GOES_FAR_LEFT:

            insertion_index = 0

        elif new_page_goes == CC.NEW_PAGE_GOES_LEFT_OF_CURRENT:

            insertion_index = current_index

        elif new_page_goes == CC.NEW_PAGE_GOES_RIGHT_OF_CURRENT:

            insertion_index = current_index + 1

        elif new_page_goes == CC.NEW_PAGE_GOES_FAR_RIGHT:

            insertion_index = self.GetPageCount()

        return insertion_index

    def _GetMediaPages( self, only_my_level ):

        results = []

        for page in self._GetPages():

            if isinstance( page, PagesNotebook ):

                if not only_my_level:

                    results.extend( page.GetMediaPages() )

            else:

                results.append( page )

        return results

    def _GetIndex( self, page_key ):

        for ( page, index ) in ( ( self.GetPage( index ), index ) for index in range( self.GetPageCount() ) ):

            if page.GetPageKey() == page_key:

                return index

        raise HydrusExceptions.DataMissing()

    def _GetNotebookFromScreenPosition( self, screen_position ):

        current_page = self.GetCurrentPage()

        if current_page is None or not isinstance( current_page, PagesNotebook ):

            return self

        else:

            ( tab_index, flags ) = ClientGUICommon.NotebookScreenToHitTest( self, screen_position )

            if tab_index != wx.NOT_FOUND:

                return self

            if HC.PLATFORM_OSX:

                ( x, y ) = screen_position
                ( child_x, child_y ) = current_page.GetPosition()

                on_child_notebook_somewhere = y > child_y # wew lad, OSX not delivering onpage maybe?

            else:

                on_child_notebook_somewhere = flags & wx.NB_HITTEST_ONPAGE

            if on_child_notebook_somewhere:

                return current_page._GetNotebookFromScreenPosition( screen_position )

        return self

    def _GetPages( self ):

        return [ self.GetPage( i ) for i in range( self.GetPageCount() ) ]

    def _GetPageFromPageKey( self, page_key ):

        for page in self._GetPages():

            if page.GetPageKey() == page_key:

                return page

            if isinstance( page, PagesNotebook ):

                if page.HasPageKey( page_key ):

                    return page._GetPageFromPageKey( page_key )

        return None

    def _GetPageFromName( self, page_name ):

        for page in self._GetPages():

            if page.GetDisplayName() == page_name:

                return page

            if isinstance( page, PagesNotebook ):

                result = page._GetPageFromName( page_name )

                if result is not None:

                    return result

        return None

    def _MovePage( self, page, dest_notebook, insertion_tab_index, follow_dropped_page = False ):

        source_notebook = page.GetParent()

        for ( index, p ) in enumerate( source_notebook._GetPages() ):

            if p == page:

                source_notebook.RemovePage( index )

                break

        if source_notebook != dest_notebook:

            page.Reparent( dest_notebook )

            self._controller.pub( 'refresh_page_name', source_notebook.GetPageKey() )

        insertion_tab_index = min( insertion_tab_index, dest_notebook.GetPageCount() )

        dest_notebook.InsertPage( insertion_tab_index, page, page.GetDisplayName(), select = follow_dropped_page )

        if follow_dropped_page:

            self.ShowPage( page )

        self._controller.pub( 'refresh_page_name', page.GetPageKey() )

    def _ShiftPage( self, page_index, delta = None, new_index = None ):

        new_page_index = page_index

        if delta is not None:

            new_page_index = page_index + delta

        if new_index is not None:

            new_page_index = new_index

        if new_page_index == page_index:

            return

        if 0 <= new_page_index and new_page_index <= self.GetPageCount() - 1:

            page_is_selected = self.GetSelection() == page_index

            page = self.GetPage( page_index )
            name = self.GetPageText( page_index )

            self.RemovePage( page_index )

            self.InsertPage( new_page_index, page, name, page_is_selected )

    def _RefreshPageName( self, index ):

        if index == -1 or index > self.GetPageCount() - 1:

            return

        new_options = self._controller.new_options

        max_page_name_chars = new_options.GetInteger( 'max_page_name_chars' )

        page_file_count_display = new_options.GetInteger( 'page_file_count_display' )

        page = self.GetPage( index )

        page_name = page.GetDisplayName()

        page_name = page_name.replace( os.linesep, '' )

        if len( page_name ) > max_page_name_chars:

            page_name = page_name[ : max_page_name_chars ] + u'\u2026'

        if page_file_count_display == CC.PAGE_FILE_COUNT_DISPLAY_ALL or ( page_file_count_display == CC.PAGE_FILE_COUNT_DISPLAY_ONLY_IMPORTERS and page.IsImporter() ):

            num_files = page.GetNumFiles()

            page_name += ' (' + HydrusData.ConvertIntToPrettyString( num_files ) + ')'

        safe_page_name = self.EscapeMnemonics( page_name )

        existing_page_name = self.GetPageText( index )

        if existing_page_name not in ( safe_page_name, page_name ):

            self.SetPageText( index, safe_page_name )

    def _RenamePage( self, index ):

        if index == -1 or index > self.GetPageCount() - 1:

            return

        page = self.GetPage( index )

        current_name = page.GetDisplayName()

        with ClientGUIDialogs.DialogTextEntry( self, 'Enter the new name.', default = current_name, allow_blank = False ) as dlg:

            if dlg.ShowModal() == wx.ID_OK:

                new_name = dlg.GetValue()

                page.SetName( new_name, from_user = True )

                self._controller.pub( 'refresh_page_name', page.GetPageKey() )

    def _SendPageToNewNotebook( self, index ):

        page = self.GetPage( index )

        dest_notebook = self.NewPagesNotebook( forced_insertion_index = index, give_it_a_blank_page = False )

        self._MovePage( page, dest_notebook, 0 )

    def _SendRightPagesToNewNotebook( self, from_index ):

        message = 'Send all pages to the right to a new page of pages?'

        with ClientGUIDialogs.DialogYesNo( self, message ) as dlg:

            if dlg.ShowModal() == wx.ID_YES:

                pages_index = self.GetPageCount()

                dest_notebook = self.NewPagesNotebook( forced_insertion_index = pages_index, give_it_a_blank_page = False )

                movees = list( range( from_index + 1, pages_index ) )

                movees.reverse()

                for index in movees:

                    page = self.GetPage( index )

                    self._MovePage( page, dest_notebook, 0 )

    def _ShowMenu( self, screen_position ):

        ( tab_index, flags ) = ClientGUICommon.NotebookScreenToHitTest( self, screen_position )

        num_pages = self.GetPageCount()

        more_than_one_tab = num_pages > 1

        click_over_tab = tab_index != -1

        click_over_page_of_pages = False

        end_index = num_pages - 1

        existing_session_names = self._controller.Read( 'serialisable_names', HydrusSerialisable.SERIALISABLE_TYPE_GUI_SESSION )

        menu = wx.Menu()

        if click_over_tab:

            page = self.GetPage( tab_index )

            click_over_page_of_pages = isinstance( page, PagesNotebook )

            ClientGUIMenus.AppendMenuItem( self, menu, 'close page', 'Close this page.', self._ClosePage, tab_index )

            can_go_left = tab_index > 0
            can_go_right = tab_index < end_index

            if num_pages > 1:

                ClientGUIMenus.AppendMenuItem( self, menu, 'close other pages', 'Close all pages but this one.', self._CloseOtherPages, tab_index )

                if can_go_left:

                    ClientGUIMenus.AppendMenuItem( self, menu, 'close pages to the left', 'Close all pages to the left of this one.', self._CloseLeftPages, tab_index )

                if can_go_right:

                    ClientGUIMenus.AppendMenuItem( self, menu, 'close pages to the right', 'Close all pages to the right of this one.', self._CloseRightPages, tab_index )

            ClientGUIMenus.AppendSeparator( menu )

            ClientGUIMenus.AppendMenuItem( self, menu, 'send this page down to a new page of pages', 'Make a new page of pages and put this page in it.', self._SendPageToNewNotebook, tab_index )

            if can_go_right:

                ClientGUIMenus.AppendMenuItem( self, menu, 'send pages to the right to a new page of pages', 'Make a new page of pages and put all the pages to the right into it.', self._SendRightPagesToNewNotebook, tab_index )

            ClientGUIMenus.AppendSeparator( menu )

            ClientGUIMenus.AppendMenuItem( self, menu, 'rename page', 'Rename this page.', self._RenamePage, tab_index )

        ClientGUIMenus.AppendMenuItem( self, menu, 'new page', 'Choose a new page.', self._ChooseNewPage )

        if click_over_page_of_pages or len( existing_session_names ) > 0:

            ClientGUIMenus.AppendSeparator( menu )

        if len( existing_session_names ) > 0:

            submenu = wx.Menu()

            for name in existing_session_names:

                ClientGUIMenus.AppendMenuItem( self, submenu, name, 'Load this session here.', self.AppendGUISession, name )

            ClientGUIMenus.AppendMenu( menu, submenu, 'append session' )

        if click_over_tab:

            if click_over_page_of_pages:

                submenu = wx.Menu()

                for name in existing_session_names:

                    if name == 'last session':

                        continue

                    ClientGUIMenus.AppendMenuItem( self, submenu, name, 'Save this page of pages to the session.', page.SaveGUISession, name )

                ClientGUIMenus.AppendMenuItem( self, submenu, 'create a new session', 'Save this page of pages to the session.', page.SaveGUISession, suggested_name = page.GetDisplayName() )

                ClientGUIMenus.AppendMenu( menu, submenu, 'save this page of pages to a session' )

            if more_than_one_tab:

                ClientGUIMenus.AppendSeparator( menu )

                can_home = tab_index > 1
                can_move_left = tab_index > 0
                can_move_right = tab_index < end_index
                can_end = tab_index < end_index - 1

                ClientGUIMenus.AppendMenuItem( self, menu, 'new page here', 'Choose a new page.', self._ChooseNewPage, tab_index )

                ClientGUIMenus.AppendSeparator( menu )

                if can_home:

                    ClientGUIMenus.AppendMenuItem( self, menu, 'move to left end', 'Move this page all the way to the left.', self._ShiftPage, tab_index, new_index = 0 )

                if can_move_left:

                    ClientGUIMenus.AppendMenuItem( self, menu, 'move left', 'Move this page one to the left.', self._ShiftPage, tab_index, delta = -1 )

                if can_move_right:

                    ClientGUIMenus.AppendMenuItem( self, menu, 'move right', 'Move this page one to the right.', self._ShiftPage, tab_index, 1 )

                if can_end:

                    ClientGUIMenus.AppendMenuItem( self, menu, 'move to right end', 'Move this page all the way to the right.', self._ShiftPage, tab_index, new_index = end_index )

            if click_over_page_of_pages and page.GetPageCount() > 0:

                ClientGUIMenus.AppendSeparator( menu )

                ClientGUIMenus.AppendMenuItem( self, menu, 'refresh all this page\'s pages', 'Command every page below this one to refresh.', page.RefreshAllPages )

        self._controller.PopupMenu( self, menu )

    def AppendGUISession( self, name, load_in_a_page_of_pages = True ):

        try:

            session = self._controller.Read( 'serialisable_named', HydrusSerialisable.SERIALISABLE_TYPE_GUI_SESSION, name )

        except Exception as e:

            HydrusData.ShowText( 'While trying to load session ' + name + ', this error happened:' )
            HydrusData.ShowException( e )

            self.NewPageQuery( CC.LOCAL_FILE_SERVICE_KEY )

            return

        if load_in_a_page_of_pages:

            destination = self.NewPagesNotebook( name = name, give_it_a_blank_page = False)

        else:

            destination = self

        page_tuples = session.GetPages()

        destination.AppendSessionPageTuples( page_tuples )

    def AppendSessionPageTuples( self, page_tuples ):

        starting_index = self._GetDefaultPageInsertionIndex()

        forced_insertion_index = starting_index

        for page_tuple in page_tuples:

            ( page_type, page_data ) = page_tuple

            if page_type == 'pages':

                ( name, subpage_tuples ) = page_data

                try:

                    page = self.NewPagesNotebook( name, forced_insertion_index = forced_insertion_index, give_it_a_blank_page = False )

                    page.AppendSessionPageTuples( subpage_tuples )

                except Exception as e:

                    HydrusData.ShowException( e )

                # append the tuples

            elif page_type == 'page':

                ( management_controller, initial_hashes ) = page_data

                try:

                    self.NewPage( management_controller, initial_hashes = initial_hashes, forced_insertion_index = forced_insertion_index )

                except Exception as e:

                    HydrusData.ShowException( e )

            forced_insertion_index += 1

    def ChooseNewPage( self ):

        self._ChooseNewPage()

    def ChooseNewPageForDeepestNotebook( self ):

        current_page = self.GetCurrentPage()

        if isinstance( current_page, PagesNotebook ):

            current_page.ChooseNewPageForDeepestNotebook()

        else:

            self._ChooseNewPage()

    def CleanBeforeDestroy( self ):

        for page in self._GetPages():

            page.CleanBeforeDestroy()

    def CloseCurrentPage( self, polite = True ):

        selection = self.GetSelection()

        if selection != wx.NOT_FOUND:

            page = self.GetPage( selection )

            if isinstance( page, PagesNotebook ):

                if page.GetNumPages() > 0:

                    page.CloseCurrentPage( polite )

                else:

                    self._ClosePage( selection, polite = polite )

            else:

                self._ClosePage( selection, polite = polite )

    def EventDrag( self, event ):

        if event.Dragging() and self._potential_drag_page is not None:

            drop_source = wx.DropSource( self._controller.gui )

            #

            hydrus_page_tab_data_object = wx.CustomDataObject( 'application/hydrus-page-tab' )

            data = self._potential_drag_page.GetPageKey()

            hydrus_page_tab_data_object.SetData( data )

            #

            drop_source.SetData( hydrus_page_tab_data_object )

            drop_source.DoDragDrop( wx.Drag_DefaultMove )

            self._potential_drag_page = None

        event.Skip()

    def EventLeftDown( self, event ):

        event_skip_ok = True

        position = event.GetPosition()

        ( tab_index, flags ) = self.HitTest( position )

        if tab_index != -1:

            page = self.GetPage( tab_index )

            if HC.PLATFORM_OSX and page == self.GetCurrentPage():

                # drag doesn't work if we allow the event to go ahead
                # but we do want the event to go ahead if it is a 'select different page' event

                event_skip_ok = False

            self._potential_drag_page = page

        if event_skip_ok:

            event.Skip()

    def EventLeftDoubleClick( self, event ):

        position = event.GetPosition()

        ( tab_index, flags ) = self.HitTest( position )

        if tab_index == wx.NOT_FOUND:

            if flags & wx.NB_HITTEST_NOWHERE and flags & wx.NB_HITTEST_ONPAGE:

                screen_position = self.ClientToScreen( position )

                notebook = self._GetNotebookFromScreenPosition( screen_position )

                notebook.EventNewPageFromScreenPosition( screen_position )

            else:

                self.ChooseNewPage()

        else:

            event.Skip()

    def EventLeftUp( self, event ):

        self._potential_drag_page = None

        event.Skip()

    def EventMenu( self, event ):

        if not self.IsShown(): # can't clienttoscreen if not shown, like during init

            return

        screen_position = self.ClientToScreen( event.GetPosition() )

        self._ShowMenu( screen_position )

    def EventMenuFromScreenPosition( self, position ):

        notebook = self._GetNotebookFromScreenPosition( position )

        notebook._ShowMenu( position )

    def EventMiddleClick( self, event ):

        if self._controller.MenuIsOpen():

            return

        position = event.GetPosition()

        ( tab_index, flags ) = self.HitTest( position )

        if tab_index == wx.NOT_FOUND:

            if flags & wx.NB_HITTEST_NOWHERE and flags & wx.NB_HITTEST_ONPAGE:

                screen_position = self.ClientToScreen( position )

                notebook = self._GetNotebookFromScreenPosition( screen_position )

                notebook.EventNewPageFromScreenPosition( screen_position )

            else:

                self.ChooseNewPage()

        else:

            self._ClosePage( tab_index )

    def EventNewPageFromScreenPosition( self, position ):

        notebook = self._GetNotebookFromScreenPosition( position )

        notebook._ChooseNewPage()

    def EventPageChanged( self, event ):

        if event.EventObject == self: # because OS X wants to bump this up to parent notebooks

            old_selection = event.GetOldSelection()
            selection = event.GetSelection()

            if old_selection != wx.NOT_FOUND:

                self.GetPage( old_selection ).PageHidden()

            if selection != wx.NOT_FOUND:

                self.GetPage( selection ).PageShown()

            self._controller.gui.RefreshStatusBar()

        if HC.PLATFORM_OSX:

            event.Skip() # need this or OS X spergs out and never .Show()s new page, wew

    def GetCurrentMediaPage( self ):

        page = self.GetCurrentPage()

        if isinstance( page, PagesNotebook ):

            return page.GetCurrentMediaPage()

        else:

            return page # this can be None

    def GetDisplayName( self ):

        if self._name.startswith( USER_PAGE_NAME_PREFIX ):

            return self._name.replace( USER_PAGE_NAME_PREFIX, '', 1 )

        else:

            return self._name

    def GetMediaPages( self, only_my_level = False ):

        return self._GetMediaPages( only_my_level )

    def GetName( self ):

        return self._name

    def GetNumFiles( self ):

        return sum( page.GetNumFiles() for page in self._GetPages() )

    def GetNumPages( self, only_my_level = False ):

        if only_my_level:

            return self.GetPageCount()

        else:

            total = 0

            for page in self._GetPages():

                if isinstance( page, PagesNotebook ):

                    total += page.GetNumPages( False )

                else:

                    total += 1

            return total

    def GetOrMakeURLImportPage( self ):

        for page in self._GetPages():

            if isinstance( page, PagesNotebook ):

                if page.HasURLImportPage():

                    return page.GetOrMakeURLImportPage()

            elif page.IsURLImportPage():

                return page

        # import page does not exist

        return self.NewPageImportURLs( on_deepest_notebook = True )

    def GetPageKey( self ):

        return self._page_key

    def GetPages( self ):

        return self._GetPages()

    def GetPrettyStatus( self ):

        return HydrusData.ConvertIntToPrettyString( self.GetPageCount() ) + ' pages, ' + HydrusData.ConvertIntToPrettyString( self.GetNumFiles() ) + ' files'

    def HasPage( self, page ):

        return self.HasPageKey( page.GetPageKey() )

    def HasPageKey( self, page_key ):

        for page in self._GetPages():

            if page.GetPageKey() == page_key:

                return True

            elif isinstance( page, PagesNotebook ) and page.HasPageKey( page_key ):

                return True

        return False

    def HasURLImportPage( self ):

        for page in self._GetPages():

            if isinstance( page, PagesNotebook ):

                if page.HasURLImportPage():

                    return True

            else:

                if page.IsURLImportPage():

                    return True

        return False

    def LoadGUISession( self, name ):

        if self.GetPageCount() > 0:

            message = 'Close the current pages and load session "' + name + '"?'

            with ClientGUIDialogs.DialogYesNo( self, message, title = 'Clear and load session?' ) as yn_dlg:

                if yn_dlg.ShowModal() != wx.ID_YES:

                    return

            try:

                self.TestAbleToClose()

            except HydrusExceptions.PermissionException:

                return

            self._CloseAllPages( polite = False )

        self.AppendGUISession( name, load_in_a_page_of_pages = False )

    def MediaDragAndDropDropped( self, source_page_key, hashes ):

        source_page = self._GetPageFromPageKey( source_page_key )

        if source_page is None:

            return

        screen_position = wx.GetMousePosition()

        dest_notebook = self._GetNotebookFromScreenPosition( screen_position )

        ( x, y ) = screen_position

        ( tab_index, flags ) = ClientGUICommon.NotebookScreenToHitTest( dest_notebook, ( x, y ) )

        do_add = True
        # do chase - if we need to chase to an existing dest page on which we dropped files
        # do return - if we need to return to source page if we created a new one

        if flags & wx.NB_HITTEST_ONPAGE:

            dest_page = dest_notebook.GetCurrentPage()

        elif tab_index == wx.NOT_FOUND:

            dest_page = dest_notebook.NewPageQuery( CC.LOCAL_FILE_SERVICE_KEY, initial_hashes = hashes )

            do_add = False

        else:

            dest_page = dest_notebook.GetPage( tab_index )

            if isinstance( dest_page, PagesNotebook ):

                result = dest_page.GetCurrentMediaPage()

                if result is None:

                    dest_page = dest_page.NewPageQuery( CC.LOCAL_FILE_SERVICE_KEY, initial_hashes = hashes )

                    do_add = False

                else:

                    dest_page = result

        if dest_page is None:

            return # we somehow dropped onto a new notebook that has no pages

        if dest_page.GetPageKey() == source_page_key:

            return # we dropped onto the same page we picked up on

        if do_add:

            unsorted_media_results = self._controller.Read( 'media_results', hashes )

            hashes_to_media_results = { media_result.GetHash() : media_result for media_result in unsorted_media_results }

            sorted_media_results = [ hashes_to_media_results[ hash ] for hash in hashes ]

            dest_page.GetMediaPanel().AddMediaResults( dest_page.GetPageKey(), sorted_media_results )

        else:

            self.ShowPage( source_page )

        ctrl_down = wx.GetKeyState( wx.WXK_COMMAND ) or wx.GetKeyState( wx.WXK_CONTROL )

        if not ctrl_down:

            source_page.GetMediaPanel().RemoveMedia( source_page.GetPageKey(), hashes )

    def NewPage( self, management_controller, initial_hashes = None, forced_insertion_index = None, on_deepest_notebook = False, select_page = True ):

        current_page = self.GetCurrentPage()

        if on_deepest_notebook and isinstance( current_page, PagesNotebook ):

            return current_page.NewPage( management_controller, initial_hashes = initial_hashes, forced_insertion_index = forced_insertion_index, on_deepest_notebook = on_deepest_notebook )

        WARNING_TOTAL_PAGES = self._controller.new_options.GetInteger( 'total_pages_warning' )
        MAX_TOTAL_PAGES = 200

        ( total_active_page_count, total_closed_page_count ) = self._controller.gui.GetTotalPageCounts()

        if total_active_page_count + total_closed_page_count >= WARNING_TOTAL_PAGES:

            self._controller.gui.DeleteAllClosedPages()

        if not HG.no_page_limit_mode:

            if total_active_page_count >= MAX_TOTAL_PAGES:

                message = 'The client should not have more than ' + str( MAX_TOTAL_PAGES ) + ' pages open, as it leads to program instability! Are you sure you want to open more pages?'

                with ClientGUIDialogs.DialogYesNo( self, message, title = 'Too many pages!', yes_label = 'yes, and do not tell me again', no_label = 'no' ) as dlg:

                    if dlg.ShowModal() == wx.ID_YES:

                        HG.no_page_limit_mode = True

                        self._controller.pub( 'notify_new_options' )

                    else:

                        return

            if total_active_page_count == WARNING_TOTAL_PAGES:

                HydrusData.ShowText( 'You have ' + str( total_active_page_count ) + ' pages open! You can only open a few more before program stability is affected! Please close some now!' )

        self._controller.ResetIdleTimer()
        self._controller.ResetPageChangeTimer()

        if initial_hashes is None:

            initial_hashes = []

        page = Page( self, self._controller, management_controller, initial_hashes )

        if forced_insertion_index is None:

            if self._next_new_page_index is None:

                insertion_index = self._GetDefaultPageInsertionIndex()

            else:

                insertion_index = self._next_new_page_index

                self._next_new_page_index = None

        else:

            insertion_index = forced_insertion_index

        page_name = page.GetDisplayName()

        # in some unusual circumstances, this gets out of whack
        insertion_index = min( insertion_index, self.GetPageCount() )

        self.InsertPage( insertion_index, page, page_name, select = select_page )

        self._controller.pub( 'refresh_page_name', page.GetPageKey() )
        self._controller.pub( 'notify_new_pages' )

        wx.CallAfter( page.Start )
        wx.CallAfter( page.SetSearchFocus )

        return page

    def NewPageDuplicateFilter( self, on_deepest_notebook = False ):

        management_controller = ClientGUIManagement.CreateManagementControllerDuplicateFilter()

        return self.NewPage( management_controller, on_deepest_notebook = on_deepest_notebook )

    def NewPageImportBooru( self, on_deepest_notebook = False ):

        with ClientGUIDialogs.DialogSelectBooru( self ) as dlg:

            if dlg.ShowModal() == wx.ID_OK:

                gallery_identifier = dlg.GetGalleryIdentifier()

                return self.NewPageImportGallery( gallery_identifier, on_deepest_notebook = on_deepest_notebook )

    def NewPageImportGallery( self, gallery_identifier, on_deepest_notebook = False ):

        management_controller = ClientGUIManagement.CreateManagementControllerImportGallery( gallery_identifier )

        return self.NewPage( management_controller, on_deepest_notebook = on_deepest_notebook )

    def NewPageImportPageOfImages( self, on_deepest_notebook = False ):

        management_controller = ClientGUIManagement.CreateManagementControllerImportPageOfImages()

        return self.NewPage( management_controller, on_deepest_notebook = on_deepest_notebook )

    def NewPageImportThreadWatcher( self, thread_url = None, on_deepest_notebook = False ):

        management_controller = ClientGUIManagement.CreateManagementControllerImportThreadWatcher( thread_url )

        return self.NewPage( management_controller, on_deepest_notebook = on_deepest_notebook )

    def NewPageImportURLs( self, on_deepest_notebook = False ):

        management_controller = ClientGUIManagement.CreateManagementControllerImportURLs()

        return self.NewPage( management_controller, on_deepest_notebook = on_deepest_notebook )

    def NewPagePetitions( self, service_key, on_deepest_notebook = False ):

        management_controller = ClientGUIManagement.CreateManagementControllerPetitions( service_key )

        return self.NewPage( management_controller, on_deepest_notebook = on_deepest_notebook )

    def NewPageQuery( self, file_service_key, initial_hashes = None, initial_predicates = None, page_name = None, on_deepest_notebook = False, do_sort = False, select_page = True ):

        if initial_hashes is None:

            initial_hashes = []

        if initial_predicates is None:

            initial_predicates = []

        if page_name is None:

            page_name = 'files'

        search_enabled = len( initial_hashes ) == 0

        new_options = self._controller.new_options

        tag_service_key = new_options.GetKey( 'default_tag_service_search_page' )

        if not self._controller.services_manager.ServiceExists( tag_service_key ):

            tag_service_key = CC.COMBINED_TAG_SERVICE_KEY

        file_search_context = ClientSearch.FileSearchContext( file_service_key = file_service_key, tag_service_key = tag_service_key, predicates = initial_predicates )

        management_controller = ClientGUIManagement.CreateManagementControllerQuery( page_name, file_service_key, file_search_context, search_enabled )

        page = self.NewPage( management_controller, initial_hashes = initial_hashes, on_deepest_notebook = on_deepest_notebook, select_page = select_page )

        if do_sort:

            HG.client_controller.pub( 'do_page_sort', page.GetPageKey() )

        return page

    def NewPagesNotebook( self, name = 'pages', forced_insertion_index = None, on_deepest_notebook = False, give_it_a_blank_page = True ):

        current_page = self.GetCurrentPage()

        if on_deepest_notebook and isinstance( current_page, PagesNotebook ):

            current_page.NewPagesNotebook( name = name, forced_insertion_index = forced_insertion_index, on_deepest_notebook = on_deepest_notebook, give_it_a_blank_page = give_it_a_blank_page )

            return

        self._controller.ResetIdleTimer()
        self._controller.ResetPageChangeTimer()

        page = PagesNotebook( self, self._controller, name )

        if forced_insertion_index is None:

            if self._next_new_page_index is None:

                insertion_index = self._GetDefaultPageInsertionIndex()

            else:

                insertion_index = self._next_new_page_index

                self._next_new_page_index = None

        else:

            insertion_index = forced_insertion_index

        page_name = page.GetDisplayName()

        self.InsertPage( insertion_index, page, page_name, select = True )

        self._controller.pub( 'refresh_page_name', page.GetPageKey() )

        if give_it_a_blank_page:

            page.NewPageQuery( CC.LOCAL_FILE_SERVICE_KEY )

        return page

    def NotifyPageUnclosed( self, page ):

        page_key = page.GetPageKey()

        for ( index, closed_page_key ) in self._closed_pages:

            if page_key == closed_page_key:

                page.Show()

                insert_index = min( index, self.GetPageCount() )

                name = page.GetDisplayName()

                self.InsertPage( insert_index, page, name, True )

                self._controller.pub( 'refresh_page_name', page.GetPageKey() )

                self._closed_pages.remove( ( index, closed_page_key ) )

                break

    def PageHidden( self ):

        result = self.GetCurrentPage()

        if result is not None:

            result.PageHidden()

    def PageShown( self ):

        result = self.GetCurrentPage()

        if result is not None:

            result.PageShown()

    def PageDragAndDropDropped( self, page_key ):

        page = self._GetPageFromPageKey( page_key )

        if page is None:

            return

        screen_position = wx.GetMousePosition()

        dest_notebook = self._GetNotebookFromScreenPosition( screen_position )

        ( x, y ) = screen_position

        ( tab_index, flags ) = ClientGUICommon.NotebookScreenToHitTest( dest_notebook, ( x, y ) )

        if flags & wx.NB_HITTEST_ONPAGE:

            # was not dropped on label area, so ditch DnD

            return

        if tab_index == wx.NOT_FOUND:

            # if it isn't dropped on anything, put it on the end

            tab_index = dest_notebook.GetPageCount()

            if tab_index > 0:

                if dest_notebook.GetPage( tab_index - 1 ) == page:

                    return

        else:

            EDGE_PADDING = 10

            ( left_tab_index, gumpf ) = ClientGUICommon.NotebookScreenToHitTest( dest_notebook, ( x - EDGE_PADDING, y ) )
            ( right_tab_index, gumpf ) = ClientGUICommon.NotebookScreenToHitTest( dest_notebook,  ( x + EDGE_PADDING, y ) )

            landed_near_left_edge = left_tab_index != tab_index
            landed_near_right_edge = right_tab_index != tab_index

            landed_on_edge = landed_near_right_edge or landed_near_left_edge
            landed_in_middle = not landed_on_edge

            there_is_a_page_to_the_left = tab_index > 0
            there_is_a_page_to_the_right = tab_index < dest_notebook.GetPageCount() - 1

            page_on_left_is_source = there_is_a_page_to_the_left and dest_notebook.GetPage( tab_index - 1 ) == page
            page_on_right_is_source = there_is_a_page_to_the_right and dest_notebook.GetPage( tab_index + 1 ) == page

            # dropped on source and not on the right edge: do nothing

            landee_page = dest_notebook.GetPage( tab_index )

            if landee_page == page:

                if landed_near_right_edge and there_is_a_page_to_the_right:

                    tab_index += 1

                else:

                    return

            # dropped just to the left of source: do nothing

            if landed_near_right_edge and page_on_right_is_source:

                return

            # dropped on left side of an edge: insert on right side

            if landed_near_right_edge:

                tab_index += 1

            if landed_in_middle and isinstance( landee_page, PagesNotebook ):

                dest_notebook = landee_page
                tab_index = dest_notebook.GetPageCount()

        if dest_notebook == page or ClientGUICommon.IsWXAncestor( dest_notebook, page ):

            # can't drop a notebook beneath itself!
            return

        insertion_tab_index = tab_index

        shift_down = wx.GetKeyState( wx.WXK_SHIFT )

        follow_dropped_page = not shift_down

        new_options = HG.client_controller.new_options

        if new_options.GetBoolean( 'reverse_page_shift_drag_behaviour' ):

            follow_dropped_page = not follow_dropped_page

        self._MovePage( page, dest_notebook, insertion_tab_index, follow_dropped_page )

    def PrepareToHide( self ):

        for page in self._GetPages():

            page.PrepareToHide()

    def PresentImportedFilesToPage( self, hashes, page_name ):

        page = self._GetPageFromName( page_name )

        if page is None:

            page = self.NewPageQuery( CC.LOCAL_FILE_SERVICE_KEY, initial_hashes = hashes, page_name = page_name, on_deepest_notebook = True, select_page = False )

        else:

            unsorted_media_results = self._controller.Read( 'media_results', hashes )

            hashes_to_media_results = { media_result.GetHash() : media_result for media_result in unsorted_media_results }

            sorted_media_results = [ hashes_to_media_results[ hash ] for hash in hashes ]

            page.GetMediaPanel().AddMediaResults( page.GetPageKey(), sorted_media_results )

        return page

    def RefreshAllPages( self ):

        for page in self._GetPages():

            if isinstance( page, PagesNotebook ):

                page.RefreshAllPages()

            else:

                page.RefreshQuery()

    def RefreshPageName( self, page_key = None ):

        if page_key is None:

            for index in range( self.GetPageCount() ):

                self._RefreshPageName( index )

        else:

            for ( index, page ) in enumerate( self._GetPages() ):

                do_it = False

                if page.GetPageKey() == page_key:

                    do_it = True

                elif isinstance( page, PagesNotebook ) and page.HasPageKey( page_key ):

                    do_it = True

                if do_it:

                    self._RefreshPageName( index )

                    break

    def RenamePage( self, page_key, name ):

        for page in self._GetPages():

            if page.GetPageKey() == page_key:

                existing_name = page.GetName()

                # this is the auto-renaming system. if the existing name is user-set, then don't overwrite
                if not existing_name.startswith( USER_PAGE_NAME_PREFIX ):

                    if existing_name != name:

                        page.SetName( name )

                        self.RefreshPageName( page_key )

                return

            elif isinstance( page, PagesNotebook ) and page.HasPageKey( page_key ):

                page.RenamePage( page_key, name )

                return

    def SaveGUISession( self, name = None, suggested_name = '' ):

        if name is None:

            while True:

                with ClientGUIDialogs.DialogTextEntry( self, 'Enter a name for the new session.', default = suggested_name ) as dlg:

                    if dlg.ShowModal() == wx.ID_OK:

                        name = dlg.GetValue()

                        if name in ( '', 'just a blank page', 'last session' ):

                            wx.MessageBox( 'Sorry, you cannot have that name! Try another.' )

                        else:

                            existing_session_names = self._controller.Read( 'serialisable_names', HydrusSerialisable.SERIALISABLE_TYPE_GUI_SESSION )

                            if name in existing_session_names:

                                message = 'Session \'' + name + '\' already exists! Do you want to overwrite it?'

                                with ClientGUIDialogs.DialogYesNo( self, message, title = 'Overwrite existing session?', yes_label = 'yes, overwrite', no_label = 'no, choose another name' ) as yn_dlg:

                                    if yn_dlg.ShowModal() != wx.ID_YES:

                                        continue

                            break

                    else:

                        return

        elif name != 'last session':

            message = 'Overwrite this session?'

            with ClientGUIDialogs.DialogYesNo( self, message, title = 'Overwrite existing session?', yes_label = 'yes, overwrite', no_label = 'no' ) as yn_dlg:

                if yn_dlg.ShowModal() != wx.ID_YES:

                    return

        #

        session = GUISession( name )

        for page in self._GetPages():

            session.AddPage( page )

        #

        if name == 'last session':

            session_hash = hashlib.sha256( session.DumpToString() ).digest()

            if session_hash == self._last_last_session_hash:

                return

            self._last_last_session_hash = session_hash

        self._controller.Write( 'serialisable', session )

        self._controller.pub( 'notify_new_sessions' )

    def SetName( self, name, from_user = False ):

        if from_user:

            name = USER_PAGE_NAME_PREFIX + name

        self._name = name

    def ShowPage( self, showee ):

        for ( i, page ) in enumerate( self._GetPages() ):

            if isinstance( page, wx.Notebook ) and page.HasPage( showee ):

                self.SetSelection( i )

                page.ShowPage( showee )

                break

            elif page == showee:

                self.SetSelection( i )

                break

    def TestAbleToClose( self ):

        for page in self._GetPages():

            page.TestAbleToClose()

    def TIMERPageUpdate( self ):

        pass

class GUISession( HydrusSerialisable.SerialisableBaseNamed ):

    SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_GUI_SESSION
    SERIALISABLE_NAME = 'GUI Session'
    SERIALISABLE_VERSION = 3

    def __init__( self, name ):

        HydrusSerialisable.SerialisableBaseNamed.__init__( self, name )

        self._pages = []

    def _GetPageTuple( self, page ):

        if isinstance( page, PagesNotebook ):

            name = page.GetName()

            page_tuples = [ self._GetPageTuple( subpage ) for subpage in page.GetPages() ]

            return ( 'pages', ( name, page_tuples ) )

        else:

            management_controller = page.GetManagementController()

            hashes = list( page.GetHashes() )

            return ( 'page', ( management_controller, hashes ) )

    def _GetSerialisableInfo( self ):

        def GetSerialisablePageTuple( page_tuple ):

            ( page_type, page_data ) = page_tuple

            if page_type == 'pages':

                ( name, page_tuples ) = page_data

                serialisable_page_tuples = [ GetSerialisablePageTuple( pt ) for pt in page_tuples ]

                serialisable_page_data = ( name, serialisable_page_tuples )

            elif page_type == 'page':

                ( management_controller, hashes ) = page_data

                serialisable_management_controller = management_controller.GetSerialisableTuple()

                serialisable_hashes = [ hash.encode( 'hex' ) for hash in hashes ]

                serialisable_page_data = ( serialisable_management_controller, serialisable_hashes )

            serialisable_tuple = ( page_type, serialisable_page_data )

            return serialisable_tuple

        serialisable_info = []

        for page_tuple in self._pages:

            serialisable_page_tuple = GetSerialisablePageTuple( page_tuple )

            serialisable_info.append( serialisable_page_tuple )

        return serialisable_info

    def _InitialiseFromSerialisableInfo( self, serialisable_info ):

        def GetPageTuple( serialisable_page_tuple ):

            ( page_type, serialisable_page_data ) = serialisable_page_tuple

            if page_type == 'pages':

                ( name, serialisable_page_tuples ) = serialisable_page_data

                page_tuples = [ GetPageTuple( spt ) for spt in serialisable_page_tuples ]

                page_data = ( name, page_tuples )

            elif page_type == 'page':

                ( serialisable_management_controller, serialisable_hashes ) = serialisable_page_data

                management_controller = HydrusSerialisable.CreateFromSerialisableTuple( serialisable_management_controller )

                hashes = [ hash.decode( 'hex' ) for hash in serialisable_hashes ]

                page_data = ( management_controller, hashes )

            page_tuple = ( page_type, page_data )

            return page_tuple

        for serialisable_page_tuple in serialisable_info:

            page_tuple = GetPageTuple( serialisable_page_tuple )

            self._pages.append( page_tuple )

    def _UpdateSerialisableInfo( self, version, old_serialisable_info ):

        if version == 1:

            new_serialisable_info = []

            for ( page_name, serialisable_management_controller, serialisable_hashes ) in old_serialisable_info:

                management_controller = HydrusSerialisable.CreateFromSerialisableTuple( serialisable_management_controller )

                management_controller.SetPageName( page_name )

                serialisable_management_controller = management_controller.GetSerialisableTuple()

                new_serialisable_info.append( ( serialisable_management_controller, serialisable_hashes ) )

            return ( 2, new_serialisable_info )

        if version == 2:

            new_serialisable_info = []

            for ( serialisable_management_controller, serialisable_hashes ) in old_serialisable_info:

                new_serialisable_info.append( ( 'page', ( serialisable_management_controller, serialisable_hashes ) ) )

            return ( 3, new_serialisable_info )

    def AddPage( self, page ):

        page_tuple = self._GetPageTuple( page )

        self._pages.append( page_tuple )

    def GetPages( self ):

        return self._pages

HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_GUI_SESSION ] = GUISession


from qulacs import QuantumCircuit, QuantumState
from qulacs.gate import *
import random
from pwn import *
from Crypto.Cipher import AES
import base64

# CNOT gate
def ADD_CNOT(a, b):
    return "CNOT " + str(a) + "," + str(b) + "; "

# single qubit gates
def ADD_single(s, a):
    return s + " " + str(a) + "; "

# toffoli
def ADD_toffoli(a, b, c):
    ret = ""
    ret += ADD_single("H", c)
    ret += ADD_CNOT(b, c)

    ret += ADD_single("TDAG", c)
    ret += ADD_CNOT(a, c)

    ret += ADD_single("T", c)
    ret += ADD_CNOT(b, c)

    ret += ADD_single("TDAG", c)
    ret += ADD_CNOT(a, c)

    ret += ADD_single("T", b)
    ret += ADD_single("T", c)

    ret += ADD_CNOT(a, b)
    ret += ADD_single("H", c)

    ret += ADD_single("T", a)
    ret += ADD_single("TDAG", b)

    ret += ADD_CNOT(a, b)

    return ret

N = 128
xi, xip = 0.98, 0.98
p = (xi * (1 + xi))**0.5 - xi
Np = int(N * (1 + 2*xi + 2*(xi*(1+xi))**0.5 + xip))

# shor error correction
decoder = ""
decoder += ADD_CNOT(0, 1)
decoder += ADD_CNOT(3, 4)
decoder += ADD_CNOT(6, 7)
decoder += ADD_CNOT(0, 2)
decoder += ADD_CNOT(3, 5)
decoder += ADD_CNOT(6, 8)
decoder += ADD_toffoli(1, 2, 0)
decoder += ADD_toffoli(4, 5, 3)
decoder += ADD_toffoli(7, 8, 6)
decoder += ADD_single("H", 0)
decoder += ADD_single("H", 3)
decoder += ADD_single("H", 6)
decoder += ADD_CNOT(0, 3)
decoder += ADD_CNOT(0, 6)
decoder += ADD_toffoli(3, 6, 0)

r = remote('others.ctf.zer0pts.com', '11099')

def get_bin():
    s = r.recvline()
    s = s.split()[-1].decode()
    return int(s, 2)

r.recvline()
for i in range(0, 860):
    r.sendline(decoder)

measure = get_bin()
bb = (1 << 400) - 1
r.sendline(bin(bb))

ba = get_bin()
xa = get_bin()
m = get_bin()

l = []
for i in range(Np):
    if (ba >> i) & 1 == (bb >> i) & 1 == 0:
        l.append(i)

chosen = []
for i in range(Np):
    if (m & (1 << i)) > 0:
        chosen.append(i)

k = 0
for i in sorted(list(set(l) - set(chosen))):
    k = (k << 1) | ((measure >> i) & 1)

key = int.to_bytes(k, N // 8, 'big')
s = r.recvline()
vv = s.split()[-1]

fin = base64.b64decode(vv)
iv = fin[:16]
ct = fin[16:]

cipher = AES.new(key, AES.MODE_CBC, iv)
print(cipher.decrypt(ct))


# 278. 第一个错误的版本
# 你是产品经理，目前正在带领一个团队开发新的产品。不幸的是，你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的，
# 所以错误的版本之后的所有版本都是错的。
# 假设你有 n 个版本 [1, 2, ..., n]，你想找出导致之后所有版本出错的第一个错误的版本。
# 你可以通过调用 bool isBadVersion(version) 接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。
# 你应该尽量减少对调用 API 的次数。

# The isBadVersion API is already defined for you.
# @param version, an integer
# @return an integer
def isBadVersion(version):
    return version >= 3

def firstBadVersion(n):
    """
    :type n: int
    :rtype: int
    """
    start = 0
    while start < n:
        mid = (start + n) // 2
        if isBadVersion(mid):
            n = mid
        else:
            start = mid + 1
    return start

firstBadVersion(5)

278. 第一个错误的版本.py


from gi.repository import Gtk, GdkPixbuf
from typing import Callable, List
import time

from frames import LightsFrame, WindowFrame, ProjectorFrame, GeneralFrame
from screens import OffScreen

button_filenames = ["light.png", "window.png", "projector.png", "on.png"]
other_frames = [LightsFrame, WindowFrame, ProjectorFrame, GeneralFrame]

class HomeButtonRow(Gtk.Frame):
    def __init__(self, change_other_screen: Callable, change_screen: Callable, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.on = False
        self.current_state_index = len(button_filenames)
        self.change_other_screen = change_other_screen
        self.change_screen = change_screen
        self.__add_elements()
        self.__change_button_border()

    def __change_button_border(self):
        for i in range(len(button_filenames) - 1):
            if i == self.current_state_index:
                self.box.get_children()[i].get_style_context().add_class("active")
            else:
                self.box.get_children()[i].get_style_context().remove_class("active")

    def __on_click(self, index: int):
        if index == len(button_filenames) - 1:
            self.change_screen(OffScreen)
            return

        if index == self.current_state_index:
            self.current_state_index = len(button_filenames) - 1
        else:
            self.current_state_index = index

        self.change_other_screen(other_frames[self.current_state_index])
        self.__change_button_border()

    def __add_elements(self):

        self.box = Gtk.Box()
        self.box.set_homogeneous(True)
        buttons: List[Gtk.Button] = [Gtk.Button() for _ in button_filenames]

        for i, name in enumerate(button_filenames):
            pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(f"images/{name}", 160, 160, True)
            image = Gtk.Image.new_from_pixbuf(pixbuf)

            buttons[i].set_image(image)

        for i in range(len(buttons)):
            buttons[i].connect("clicked", lambda _, x=i: self.__on_click(x))
            self.box.pack_start(buttons[i], True, False, 20)
            buttons[i].show()

        buttons[-1].get_style_context().add_class("red")

        self.add(self.box)
        self.props.name = "home-button-row"
        self.box.show()


#!/usr/bin/python3
import sys
import os
from pprint import pprint
import utils
import cwnd
import goodput
import loss
import rtt

iterations = int(sys.argv[1])
algorithms = [sys.argv[2], sys.argv[3], sys.argv[4]]
average_gp_flow0 = []
average_gp_flow1 = []
average_plr_flow0 = []
average_plr_flow1 = []
average_rtt_flow0 = []
average_rtt_flow1 = []

for algorithm in algorithms:
	#CWND
	time_0, average_cwnd_0, time_1, average_cwnd_1 = cwnd.get_time_and_averages(iterations, algorithm)
	cwnd.plot_flows(time_0, average_cwnd_0, time_1, average_cwnd_1, algorithm)

	#Goodput
	gp_flow0, gp_flow1 = goodput.get_averages(iterations, algorithm, time_lenght = 1)
	gp_flow0 = float("{:.3f}".format(gp_flow0))
	gp_flow1 = float("{:.3f}".format(gp_flow1))
	average_gp_flow0.append(gp_flow0)
	average_gp_flow1.append(gp_flow1)

	#Packet Loss Rate
	plr_flow0, plr_flow1 = loss.get_averages(iterations, algorithm)
	plr_flow0 = float("{:.4f}".format(plr_flow0))
	plr_flow1 = float("{:.4f}".format(plr_flow1))
	average_plr_flow0.append(plr_flow0)
	average_plr_flow1.append(plr_flow1)

	#Round Trip Time Rate
	average_rtt_0, average_rtt_1 = rtt.get_averages(iterations, algorithm)
	rtt.plot_flows(average_rtt_0, average_rtt_1, algorithm)

	print("--------------------------\n", algorithm,"\n Goodputs:", gp_flow0, "Mb/s", gp_flow1, "Mb/s",
	"\n Loss rates:", plr_flow0, "%", plr_flow1, "%")

utils.bar_plot(average_gp_flow0, average_gp_flow1, algorithms,
	title="Goodputs on flows and algorithms", address="img/goodput/bar.png", ylabel="Goodputs (Mb/s)")

utils.bar_plot(average_plr_flow0, average_plr_flow1, algorithms,
	title="Packet Loss on flows and algorithms", address="img/loss/bar.png", ylabel="Loss Rate (%)")


import unittest

import pyAgrum as gum
from .pyAgrumTestSuite import pyAgrumTestCase, addTests
import pyAgrum.causal as csl

class TestSimpson(pyAgrumTestCase):
  def setUp(self):
    m1 = gum.fastBN("Gender->Drug->Patient;Gender->Patient")

    m1.cpt("Gender")[:] = [0.5, 0.5]
    m1.cpt("Drug")[:] = [[0.25, 0.75],  # Gender=0
                         [0.75, 0.25]]  # Gender=1

    m1.cpt("Patient")[{'Drug': 0, 'Gender': 0}] = [
      0.2, 0.8]  # No Drug, Male -> healed in 0.8 of cases
    # No Drug, Female -> healed in 0.4 of cases
    m1.cpt("Patient")[{'Drug': 0, 'Gender': 1}] = [0.6, 0.4]
    m1.cpt("Patient")[{'Drug': 1, 'Gender': 0}] = [
      0.3, 0.7]  # Drug, Male -> healed 0.7 of cases
    m1.cpt("Patient")[{'Drug': 1, 'Gender': 1}] = [
      0.8, 0.2]  # Drug, Female -> healed in 0.2 of cases
    self.model = csl.CausalModel(m1)

  def test_CausalImpactWithoutObservation(self):
    evs = {}
    latex, impact, explain = csl.causalImpact(
      self.model, "Patient", doing="Drug", knowing=evs)

  def test_CausalImpactWithObservation(self):
    evs = {'Gender'}
    latex, impact, explain = csl.causalImpact(
      self.model, "Patient", doing="Drug", knowing=evs)

class TestFromUsers(pyAgrumTestCase):
  def test_AccentsInVariables(self):
    bn = gum.fastBN("héhé->hoho")
    cm = csl.CausalModel(bn)
    formula, impact, explanation = csl.causalImpact(cm, "héhé", "hoho")

  def test_from_Musfiqshohan(self):
    cm = csl.CausalModel(gum.fastBN('A->B->C->F;'
                                    'A->D;'
                                    'A->E;'
                                    'C->D->E->F;'
                                    'C->E;'
                                    ), [
                           ('U0', ('C', 'F')),
                           ('U1', ('C', 'E')),
                           ('U2', ('E', 'F'))
                         ], keepArcs=True)
    csl.causalImpact(cm, on={'E'}, doing={'A', 'D', 'B'})

class TestFromR(pyAgrumTestCase):
  def setUp(self):
    m = gum.fastBN("z2->x->z1->y;z2->z1;z2->z3->y")

    m.cpt("z2")[:] = [0.5, 0.5]
    m.cpt("x")[:] = [[0.4, 0.6],  # z2=0
                     [0.4, 0.6]]  # z2=1
    m.cpt("z3")[:] = [[0.3, 0.7],  # z2=0
                      [0.3, 0.7]]  # z2=1
    m.cpt("z1")[{"z2": 0, "x": 0}] = [0.2, 0.8]
    m.cpt("z1")[{"z2": 0, "x": 1}] = [0.25, 0.75]
    m.cpt("z1")[{"z2": 1, "x": 0}] = [0.1, 0.9]
    m.cpt("z1")[{"z2": 1, "x": 1}] = [0.15, 0.85]

    m.cpt("y")[{"z1": 0, "z3": 0}] = [0.5, 0.5]
    m.cpt("y")[{"z1": 0, "z3": 1}] = [0.45, 0.55]
    m.cpt("y")[{"z1": 1, "z3": 0}] = [0.4, 0.6]
    m.cpt("y")[{"z1": 1, "z3": 1}] = [0.35, 0.65]

    self.d = csl.CausalModel(m, [("X-Z2", ["x", "z2"]),
                                 ("X-Z3", ["x", "z3"]),
                                 ("X-Y", ["x", "y"]),
                                 ("Y-Z2", ["y", "z2"])],
                             True)
    try:
      formula, result, msg = csl.causalImpact(
        self.d, on={"y", "z2", "z1", "z3"}, doing={"x"})
    except csl.HedgeException as h:
      self.fail("Should not raise")

class TestsFromGumWhy(pyAgrumTestCase):
  def test_Carouselp115(self):
    ab = gum.fastBN('Elapsed time[11]->Bag on Carousel<-Bag on Plane')
    ab.cpt("Bag on Plane").fillWith(1).normalize()
    ab.cpt("Elapsed time").fillWith(1).normalize()
    ab.cpt("Bag on Carousel").fillWith(
      [1.0, 0.0] * 11 + [1 - i / 20 if i % 2 == 0 else (i - 1) / 20 for i in range(22)])
    abModele = csl.CausalModel(ab)
    formula, impact, explanation = csl.causalImpact(abModele, on={"Bag on Plane"}, doing={
      "Elapsed time"}, knowing={"Bag on Carousel"}, values={"Elapsed time": 7, "Bag on Carousel": 0})
    self.assertAlmostEqual(impact[0], 0.7692, 4)

  def test_DoCalculusp213(self):
    fd = gum.fastBN("w->z->x->y")
    fdModele = csl.CausalModel(
      fd, [("u1", ["w", "x"]), ("u2", ["w", "y"])], True)
    formula, impact, explanation = csl.causalImpact(
      fdModele, on="y", doing="x")

ts = unittest.TestSuite()
addTests(ts, TestSimpson)
addTests(ts, TestFromUsers)
addTests(ts, TestFromR)
addTests(ts, TestsFromGumWhy)


import os
import requests
import urllib3
import unidecode
import time

from selenium import webdriver
from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import Select
from selenium.webdriver.chrome.options import Options

apiURL = "https://www.rad.cvm.gov.br/ENET/frmDownloadDocumento.aspx?Tela=ext"
initialURL = "https://cvmweb.cvm.gov.br/SWB/Sistemas/SCW/CPublica/CiaAb/FormBuscaCiaAb.aspx?TipoConsult=c"
validTypes = ["AGO","AGO/E","AGE"]
invalidSpecimens = ["manual para participacao","justificacao de incorporacao, fusao ou cisao","protocolo de incorporacao, fusao ou cisao","protocolo e justificativa de incorporacao, fusao ou cisao","protocolo e justificacao de incorporacao, fusao ou cisao","edital de convocacao"]
dictionary = {
    "ata": "ATA",
    "boletim de voto a distancia":"BVD",
    "mapa consolidado de voto a distancia":"MPBVD",
    "mapa de depositario central":"MPDC",
    "mapa do escriturador":"MPESC",
    "mapa final de votacao":"MPFIN",
    "mapa final de votacao detalhado":"MPFDET",
    "mapa final de votacao sintetico":"MPFSIN",
    "material referente a pedidos publicos de procuracao":"PPP",
    "proposta da administracao":"PROPAD",
    "sumario das decisoes":"SUM"
}

firefox = webdriver.Firefox()
wait = WebDriverWait(firefox,50)
downloadedFiles = []

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

# Transforma cada linha válida da tabela no nome de arquivo especificado pela
# minha contratante

def TableRowDataToFileName(companyCode,documentType,specimenCode,date,status,version,category):
    companyCode = ''.join(companyCode.split('-'))

    documentType = ''.join(documentType.split('/'))
    try:
        specimenCode = dictionary[unidecode.unidecode(specimenCode.lower())]
    except KeyError:
        print ("|"+unidecode.unidecode(specimenCode.lower())+"|")
        specimenCode = "ERROR"

    dateArray = date.split()
    dateArray[0] = dateArray[0].split('/')[::-1]

    if (len(dateArray[0][0]) is 2):
        dateArray[0][0] = "20" + dateArray[0][0]
    if (len(dateArray[0][1]) is 1):
        dateArray[0][1] = '0' + dateArray[0][1]
    if (len(dateArray[0][2]) is 1):
        dateArray[0][2] = '0' + dateArray[0][2]

    dateArray[0] = ''.join(dateArray[0])
    dateArray[1] = ''.join(dateArray[1].split(':'))
    date = ''.join(dateArray)

    status = status[0]

    finalIterable = (companyCode,documentType,specimenCode,date,status,version,category)

    return '_'.join(finalIterable)

# Escolhida a empresa, essa função seleciona o periodo em que os documentos devem ser
# pesquisados e submete a pesquisa

def queryCVM():

    # Parte onde temos o formulario para a empresa escolhida
    wait.until(EC.invisibility_of_element_located((By.ID,"divLoading")))
    # wait.until(EC.presence_of_element_located((By.ID, "rdPeriodo")))
    wait.until(EC.invisibility_of_element_located((By.ID,"divSplash")))

    periodRadio = firefox.find_element_by_id("rdPeriodo")
    periodRadio.click()

    wait.until(EC.visibility_of_element_located((By.ID, "txtDataIni")))

    txtDataIni = firefox.find_element_by_id("txtDataIni")
    txtDataIni.send_keys("05/01/2017")
    txtHoraIni = firefox.find_element_by_id("txtHoraIni")
    txtHoraIni.send_keys("00:00")

    cboCategoria = firefox.find_elements_by_tag_name("select")[0]
    try:
        Select(cboCategoria).select_by_visible_text("Assembleia")
    except NoSuchElementException:
        return 0

    wait.until(EC.invisibility_of_element_located((By.ID,"divSplash")))
    btnConsulta = firefox.find_element_by_id("btnConsulta")
    btnConsulta.click()
    wait.until(EC.invisibility_of_element_located((By.ID,"divSplash")))
    return 1

# Preenche o nome da empresa no campo inicial do script
def fillCompanyName(companyID):

    firefox.get(initialURL)
    inputField = firefox.find_element_by_id("txtCNPJNome")
    inputField.send_keys(companyID)
    inputField.send_keys(Keys.ENTER)

# Dado o resultado de fillCompanyName(), pego o primeiro resultado valido e clico nele
def findFirstValidCompany():
    wait.until_not(EC.presence_of_element_located((By.ID,"txtCNPJNome")))
    try:
        if (EC.presence_of_element_located((By.ID, "lblMsg"))):
            possibleError = firefox.find_element_by_id("lblMsg")
            if "Nenhuma companhia foi encontrada com o critério de busca especificado" in possibleError.text:
                return 0
    except NoSuchElementException:
        print("Regular")
    except StaleElementReferenceException:
        print('Regular')
    wait.until(EC.presence_of_element_located((By.ID, "dlCiasCdCVM")))
    # Parte onde temos a tabela de empresas possíveis
    tableRows = firefox.find_elements_by_tag_name("tr")
    found = 0
    for row in tableRows:
        col = row.find_elements_by_tag_name("td")
        if "Concedido" in col[4].text:
            col[4].find_element_by_tag_name("a").click()
            found = 1
            break
    if (not found):
        return 0
    return 1

# Baixo os arquivos no formato desejado para a pasta out
def DownloadFilesFromResultTable():
    paginationText = firefox.find_element_by_id("grdDocumentos_info")
    text = paginationText.text.split()
    paginationNumberString = text[len(text) - 2]
    paginationNumber = int( int(paginationNumberString) / 100 ) + 1
    for i in range(0,paginationNumber):
        wait.until(EC.visibility_of_element_located((By.CLASS_NAME,"fi-download")))
        resultRows = firefox.find_elements_by_tag_name("tr")
        GetValidDocs(resultRows)
        nextButton = firefox.find_element_by_id("grdDocumentos_next")
        if ("disabled" not in nextButton.get_attribute("class")):
            nextButton.click()

# Função que verifica a validade do documento baseado no critério de escolha da contratante
def ValidateDocumentCriteria(informationRow):
    typeValidation = informationRow[3].text in validTypes
    specimenValidation = unidecode.unidecode(informationRow[4].text.lower()) not in invalidSpecimens
    active = "Ativo" in informationRow[7].text

    return (typeValidation and specimenValidation and active)

# Percorre a tabela baixando os arquivos válidos
def GetValidDocs(resultRows):
    validCount = 0
    invalidCount = 0
    for row in resultRows:
        if (len(row.find_elements_by_tag_name("td")) < 10):
            continue
        rowData = row.find_elements_by_tag_name("td")
        if (ValidateDocumentCriteria(rowData)):
            validCount += 1
            filename = TableRowDataToFileName(rowData[0].text,rowData[3].text,rowData[4].text,rowData[6].text,rowData[7].text,rowData[8].text,rowData[9].text)
            fileLink = row.find_element_by_class_name('fi-download')
            fileData = fileLink.get_attribute('onclick').rstrip().split(',')
            if (not DownloadFile(fileData[0],fileData[1],fileData[2],fileData[3],filename)):
                print("Erro no download do arquivo " + filename)

        else:
            invalidCount += 1

def fileLen(fname):
    with open(fname) as f:
        for i, l in enumerate(f):
            pass
    return i + 1

# Pequena abstração. Ao invés de clicar no botão de download, pego os parametros passados pro botão e trato eles para construir minha própria
# requisição para a API da CVM. Isso me garante mais controle no download e mudança de nome de arquivo, porque posso usar requests.get e
# mudar o nome diretamente, ao invés de baixar pelo navegador
def DownloadFile(numSequencia, numVersao, numProtocolo, descTipo, endFileName):
    url = apiURL + "&numSequencia=" + numSequencia + "&numVersao=" + numVersao + "&numProtocolo=" + numProtocolo + "&descTipo=" + descTipo + "&CodigoInstituicao=1"
    try:
        endFile = requests.get( url, allow_redirects=True, verify=False)
    except requests.ConnectionError:
        return 0
    except ConnectionResetError:
        return 0
    file = open(endFileName,"wb")
    file.write(endFile.content)
    return 1

# Estrutura geral do código
def DownloadDocumentsByCompanyName(companyID):
    fillCompanyName(companyID)
    if (not findFirstValidCompany()):
        return 0
    if (not queryCVM()):
        return 0
    DownloadFilesFromResultTable()
    return 1

def main():
    companyFile = open("actual.csv","r")
    size = fileLen("./actual.csv")
    os.chdir('./out')
    i = 0
    for company in companyFile:
        i += 1
        print( str( (i/size)*100 ) + "%")
        print ("CNPJ: "+ company)
        if DownloadDocumentsByCompanyName(company):
            print("Resultados encontrados")
        else:
            print("Não foram encontrados resultados")

main()


import json
import re

import pandas as pd

with open('20191116_wawa.json', 'r') as file:
    data = json.load(file)

data_clean = []

raw_any_num = re.compile(r"(\d+,\d+) m|\d+ m")
raw_number = re.compile(r"\d+")
raw_inside_strong = re.compile(r"(\w+|\w+\s\w+)")
raw_inside_strong_prop = re.compile(r"(...+)")
raw_district = re.compile(r".*?,\s*(w*?)(...+)")

for record in data:

    #Cleaning the title data
    title_clean = record["title"]

    #Cleaning the district data
    district = record["district"]

    #Cleaning the prices data
    price = record["price"].strip(" zł").replace(" ", "").replace(",", ".")
    if price == "Zapytajocenę":
         price_clean = None
    elif price.endswith("€"):
         price_clean = float(price.strip("€")) * 4.288
    else:
         price_clean = float(price)

    #Clean the rent data
    rent = list(filter(re.compile(r"Czynsz: ").search, record["bag"]))
    if (len(rent) < 1) or (rent is None):
        rent_clean = None
    else:
        rent_clean = (float(raw_number.search(rent[-1]).group().strip(" zł").replace(",", ".")))

    #Clean the area data
    area = list(filter(re.compile(r"Powierzchnia: ").search, record["bag"]))
    if (len(area) < 1) or (area is None):
        area_clean = None
    else:
        area_clean = (float(raw_any_num.search(area[-1]).group().strip(" m").replace(",", ".")))

    #Clean the room data
    chamber = list(filter(re.compile(r"Liczba pokoi: ").search, record["bag"]))
    if (len(chamber) < 1) or (chamber is None):
        chamber_clean = None
    else:
        chamber_clean = int(raw_number.search(chamber[-1]).group())

    #Clean the market data
    market = list(filter(re.compile(r"Rynek: ").search, record["bag"]))
    if (len(market) < 1) or (market is None):
        market_clean = None
    else:
       market_clean = raw_inside_strong.search(market[-1]).group(1)

    #Clean the building type data
    building = list(filter(re.compile(r"Rodzaj zabudowy: ").search, record["bag"]))
    if (len(building) < 1) or (building is None):
        building_clean = None
    else:
        building_clean = raw_inside_strong.search(building[-1]).group(1)

    #Clean the floor data
    floor = list(filter(re.compile(r"Piętro: ").search, record["bag"]))
    if (len(floor) < 1) or (floor is None) or ("poddasze" in floor[0]):
        floor_clean = None
    else:
        if ">" in floor[0]:
            floor_clean = 11
        elif "parter" in floor[0]:
            floor_clean = 0
        elif "suterena" in floor[0]:
            floor_clean = 0
        else:
            floor_clean = int(raw_inside_strong.search(floor[-1]).group(1))

    #Clean the building floor number data
    building_floors = list(filter(re.compile(r"Liczba pięter: ").search, record["bag"]))
    if (len(building_floors) < 1) or (building_floors is None):
        building_floors_clean = None
    else:
        building_floors_clean = int(raw_inside_strong.search(building_floors[-1]).group(1))

    #Clean the construction year data
    construction_year = list(filter(re.compile(r"Rok budowy: ").search, record["bag"]))
    if (len(construction_year) < 1) or (construction_year is None):
        construction_year_clean = None
    else:
        construction_year_clean = int(raw_inside_strong.search(construction_year[-1]).group(1))

    #Clean the standard data
    standard = list(filter(re.compile(r"Stan wykończenia: ").search, record["bag"]))
    if (len(standard) < 1) or (standard is None):
        standard_clean = None
    else:
        standard_clean = raw_inside_strong.search(standard[-1]).group(1)

    #Clean the property data
    property = list(filter(re.compile(r"Forma własności: ").search, record["bag"]))
    if (len(property) < 1) or (property is None):
        property_clean = None
    else:
        property_clean = raw_inside_strong_prop.search(property[-1]).group(1)

    #Clean the window data
    window = list(filter(re.compile(r"Okna: ").search, record["bag"]))
    if (len(window) < 1) or (window is None):
        window_clean = None
    else:
        window_clean = raw_inside_strong.search(window[-1]).group(1)

    #Clean the heating data
    heating = list(filter(re.compile(r"Ogrzewanie: ").search, record["bag"]))
    if (len(heating) < 1) or (heating is None):
        heating_clean = None
    else:
        heating_clean = raw_inside_strong.search(heating[-1]).group(1)

    #Colect the data
    item = {
    "district": district,
    "area": area_clean,
    "chamber": chamber_clean,
    "market": market_clean,
    "building": building_clean,
    "floor": floor_clean,
    "building_floors": building_floors_clean,
    "construction_year": construction_year_clean,
    "standard": standard_clean,
    "property": property_clean,
    "window": window_clean,
    "heating": heating_clean,
    "url": record["url"],
    "rent": rent_clean,
    "price": price_clean,
    }

    data_clean.append(item)

clean_df = pd.DataFrame(data_clean)

clean_df.to_csv(f"20191117_data_clean_wawa.csv", index=False)

print("Processing task finished")


# Задана натуральная степень k. Сформировать случайным образом список коэффициентов
# (значения от 0 до 100) многочлена и записать в файл многочлен степени k.

#Пример:
#- k=2 => 2*x² + 4*x + 5 = 0 или x² + 5 = 0 или 10*x² = 0
import random
flag = True
num = int(input('Введите коэффециент: '))
f = open('mnogochlen.txt','a')
while flag:
    rand = random.randint(0, 100)
    if num == 0:
        if rand == 0:
            f.write('')
        else:
            f.write(f'{rand}')
            num -= 1
    elif num == -1:
        f.write(' = 0\n')
        flag = False
    else:
        if rand == 0:
            f.write('')
        else:
            if num == 1:
                f.write(f'{rand}x + ')
                num -= 1
            else:
                f.write(f'{rand}x^{num} + ')
                num -= 1
f.close()


""" EoM of 3D double pendulum with the second mass distributed """
from sympy import symbols, Function, Eq, Matrix
from sympy import sin, cos
from sympy import diff, simplify, expand, solve, factor
from sympy import init_printing, pprint, latex

init_printing()

""" Define Symbols """
l1, l2, m1, m2 = symbols("l1 l2 m1 m2")
Ixx, Iyy, Izz = symbols("Ixx, Iyy, Izz")
t = symbols("t")
g = symbols("g")
L, Pot, Kin = symbols("L Pot Kin")
theta11 = Function("theta11")(t)
theta12 = Function("theta12")(t)
theta21 = Function("theta21")(t)
theta22 = Function("theta22")(t)
theta23 = Function("theta23")(t)
u11, u12, udot11, udot12 = symbols("u11 u12 udot11 udot12")
u21, u22, u23, udot21, udot22, udot23 = symbols("u21 u22 u23 udot21 udot22 udot23")
x1, y1, z1, x2, y2, z2 = symbols("x1 y1 z1 x2 y2 z1")
q = [theta11, theta12, theta21, theta22, theta23]
substitutions = [
    (theta11.diff(t).diff(t), udot11),
    (theta12.diff(t).diff(t), udot12),
    (theta21.diff(t).diff(t), udot21),
    (theta22.diff(t).diff(t), udot22),
    (theta23.diff(t).diff(t), udot23),
    (theta11.diff(t), u11),
    (theta12.diff(t), u12),
    (theta21.diff(t), u21),
    (theta22.diff(t), u22),
    (theta23.diff(t), u23),
]

""" Define Kinematic Relations """
x1 = l1 * sin(theta11)
y1 = l1 * cos(theta11) * sin(theta12)
z1 = -l1 * cos(theta11) * cos(theta12)
x2 = x1 + l2 * sin(theta21)
y2 = y1 + l2 * cos(theta21) * sin(theta22)
z2 = z1 - l2 * cos(theta21) * cos(theta22)

""" Equations for omega from cog-rotations.py """
omega_x = -theta21.diff(t) * (
    -sin(theta21) * sin(theta22) * cos(theta23) - sin(theta23) * cos(theta21)
) + theta22.diff(t) * (
    -sin(theta21) * sin(theta22) * sin(theta23) + cos(theta21) * cos(theta23)
)
omega_y = -theta21.diff(t) * cos(theta22) * cos(theta23) + theta22.diff(t) * sin(
    theta23
) * cos(theta22)
omega_z = (
    -theta21.diff(t)
    * (-sin(theta21) * sin(theta23) + sin(theta22) * cos(theta21) * cos(theta23))
    + theta22.diff(t)
    * (sin(theta21) * cos(theta23) + sin(theta22) * sin(theta23) * cos(theta21))
    + theta23.diff(t)
)

""" Langrangian """
Pot = m1 * g * z1 + m2 * g * z2
Kin = (
    1 / 2 * m1 * (x1.diff(t) ** 2 + y1.diff(t) ** 2 + z1.diff(t) ** 2)
    + 1 / 2 * m2 * (x2.diff(t) ** 2 + y2.diff(t) ** 2 + z2.diff(t) ** 2)
    + 1 / 2 * Ixx * omega_x ** 2
    + 1 / 2 * Iyy * omega_y ** 2
    + 1 / 2 * Izz * omega_z ** 2
)
L = Kin - Pot

euleq = [Eq(0, L.diff(qi.diff(t)).diff(t) - L.diff(qi)) for qi in q]
euleq = [euleqi.subs(substitutions).expand() for euleqi in euleq]

solvedeuleq = solve(
    euleq, [udot11, udot12, udot21, udot22, udot23], simplify=True, rational=False
)

for ddot, eqn in solvedeuleq.items():
    print(ddot, eqn)