import typing

from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusData
from hydrus.core import HydrusGlobals as HG
from hydrus.core import HydrusSerialisable
from hydrus.core import HydrusTime

from hydrus.client import ClientConstants as CC

def FilterOutRedundantMetaServices( list_of_service_keys: typing.List[ bytes ] ):
    
    services_manager = HG.client_controller.services_manager
    
    special_local_file_service_keys = { CC.TRASH_SERVICE_KEY, CC.COMBINED_LOCAL_MEDIA_SERVICE_KEY, CC.LOCAL_UPDATE_SERVICE_KEY }
    
    if len( special_local_file_service_keys.intersection( list_of_service_keys ) ) <= 1:
        
        if CC.COMBINED_LOCAL_FILE_SERVICE_KEY in list_of_service_keys:
            
            list_of_service_keys.remove( CC.COMBINED_LOCAL_FILE_SERVICE_KEY )
            
        
    
    local_file_service_keys = set( services_manager.GetServiceKeys( ( HC.LOCAL_FILE_DOMAIN, ) ) )
    
    if len( local_file_service_keys.intersection( list_of_service_keys ) ) <= 1:
        
        if CC.COMBINED_LOCAL_MEDIA_SERVICE_KEY in list_of_service_keys:
            
            list_of_service_keys.remove( CC.COMBINED_LOCAL_MEDIA_SERVICE_KEY )
            
        
    
    return list_of_service_keys
    

def GetPossibleFileDomainServicesInOrder( all_known_files_allowed: bool, only_local_file_domains_allowed: bool ):
    
    services_manager = HG.client_controller.services_manager
    
    service_types_in_order = [ HC.LOCAL_FILE_DOMAIN ]
    
    if not only_local_file_domains_allowed:
        
        advanced_mode = HG.client_controller.new_options.GetBoolean( 'advanced_mode' )
        
        if len( services_manager.GetServices( ( HC.LOCAL_FILE_DOMAIN, ) ) ) > 1 or advanced_mode:
            
            service_types_in_order.append( HC.COMBINED_LOCAL_MEDIA )
            
        
        service_types_in_order.append( HC.LOCAL_FILE_TRASH_DOMAIN )
        
        if advanced_mode:
            
            service_types_in_order.append( HC.LOCAL_FILE_UPDATE_DOMAIN )
            
        
        if advanced_mode:
            
            service_types_in_order.append( HC.COMBINED_LOCAL_FILE )
            
            service_types_in_order.append( HC.COMBINED_DELETED_FILE )
            
        
        service_types_in_order.append( HC.FILE_REPOSITORY )
        service_types_in_order.append( HC.IPFS )
        
        if all_known_files_allowed:
            
            service_types_in_order.append( HC.COMBINED_FILE )
            
        
    
    services = services_manager.GetServices( service_types_in_order )
    
    return services
    

def SortFileServiceKeysNicely( list_of_service_keys ):
    
    services_in_nice_order = GetPossibleFileDomainServicesInOrder( False, False )
    
    service_keys_in_nice_order = [ service.GetServiceKey() for service in services_in_nice_order ]
    
    list_of_service_keys = [ service_key for service_key in service_keys_in_nice_order if service_key in list_of_service_keys ]
    
    return list_of_service_keys
    

def ValidLocalDomainsFilter( service_keys ):
    
    return [ service_key for service_key in service_keys if HG.client_controller.services_manager.ServiceExists( service_key ) and HG.client_controller.services_manager.GetServiceType( service_key ) == HC.LOCAL_FILE_DOMAIN ]
    
class LocationContext( HydrusSerialisable.SerialisableBase ):
    
    SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_LOCATION_CONTEXT
    SERIALISABLE_NAME = 'Location Search Context'
    SERIALISABLE_VERSION = 1
    
    def __init__( self, current_service_keys = None, deleted_service_keys = None ):
        
        # note this is pretty much a read-only class
        # sometimes we'll run FixMissingServices, but usually only on load and who cares if that fix is propagated around
        # hence no need to duplicate this for every handler, since it won't be changing
        
        if current_service_keys is None:
            
            current_service_keys = []
            
        
        if deleted_service_keys is None:
            
            deleted_service_keys = []
            
        
        self.current_service_keys = frozenset( current_service_keys )
        self.deleted_service_keys = frozenset( deleted_service_keys )
        
        if self.IsAllKnownFiles():
            
            self.current_service_keys = frozenset( [ CC.COMBINED_FILE_SERVICE_KEY ] )
            self.deleted_service_keys = frozenset()
            
        
    
    def __eq__( self, other ):
        
        if isinstance( other, LocationContext ):
            
            return self.__hash__() == other.__hash__()
            
        
        return NotImplemented
        
    
    def __hash__( self ):
        
        # works because frozenset
        return ( self.current_service_keys, self.deleted_service_keys ).__hash__()
        
    
    def _GetSerialisableInfo( self ):
        
        serialisable_current_service_keys = [ service_key.hex() for service_key in self.current_service_keys ]
        serialisable_deleted_service_keys = [ service_key.hex() for service_key in self.deleted_service_keys ]
        
        return ( serialisable_current_service_keys, serialisable_deleted_service_keys )
        
    
    def _InitialiseFromSerialisableInfo( self, serialisable_info ):
        
        ( serialisable_current_service_keys, serialisable_deleted_service_keys ) = serialisable_info
        
        self.current_service_keys = frozenset( { bytes.fromhex( service_key ) for service_key in serialisable_current_service_keys } )
        self.deleted_service_keys = frozenset( { bytes.fromhex( service_key ) for service_key in serialisable_deleted_service_keys } )
        
    
    def ClearSurplusLocalFilesServices( self, service_type_func: typing.Callable ):
        # if we have combined local files, then we don't need specific local domains
        
        if CC.COMBINED_LOCAL_FILE_SERVICE_KEY in self.current_service_keys:
            
            self.current_service_keys = frozenset( ( service_key for service_key in self.current_service_keys if service_type_func( service_key ) not in HC.FILE_SERVICES_COVERED_BY_COMBINED_LOCAL_FILE ) )
            
        
        if CC.COMBINED_LOCAL_FILE_SERVICE_KEY in self.deleted_service_keys:
            
            self.deleted_service_keys = frozenset( ( service_key for service_key in self.deleted_service_keys if service_type_func( service_key ) not in HC.FILE_SERVICES_COVERED_BY_COMBINED_LOCAL_FILE ) )
            
        
        if CC.COMBINED_LOCAL_MEDIA_SERVICE_KEY in self.current_service_keys:
            
            self.current_service_keys = frozenset( ( service_key for service_key in self.current_service_keys if service_type_func( service_key ) not in HC.FILE_SERVICES_COVERED_BY_COMBINED_LOCAL_MEDIA ) )
            
        
        if CC.COMBINED_LOCAL_MEDIA_SERVICE_KEY in self.deleted_service_keys:
            
            self.deleted_service_keys = frozenset( ( service_key for service_key in self.deleted_service_keys if service_type_func( service_key ) not in HC.FILE_SERVICES_COVERED_BY_COMBINED_LOCAL_MEDIA ) )
            
        
    
    def FixMissingServices( self, services_exist_func: typing.Callable ):
        
        self.current_service_keys = frozenset( services_exist_func( self.current_service_keys ) )
        self.deleted_service_keys = frozenset( services_exist_func( self.deleted_service_keys ) )
        
    
    def GetCoveringCurrentFileServiceKeys( self ):
        
        file_location_is_cross_referenced = not ( self.IsAllKnownFiles() or self.IncludesDeleted() )
        
        file_service_keys = list( self.current_service_keys )
        
        if self.IncludesDeleted():
            
            file_service_keys.append( CC.COMBINED_DELETED_FILE_SERVICE_KEY )
            
        
        return ( file_service_keys, file_location_is_cross_referenced )
        
    
    def GetDeletedInverse( self ):
        
        inverse = self.Duplicate()
        
        a = inverse.current_service_keys
        inverse.current_service_keys = inverse.deleted_service_keys
        inverse.deleted_service_keys = a
        
        return inverse
        
    
    def GetStatusesAndServiceKeysList( self ):
        
        statuses_and_service_keys = [ ( HC.CONTENT_STATUS_CURRENT, service_key ) for service_key in self.current_service_keys ]
        statuses_and_service_keys.extend( [ ( HC.CONTENT_STATUS_DELETED, service_key ) for service_key in self.deleted_service_keys ] )
        
        return statuses_and_service_keys
        
    
    def IncludesCurrent( self ):
        
        return len( self.current_service_keys ) > 0
        
    
    def IncludesDeleted( self ):
        
        return len( self.deleted_service_keys ) > 0
        
    
    def IsAllKnownFiles( self ):
        
        return CC.COMBINED_FILE_SERVICE_KEY in self.current_service_keys
        
    
    def IsAllLocalFiles( self ):
        
        return self.IsOneDomain() and CC.COMBINED_LOCAL_FILE_SERVICE_KEY in self.current_service_keys
        
    
    def IsAllMediaFiles( self ):
        
        return self.IsOneDomain() and CC.COMBINED_LOCAL_MEDIA_SERVICE_KEY in self.current_service_keys
        
    
    def IsEmpty( self ):
        
        return len( self.current_service_keys ) + len( self.deleted_service_keys ) == 0
        
    
    def IsOneDomain( self ):
        
        return len( self.current_service_keys ) + len( self.deleted_service_keys ) == 1
        
    
    def LimitToServiceTypes( self, service_type_func: typing.Callable, service_types ):
        
        self.current_service_keys = frozenset( ( service_key for service_key in self.current_service_keys if service_type_func( service_key ) in service_types ) )
        
        self.deleted_service_keys = frozenset( ( service_key for service_key in self.deleted_service_keys if service_type_func( service_key ) in service_types ) )
        
    
    def ToString( self, name_method ):
        
        # this probably needs some params for 'short string' and stuff later on
        
        if self.IsEmpty():
            
            return 'nothing'
            
        
        if self.IncludesCurrent() and self.IncludesDeleted():
            
            if self.current_service_keys == self.deleted_service_keys:
                
                prefix = 'current and deleted files of '
                
            else:
                
                prefix = 'a mix of current and deleted files of '
                
            
        elif self.IncludesDeleted():
            
            prefix = 'deleted files of '
            
        else:
            
            prefix = ''
            
        
        if self.current_service_keys == self.deleted_service_keys:
            
            service_keys_to_consider = self.current_service_keys
            
        else:
            
            service_keys_to_consider = self.current_service_keys.union( self.deleted_service_keys )
            
        
        if len( service_keys_to_consider ) <= 2:
            
            service_strings = sorted( ( name_method( service_key ) for service_key in service_keys_to_consider ) )
            
            service_string = ', '.join( service_strings )
            
        else:
            
            service_string = '{} services'.format( HydrusData.ToHumanInt( len( service_keys_to_consider ) ) )
            
        
        return prefix + service_string
        
    
    def ToDictForAPI( self ):

        return {
            'current_service_keys' : [ service_key.hex() for service_key in self.current_service_keys ],
            'deleted_service_keys' : [ service_key.hex() for service_key in self.deleted_service_keys ]
        }
        
    
    @staticmethod
    def STATICCreateAllCurrent( current_service_keys ) -> "LocationContext":
        
        return LocationContext( current_service_keys, [] )
        
    
    @staticmethod
    def STATICCreateSimple( file_service_key ) -> "LocationContext":
        
        return LocationContext( [ file_service_key ], [] )
        
    
HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_LOCATION_CONTEXT ] = LocationContext

