import collections
import dircache
import hashlib
import httplib
import ClientConstants as CC
import HydrusConstants as HC
import HydrusImageHandling
import HydrusMessageHandling
import multipart
import os
import random
import sqlite3
import threading
import time
import traceback
import yaml
import wx
import zlib

class Conversation( object ):
    
    def __init__( self, identity, conversation_key, subject, messages, drafts, search_context ):
        
        self._identity = identity
        self._conversation_key = conversation_key
        self._subject = subject
        self._messages = messages
        self._drafts = drafts
        self._search_context = search_context
        
        HC.pubsub.sub( self, 'DeleteDraft', 'delete_draft_data' )
        HC.pubsub.sub( self, 'DeleteMessage', 'delete_message' )
        HC.pubsub.sub( self, 'DraftSaved', 'draft_saved' )
        HC.pubsub.sub( self, 'ArchiveConversation', 'archive_conversation_data' )
        HC.pubsub.sub( self, 'InboxConversation', 'inbox_conversation_data' )
        HC.pubsub.sub( self, 'UpdateMessageStatuses', 'message_statuses_data' )
        
    
    def AddDraft( self, draft ): self._drafts.append( draft )
    
    def AddMessage( self, message ): self._messages.append( message )
    
    def ArchiveConversation( self, conversation_key ):
        
        if conversation_key == self._conversation_key:
            
            self._inbox = False
            
            for message in self._messages: message.Archive()
            
        
    
    def DeleteDraft( self, draft_key ):
        
        self._drafts = [ draft for draft in self._drafts if draft.GetDraftKey() != draft_key ]
        
        if len( self._messages ) + len( self._drafts ) == 0:
            
            HC.pubsub.pub( 'delete_conversation_data', self._conversation_key )
            HC.pubsub.pub( 'delete_conversation_gui', self._conversation_key )
            
        
    
    def DraftSaved( self, draft_key, draft_message ):
        
        for ( index, draft ) in enumerate( self._drafts ):
            
            if draft.GetDraftKey() == draft_key:
                
                self._drafts[ index ] = draft_message
                
                return
                
            
        
    
    def DeleteMessage( self, message_key ):
        
        self._messages = [ message for message in self._messages if message.GetMessageKey() != message_key ]
        
        if len( self._messages ) + len( self._drafts ) == 0:
            
            HC.pubsub.pub( 'delete_conversation_data', self._conversation_key )
            HC.pubsub.pub( 'delete_conversation_gui', self._conversation_key )
            
        
    
    def GetConversationKey( self ): return self._conversation_key
    
    def GetListCtrlTuple( self ):
        
        if len( self._messages ) > 0:
            
            first_message = self._messages[0]
            last_message = self._messages[-1]
            
            first_timestamp = first_message.GetTimestamp()
            last_timestamp = last_message.GetTimestamp()
            
            from_name = first_message.GetContactFrom().GetName()
            
        else:
            
            first_timestamp = None
            last_timestamp = None
            
            from_name = self._drafts[0].GetContactFrom().GetName()
            
        
        participants = self.GetParticipants()
        
        num_messages_unread = len( [ message for message in self._messages if ( self._identity, 'sent' ) in message.GetDestinations() ] )
        
        inbox = True in ( message.IsInbox() for message in self._messages )
        
        return ( self._conversation_key, inbox, self._subject, from_name, participants, len( self._messages ), num_messages_unread, first_timestamp, last_timestamp )
        
    
    def GetMessages( self ): return ( self._messages, self._drafts )
    
    def GetMessageKeysWithDestination( self, destination ): return [ message.GetMessageKey() for message in self._messages if message.HasDestination( destination ) ]
    
    def GetParticipants( self ):
        
        if len( self._messages ) == 0: return []
        else:
            
            first_message = self._messages[ 0 ]
            
            return first_message.GetParticipants()
            
        
    
    def GetStartedBy( self ):
        
        if len( self._messages ) > 0: return self._messages[ 0 ].GetContactFrom()
        elif len( self._drafts ) > 0: return self._drafts[ 0 ].GetContactFrom()
        else: return None
        
    
    def GetSubject( self ): return self._subject
    
    def GetUpdated( self ):
        
        if len( self._messages ) > 0:
            
            last_message = self._messages[-1]
            last_timestamp = last_message.GetTimestamp()
            
        else: last_timestamp = None
        
        return last_timestamp
        
    
    def HasMessageKey( self, message_key ): return True in ( message_key == message.GetMessageKey() for message in self._messages )
    
    def HasRead( self ): return True in ( message.IsRead( self._identity ) for message in self._messages )
    
    def HasUnread( self ): return True in ( message.IsUnread( self._identity ) for message in self._messages )
    
    def InboxConversation( self, conversation_key ):
        
        if conversation_key == self._conversation_key:
            
            self._inbox = True
            
            for message in self._messages: message.Inbox()
            
        
    
    def IsInbox( self ): return True in ( message.IsInbox() for message in self._messages )
    
    def UpdateMessageStatuses( self, message_key, status_updates ):
        
        for message in self._messages:
            
            if message_key == message.GetMessageKey():
                
                message.UpdateMessageStatuses( status_updates )
                
                break
                
            
        
    
class Contact( HC.HydrusYAMLBase ):
    
    yaml_tag = u'!Contact'
    
    def __init__( self, public_key, name, host, port ):
        
        HC.HydrusYAMLBase.__init__( self )
        
        self._public_key = public_key
        self._name = name
        self._host = host
        self._port = port
        
    
    def __hash__( self ): return self._name.__hash__()
    
    def __eq__( self, other ): return self.__hash__() == other.__hash__()
    
    def __ne__( self, other ): return not self.__eq__( other )
    
    def Encrypt( self, message ): return HydrusMessageHandling.PackageMessageForDelivery( message, self._public_key )
    
    def GetAddress( self ): return ( self._host, self._port )
    
    def GetConnection( self ): return None # this used to be connectiontoservice
    
    def GetContactKey( self ):
        
        if self._public_key is None: return None
        else: return hashlib.sha256( self._public_key ).digest()
        
    
    def GetInfo( self ): return ( self._public_key, self._name, self._host, self._port )
    
    def GetName( self ): return self._name
    
    def GetPublicKey( self ): return self._public_key
    
    def HasPublicKey( self ): return self._public_key is not None
    
class DraftMessage( object ):
    
    def __init__( self, draft_key, conversation_key, subject, contact_from, contacts_names_to, recipients_visible, body, attachment_hashes, is_new = False ):
        
        self._draft_key = draft_key
        self._conversation_key = conversation_key
        self._subject = subject
        self._contact_from = contact_from
        self._contacts_names_to = contacts_names_to
        self._recipients_visible = recipients_visible
        self._body = body
        self._attachment_hashes = attachment_hashes
        self._is_new = is_new
        
    
    def __hash__( self ): return self._draft_key.__hash__()
    
    def __eq__( self, other ): return self.__hash__() == other.__hash__()
    
    def __ne__( self, other ): return not self.__eq__( other )
    
    def GetContactFrom( self ): return self._contact_from
    
    def GetDraftKey( self ): return self._draft_key
    
    def GetInfo( self ): return ( self._draft_key, self._conversation_key, self._subject, self._contact_from, self._contacts_names_to, self._recipients_visible, self._body, self._attachment_hashes )
    
    def IsNew( self ): return self._is_new
    
    def IsReply( self ): return self._conversation_key != self._draft_key
    
    def Saved( self ): self._is_new = False
    
class Message( object ):
    
    def __init__( self, message_key, contact_from, destinations, timestamp, body, attachment_hashes, inbox ):
        
        self._message_key = message_key
        self._contact_from = contact_from
        self._destinations = destinations
        self._timestamp = timestamp
        self._body = body
        self._attachment_hashes = attachment_hashes
        self._inbox = inbox
        
    
    def __hash__( self ): return self._message_key.__hash__()
    
    def __eq__( self, other ): return self.__hash__() == other.__hash__()
    
    def __ne__( self, other ): return not self.__eq__( other )
    
    def Archive( self ): self._inbox = False
    
    def GetBody( self ): return self._body
    def GetContactFrom( self ): return self._contact_from
    def GetContactsTo( self ): return [ contact_to for ( contact_to, status ) in self._destinations ]
    def GetDestinations( self ): return self._destinations
    def GetMessageKey( self ): return self._message_key
    def GetParticipants( self ): return [ self._contact_from ] + self.GetContactsTo()
    def GetTimestamp( self ): return self._timestamp
    
    def HasDestination( self, destination ): return destination in self._destinations
    
    def Inbox( self ): self._inbox = True
    
    def IsInbox( self ): return self._inbox
    def IsRead( self, identity ): return ( identity, 'read' ) in self._destinations
    def IsUnread( self, identity ): return ( identity, 'sent' ) in self._destinations
    
    def Read( self, identity ): self.UpdateMessageStatuses( [ ( identity.GetContactKey(), 'read' ) ] )
    
    def UpdateMessageStatuses( self, updates ):
        
        contact_keys_to_contacts = { contact.GetContactKey() : contact for ( contact, status ) in self._destinations }
        
        dest_dict = dict( self._destinations )
        
        for ( contact_key, status ) in updates:
            
            if contact_key in contact_keys_to_contacts:
                
                dest_dict[ contact_keys_to_contacts[ contact_key ] ] = status
                
            
        
        self._destinations = dest_dict.items()
        
    
    def Unread( self, identity ): self.UpdateMessageStatuses( [ ( identity.GetContactKey(), 'sent' ) ] )
    
class MessageSearchContext( object ):
    
    def __init__( self, identity, raw_predicates = [] ):
        
        self._identity = identity
        
        raw_system_predicates = [ predicate for predicate in raw_predicates if predicate.startswith( 'system:' ) ]
        
        self._system_predicates = MessageSystemPredicates( raw_system_predicates )
        
        raw_search_terms = [ predicate for predicate in raw_predicates if not predicate.startswith( 'system:' ) ]
        
        self._search_terms_to_include = [ search_term for search_term in raw_search_terms if not search_term.startswith( '-' ) ]
        self._search_terms_to_exclude = [ search_term[1:] for search_term in raw_search_terms if search_term.startswith( '-' ) ]
        
    
    def GetIdentity( self ): return self._identity
    def GetSystemPredicates( self ): return self._system_predicates
    def GetTermsToExclude( self ): return self._search_terms_to_exclude
    def GetTermsToInclude( self ): return self._search_terms_to_include

class MessageSystemPredicates( object ):
    
    STATUS = 0
    CONTACT_STARTED = 1
    CONTACT_FROM = 2
    CONTACT_TO = 3
    TIMESTAMP = 4
    NUM_ATTACHMENTS = 5
    
    def __init__( self, system_predicates ):
        
        self._predicates = {}
        
        self._predicates[ self.NUM_ATTACHMENTS ] = []
        
        self._status = None
        
        self._contact_from = None
        self._contact_to = None
        self._contact_started = None
        self._min_timestamp = None
        self._max_timestamp = None
        
        self._inbox = 'system:inbox' in system_predicates
        
        self._archive = 'system:archive' in system_predicates
        
        self._draft = 'system:draft' in system_predicates
        
        isin = lambda a, b: a in b
        startswith = lambda a, b: a.startswith( b )
        lessthan = lambda a, b: a < b
        greaterthan = lambda a, b: a > b
        equals = lambda a, b: a == b
        about_equals = lambda a, b: a < b * 1.15 and a > b * 0.85
        
        for predicate in system_predicates:
            
            if predicate.startswith( 'system:status' ):
                
                try:
                    
                    status = predicate[14:]
                    
                    self._status = status
                    
                except: raise Exception( 'I could not parse the status predicate.' )
                
            
            if predicate.startswith( 'system:started_by' ):
                
                try:
                    
                    started_by = predicate[18:]
                    
                    self._contact_started = started_by
                    
                except: raise Exception( 'I could not parse the started by predicate.' )
                
            
            if predicate.startswith( 'system:from' ):
                
                try:
                    
                    contact_from = predicate[12:]
                    
                    self._contact_from = contact_from
                    
                except: raise Exception( 'I could not parse the contact from predicate.' )
                
            
            if predicate.startswith( 'system:to' ):
                
                try:
                    
                    contact_to = predicate[10:]
                    
                    self._contact_to = contact_to
                    
                except: raise Exception( 'I could not parse the contact to predicate.' )
                
            
            if predicate.startswith( 'system:age' ):
                
                try:
                    
                    condition = predicate[10]
                    
                    if condition not in ( '<', '>', u'\u2248' ): raise Exception()
                    
                    age = predicate[11:]
                    
                    years = 0
                    months = 0
                    days = 0
                    
                    if 'y' in age:
                        
                        ( years, age ) = age.split( 'y' )
                        
                        years = int( years )
                        
                    
                    if 'm' in age:
                        
                        ( months, age ) = age.split( 'm' )
                        
                        months = int( months )
                        
                    
                    if 'd' in age:
                        
                        ( days, age ) = age.split( 'd' )
                        
                        days = int( days )
                        
                    
                    timestamp = HC.GetNow() - ( ( ( ( ( years * 12 ) + months ) * 30 ) + days ) * 86400 )
                    
                    # this is backwards because we are talking about age, not timestamp
                    
                    if condition == '<': self._max_timestamp = timestamp
                    elif condition == '>': self._min_timestamp = timestamp
                    elif condition == u'\u2248':
                        self._min_timestamp = int( timestamp * 0.85 )
                        self._max_timestamp = int( timestamp * 1.15 )
                    
                except: raise Exception( 'I could not parse the age predicate.' )
                
            
            if predicate.startswith( 'system:numattachments' ):
                
                try:
                    
                    condition = predicate[21]
                    
                    if condition not in ( '>', '<', '=', u'\u2248' ): raise Exception()
                    
                    num_attachments = int( predicate[22:] )
                    
                    if num_attachments >= 0:
                        
                        if condition == '<': self._predicates[ self.NUM_ATTACHMENTS ].append( ( lessthan, num_attachments ) )
                        elif condition == '>': self._predicates[ self.NUM_ATTACHMENTS ].append( ( greaterthan, num_attachments ) )
                        elif condition == '=': self._predicates[ self.NUM_ATTACHMENTS ].append( ( equals, num_attachments ) )
                        elif condition == u'\u2248': self._predicates[ self.NUM_ATTACHMENTS ].append( ( about_equals, num_attachments ) )
                        
                    
                except: raise Exception( 'I could not parse the num attachments predicate.' )
                
            
        
    
    def GetInfo( self ): return ( self._inbox, self._archive, self._draft, self._status, self._contact_from, self._contact_to, self._contact_started, self._min_timestamp, self._max_timestamp )
    
    # maybe reconfigure this!
    # instead of Ok, I could do some real good searching and ANDing with the above predicates
    # especially since this is for getting the message_ids, not the whole convo, which will have the rich data
    
    def Ok( self, num_attachments ):
        
        if False in ( function( num_attachments, arg ) for ( function, arg ) in self._predicates[ self.NUM_ATTACHMENTS ] ): return False
        
        return True
        
    