'''
## MESSAGING CLIENT 1.0
## 
## This is a simple client side app that connects to the messaging server and provides End to End Encryption
## All messages tranfered between clients is indecipherable to all except communicating parties
## Users can register for accounts with the chosen server.
## You can also transfer files which will also be encrypted during transit.
## To transfer files, in the "Enter message: " prompt type "file: (filename)" without quotes,
## ...File must be within the same folder as this file (m_client.py) in order for it to work.
## 
## Messages use a json based api
##
## IMPORTANT: If you wish to have multiple accounts, you must create a separate folder for
## each user, each containing the m_client.py and encryption_engine.py files.
## ...DO NOT login to another account from the wrong account folder.
## ...This is because of how the encryption keys are stored
##
## Author: <NAME>
'''
import socket
import threading
import json
import sqlite3
import time
import sys
from encryption_engine import EncryptionEngine
import getpass
from io import BytesIO

class Client(object):

    def __init__(self,host,port):

        self._logged_user = None
        self._remote_user_key = None
        self._logged_user_api_key = None
        
        
        self._server_tuple = (host,port)
        self.client_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
        self._encryption_engine = EncryptionEngine()
        self._prepare_app()
        self._main_option_menu()
        self._initialize_waiting_thread()
        self._user_option_menu()

        
    def _poll_server_connection(self,server_tuple):
        #Keep trying to connect every 5 seconds until server is found and online
        while True:
            try:
                self.client_socket.connect(server_tuple)
                break
            except:
                time.sleep(5)

    def _prepare_app(self):
        #This generates the public and private keys and creates the local database
        #in SQLite
        conn = sqlite3.connect("local.db")
        cursor = conn.cursor()
        
        #Create Tables if none exist
        user_key_table_sql = "CREATE TABLE IF NOT EXISTS userkeys (id INTEGER PRIMARY KEY NOT NULL,username varchar(200),prikey varchar(2000),pubkey varchar(2000))" 
        shared_key_table_sql = "CREATE TABLE IF NOT EXISTS sharedkeys (id INTEGER PRIMARY KEY NOT NULL,username varchar(200),symkey varchar(2000))"   
        message_table_sql = "CREATE TABLE IF NOT EXISTS messages (id INTEGER PRIMARY KEY NOT NULL, message varchar(200), sender varchar(200), receipient varchar(200), date varchar(200))"
        cursor.execute(shared_key_table_sql)
        cursor.execute(message_table_sql)
        cursor.execute(user_key_table_sql)
        
        #Check if you have generated your private keys
        check_keys_sql = 'SELECT pubkey,prikey FROM userkeys WHERE username=?'
        record = conn.execute(check_keys_sql,("device",))
        keys = record.fetchone()
        
        if keys is not None:
            pass
        else:
            self._encryption_engine.generate_private_public_key()

        print "Done preparing app"

        conn.commit()
        conn.close()

    def _main_option_menu_header(self):

        print ""
        print "********* MESSAGING SERVICE *********"
        print "1. Register a User "
        print "2. Login a User "
        print ""
        
    def _main_option_menu(self):

        self._main_option_menu_header()
        
        while True:
            print "> ",
            menu_choice = raw_input("")

            if menu_choice == "1":
                
                print""
                username = raw_input("Choose a Username: ")
                password = getpass.getpass("Choose a Password: ")
                
                print "Connecting to Server"
                self._poll_server_connection(self._server_tuple)
                
                public_key = self._encryption_engine.fetch_public_key()
                request_json = '{"username":"%s","password":"%s","public_key":"%s","type":"registration"}'%(username,password,public_key)
                self.client_socket.sendall(request_json)
                
            
            elif menu_choice == "2":
                
                print""
                username = raw_input("Enter username: ")
                password = <PASSWORD>("Enter password: ")
                
                print "Connecting to Server"
                self._poll_server_connection(self._server_tuple)
                
                request_json = '{"username":"%s","password":"%s","type":"login"}'%(username,password)
                self.client_socket.sendall(request_json)
                
            
            response = self.client_socket.recv(1024)
            response_json = json.loads(response)

            if response_json["success"] == True:
                self._logged_user = username
                self._logged_user_api_key = response_json["api_key"]
                break
            else:
                self.client_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
                print""
                print response_json["reason"]
                print""
                
            
        
    def _initialize_waiting_thread(self):
        #This thread waits to receive messages from the server and other users
        t = threading.Thread(target = self._wait_for_messages)
        t.daemon = True
        t.start()

    def _wait_for_messages(self):

        try:

            while True:
                chunk = self.client_socket.recv(1024)
                response = ''

                while chunk:
                    if chunk[-2:] == "/0":
                        response+=chunk[:-2]
                        break
                    response+= chunk
                    chunk = self.client_socket.recv(1024)


                print"Response is: %s"%response
                self._handle_message_response(response)
                
        except:
            
            print"Shutting down.."
                

    def _handle_message_response(self,response):
        try:
            
            json_data = json.loads(response)
            
            if json_data["type"] == "message":

                #Handles when user receives a message
                
                sender = json_data["sender"]
                date = json_data["date"]
                message = json_data["message"]
                receipient = json_data["receipient"]
                shared_key = self._encryption_engine.fetch_local_shared_key(sender)
                decrypted_text = self._encryption_engine.decrypt_text(message,shared_key)
                
                print"\nMessage Received"
                print""
                print"From: %s"%sender
                print"Date: %s"%date
                print"Message: %s"%decrypted_text
                print""

                conn = sqlite3.connect('local.db')
                cursor = conn.cursor()
                cursor.execute('INSERT INTO messages (message,sender,receipient,date) VALUES (?,?,?,?)',(decrypted_text,sender,receipient,date))
                conn.commit()
                conn.close()
                

            elif json_data["type"] == "file":

                #Handles receiving of a file, after receiving a filename
                sender = json_data["sender"]
                shared_key = self._encryption_engine.fetch_local_shared_key(sender) 
                filename = self._encryption_engine.decrypt_text(json_data["message"],shared_key)

                print "Receiving %s from %s, please wait...."%(filename,sender)
                
                #Prevent recv from taking too long
                self.client_socket.settimeout(5)

                try:
                    with open(filename,"wb") as f:
                        chunk = self.client_socket.recv(1024)
                        data = ''
                        if chunk == "/0end":
                            pass
                        else:
                            while chunk:
                                if chunk[-5:] == '/0end':
                                    data+=chunk[:-5]
                                    break
                                data+=chunk
                                chunk = self.client_socket.recv(1024)

                            decrypted_data = self._encryption_engine.decrypt_file(data,shared_key)
                            f.write(decrypted_data)
                except:
                    pass

                self.client_socket.settimeout(None)

                print "File Received"
                        
            elif json_data["type"] == "unread":

                unread_messages = json_data["objects"]
                for message in unread_messages:
                    
                    sender = message["sender"]
                    date = message["date"]
                    message_text = message["message"]
                    receipient = message["receipient"]
                    shared_key = self._encryption_engine.fetch_local_shared_key(sender)
                    decrypted_text = self._encryption_engine.decrypt_text(message_text,shared_key)
                    
                    print""
                    print"From: %s"%sender
                    print"Date: %s"%date
                    print"Message: %s"%decrypted_text
                    print""

                    conn = sqlite3.connect('local.db')
                    cursor = conn.cursor()
                    cursor.execute('INSERT INTO messages (message,sender,receipient,date) VALUES (?,?,?,?)',(decrypted_text,sender,receipient,date))
                    conn.commit()
                    conn.close()
                
            
            elif json_data["type"] == "alert":
                
                #Handles alerts like success and fails
                
                message = json_data["message"]
                
                #This helps throw an exception if encryption is tried
                #on a non existent key
                if json_data["success"] == False:
                    self._remote_user_key = 0
                    
                print""
                print"Alert: %s"%message
                print""


            elif json_data["type"] == "publickey":
                #Handles response when you fetch a public key remotely
                
                username = json_data["username"]
                public_key = json_data["public_key"]
                
                print""
                print"Public Key for %s: %s"%(username,public_key)
                print""
                
                self._remote_user_key = public_key

            elif json_data["type"] == "sharedkey":

                #Handle when a user sends you a shared key
                #Receives key and saves it to the database
                
                message = json_data["message"]
                sender = json_data["sender"]
                
                private_key = self._encryption_engine.fetch_private_key()
                decrypted_shared_key = self._encryption_engine.decrypt_key(message,private_key)
                self._encryption_engine.save_shared_key(sender,decrypted_shared_key)
                
                print""             


            self._user_option_menu_header()
            
        except:
            if response == 'sent':
                print""
                print"Success"
                print""
            else:
                print""
                print"Failed"
                print""
            raise
            
            self._user_option_menu_header()

    def _user_option_menu_header(self):
        print ""
        print "1. Send Message "
        print "2. View Conversation "
        print "3. View Inbox "
        print "4. View Outbox "
        print "5. View Unread "
        print "6. Exit "
        print ""
        print "> ",
        
    def _user_option_menu(self):
        
        self._user_option_menu_header()

        while True:

            menu_option = raw_input("")

            if menu_option == "1":
                
                self._send_message_method()
                
            elif menu_option == "2":
                
                self._view_conversation_method()
                
            elif menu_option == "3":
                
                self._view_inbox_method()
                
            elif menu_option == "4":
                
                self._view_outbox_method()

            elif menu_option == "5":

                self._view_unread_method()

            elif menu_option == "6":
                sys.exit(0)

    def _fetch_remote_public_key(self,user):
        json_request = '{"username":"%s","logged_user":"%s","api_key":"%s","type":"publickey"}'%(user,self._logged_user,self._logged_user_api_key)
        self.client_socket.sendall(json_request)
        self.client_socket.send("/0")

        timeout = 5
        count = 0

        while self._remote_user_key is None:
            #Check every second if the remote key was fetched
            time.sleep(1)
            #If server responds with code 0 (from the receiving thread) set key to None
            #The try catch will throw an exception an fail gracefully
            if self._remote_user_key == 0:
                self._remote_user_key = None
                break

        remote_key = self._remote_user_key
        self._remote_user_key = None
        
        return remote_key

    def _send_message_method(self):

        IS_FILE = False
        
        print ""
        message = raw_input("Enter message: ")
        receipient = raw_input("Enter recipient: ")
        print ""

        if message[:6] == "file: ":
            IS_FILE = True
            message_list = message.split("file: ")
            message = message_list[1]
            filename = message

            

        sender = self._logged_user
        shared_key = self._encryption_engine.fetch_local_shared_key(receipient)

        try:
            if shared_key is not None:
                #The user has a shared key stored for recipient, so head straight to encryption
                encrypted_text = self._encryption_engine.encrypt_text(message,shared_key)
                
            else:
                #The user has no shared key stored for the recipient, 
                #so generate and send them a shared key
                
                #fetch remote public key
                
                public_key = self._fetch_remote_public_key(receipient)
                #print "Public key fetched"
                
                #generate shared key
                
                shared_key = self._encryption_engine.generate_shared_key()
                #print "Shared key generated"
                
                #encrypt shared key with public key
                
                encrypted_shared_key = self._encryption_engine.encrypt_key(shared_key,public_key)
                #print"Shared key encrypted"
                #save shared key and username to database

                self._encryption_engine.save_shared_key(receipient,shared_key)
                #print "Shared key saved"
                #send to receipient

                request_json = '{"sender":"%s","receipient":"%s","logged_user":"%s","message":"%s","api_key":"%s","type":"sharedkey"}'%(sender,receipient,sender,encrypted_shared_key,self._logged_user_api_key)
                self.client_socket.sendall(request_json)
                self.client_socket.send("/0")

                #This wait is just so the recipient of the message can do all necessary calculations and store the key
                time.sleep(5)
                encrypted_text = self._encryption_engine.encrypt_text(message,shared_key)

            #Finally send the (encrypted) message

            if IS_FILE == False:                
                message_json = '{"message":"%s", "receipient":"%s", "sender":"%s", "logged_user":"%s", "api_key":"%s","type":"message"}'%(encrypted_text,receipient,sender,self._logged_user,self._logged_user_api_key)
                self.client_socket.sendall(message_json)
                self.client_socket.send("/0")
                

                current_time_epoch = time.time()
                time_format = '%Y/%m/%d %H:%M:%S'
                date = time.strftime(time_format,time.localtime(current_time_epoch))
            
                conn = sqlite3.connect('local.db')
                cursor = conn.cursor()
                cursor.execute('INSERT INTO messages (message,sender,receipient,date) VALUES (?,?,?,?)',(message,sender,receipient,date))
                conn.commit()
                conn.close()
                
            else:

                try:

                    with open(filename,"rb") as f:
                        
                        print "Sending file to %s...."%receipient
                        message_json = '{"message":"%s", "receipient":"%s", "sender":"%s", "logged_user":"%s", "api_key":"%s","type":"file"}'%(encrypted_text,receipient,sender,self._logged_user,self._logged_user_api_key)
                        self.client_socket.sendall(message_json)
                        self.client_socket.send("/0")
                        
                        data = f.read()
                        encrypted_data = self._encryption_engine.encrypt_file(data,shared_key)
                        self.client_socket.sendall(encrypted_data+"/0end")

                    print "Done!"    
                except:
                    print "There was an error... Check that file exists"
                    self._user_option_menu_header()
                    pass
                

            
        except:
            #"There was an error!"
            pass    

    def _view_conversation_method(self):
        
        print ""
        user1 = raw_input("View conversation with: ")
        print""
        user2 = self._logged_user

        conn = sqlite3.connect("local.db")
        cursor = conn.cursor()

        conversation_sql = 'SELECT message,sender,receipient,date FROM messages WHERE (sender=? AND receipient=?) OR (sender=? AND receipient=?)'
        messages = cursor.execute(conversation_sql,(user1,user2,user2,user1))

        for message in messages:
            print""
            print"From: %s"%message[1]
            print"To: %s"%message[2]
            print"Date: %s"%message[3]
            print"Message: %s"%message[0]
            print""

        self._user_option_menu_header()
            

    def _view_inbox_method(self):
        conn = sqlite3.connect("local.db")
        cursor = conn.cursor()
        
        receipient = self._logged_user
        
        view_received_messages_sql = 'SELECT message,sender,receipient,date FROM messages WHERE receipient=?'
        messages = cursor.execute(view_received_messages_sql,(receipient,))

        for message in messages:
            print""
            print"From: %s"%message[1]
            print"Date: %s"%message[3]
            print"Message: %s"%message[0]
            print""

        conn.close()

        self._user_option_menu_header()

    def _view_unread_method(self):
        
        request = '{"logged_user":"%s","api_key":"%s","type":"unread"}'%(self._logged_user,self._logged_user_api_key)
        self.client_socket.sendall(request)
        self.client_socket.send("/0")

    
    def _view_outbox_method(self):
        conn = sqlite3.connect("local.db")
        cursor = conn.cursor()
        
        sender = self._logged_user
        
        view_received_messages_sql = 'SELECT message,sender,receipient,date FROM messages WHERE sender=?'
        messages = cursor.execute(view_received_messages_sql,(sender,))
        
        for message in messages:
            print""
            print"To: %s"%message[2]
            print"Date: %s"%message[3]
            print"Message: %s"%message[0]
            print""
        conn.close()
        self._user_option_menu_header()
                
            
if __name__=="__main__":
    HOST = '127.0.0.1'
    PORT = 1000
    client = Client(HOST,PORT)
        

    

'''
## MESSAGING CLIENT 1.0
## 
## This is a simple client side app that connects to the messaging server and provides End to End Encryption
## All messages tranfered between clients is indecipherable to all except communicating parties
## Users can register for accounts with the chosen server.
## You can also transfer files which will also be encrypted during transit.
## To transfer files, in the "Enter message: " prompt type "file: (filename)" without quotes,
## ...File must be within the same folder as this file (m_client.py) in order for it to work.
## 
## Messages use a json based api
##
## IMPORTANT: If you wish to have multiple accounts, you must create a separate folder for
## each user, each containing the m_client.py and encryption_engine.py files.
## ...DO NOT login to another account from the wrong account folder.
## ...This is because of how the encryption keys are stored
##
## Author: <NAME>
'''
import socket
import threading
import json
import sqlite3
import time
import sys
from encryption_engine import EncryptionEngine
import getpass
from io import BytesIO

class Client(object):

    def __init__(self,host,port):

        self._logged_user = None
        self._remote_user_key = None
        self._logged_user_api_key = None
        
        
        self._server_tuple = (host,port)
        self.client_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
        self._encryption_engine = EncryptionEngine()
        self._prepare_app()
        self._main_option_menu()
        self._initialize_waiting_thread()
        self._user_option_menu()

        
    def _poll_server_connection(self,server_tuple):
        #Keep trying to connect every 5 seconds until server is found and online
        while True:
            try:
                self.client_socket.connect(server_tuple)
                break
            except:
                time.sleep(5)

    def _prepare_app(self):
        #This generates the public and private keys and creates the local database
        #in SQLite
        conn = sqlite3.connect("local.db")
        cursor = conn.cursor()
        
        #Create Tables if none exist
        user_key_table_sql = "CREATE TABLE IF NOT EXISTS userkeys (id INTEGER PRIMARY KEY NOT NULL,username varchar(200),prikey varchar(2000),pubkey varchar(2000))" 
        shared_key_table_sql = "CREATE TABLE IF NOT EXISTS sharedkeys (id INTEGER PRIMARY KEY NOT NULL,username varchar(200),symkey varchar(2000))"   
        message_table_sql = "CREATE TABLE IF NOT EXISTS messages (id INTEGER PRIMARY KEY NOT NULL, message varchar(200), sender varchar(200), receipient varchar(200), date varchar(200))"
        cursor.execute(shared_key_table_sql)
        cursor.execute(message_table_sql)
        cursor.execute(user_key_table_sql)
        
        #Check if you have generated your private keys
        check_keys_sql = 'SELECT pubkey,prikey FROM userkeys WHERE username=?'
        record = conn.execute(check_keys_sql,("device",))
        keys = record.fetchone()
        
        if keys is not None:
            pass
        else:
            self._encryption_engine.generate_private_public_key()

        print "Done preparing app"

        conn.commit()
        conn.close()

    def _main_option_menu_header(self):

        print ""
        print "********* MESSAGING SERVICE *********"
        print "1. Register a User "
        print "2. Login a User "
        print ""
        
    def _main_option_menu(self):

        self._main_option_menu_header()
        
        while True:
            print "> ",
            menu_choice = raw_input("")

            if menu_choice == "1":
                
                print""
                username = raw_input("Choose a Username: ")
                password = getpass.getpass("Choose a Password: ")
                
                print "Connecting to Server"
                self._poll_server_connection(self._server_tuple)
                
                public_key = self._encryption_engine.fetch_public_key()
                request_json = '{"username":"%s","password":"%s","public_key":"%s","type":"registration"}'%(username,password,public_key)
                self.client_socket.sendall(request_json)
                
            
            elif menu_choice == "2":
                
                print""
                username = raw_input("Enter username: ")
                password = <PASSWORD>("Enter password: ")
                
                print "Connecting to Server"
                self._poll_server_connection(self._server_tuple)
                
                request_json = '{"username":"%s","password":"%s","type":"login"}'%(username,password)
                self.client_socket.sendall(request_json)
                
            
            response = self.client_socket.recv(1024)
            response_json = json.loads(response)

            if response_json["success"] == True:
                self._logged_user = username
                self._logged_user_api_key = response_json["api_key"]
                break
            else:
                self.client_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
                print""
                print response_json["reason"]
                print""
                
            
        
    def _initialize_waiting_thread(self):
        #This thread waits to receive messages from the server and other users
        t = threading.Thread(target = self._wait_for_messages)
        t.daemon = True
        t.start()

    def _wait_for_messages(self):

        try:

            while True:
                chunk = self.client_socket.recv(1024)
                response = ''

                while chunk:
                    if chunk[-2:] == "/0":
                        response+=chunk[:-2]
                        break
                    response+= chunk
                    chunk = self.client_socket.recv(1024)


                print"Response is: %s"%response
                self._handle_message_response(response)
                
        except:
            
            print"Shutting down.."
                

    def _handle_message_response(self,response):
        try:
            
            json_data = json.loads(response)
            
            if json_data["type"] == "message":

                #Handles when user receives a message
                
                sender = json_data["sender"]
                date = json_data["date"]
                message = json_data["message"]
                receipient = json_data["receipient"]
                shared_key = self._encryption_engine.fetch_local_shared_key(sender)
                decrypted_text = self._encryption_engine.decrypt_text(message,shared_key)
                
                print"\nMessage Received"
                print""
                print"From: %s"%sender
                print"Date: %s"%date
                print"Message: %s"%decrypted_text
                print""

                conn = sqlite3.connect('local.db')
                cursor = conn.cursor()
                cursor.execute('INSERT INTO messages (message,sender,receipient,date) VALUES (?,?,?,?)',(decrypted_text,sender,receipient,date))
                conn.commit()
                conn.close()
                

            elif json_data["type"] == "file":

                #Handles receiving of a file, after receiving a filename
                sender = json_data["sender"]
                shared_key = self._encryption_engine.fetch_local_shared_key(sender) 
                filename = self._encryption_engine.decrypt_text(json_data["message"],shared_key)

                print "Receiving %s from %s, please wait...."%(filename,sender)
                
                #Prevent recv from taking too long
                self.client_socket.settimeout(5)

                try:
                    with open(filename,"wb") as f:
                        chunk = self.client_socket.recv(1024)
                        data = ''
                        if chunk == "/0end":
                            pass
                        else:
                            while chunk:
                                if chunk[-5:] == '/0end':
                                    data+=chunk[:-5]
                                    break
                                data+=chunk
                                chunk = self.client_socket.recv(1024)

                            decrypted_data = self._encryption_engine.decrypt_file(data,shared_key)
                            f.write(decrypted_data)
                except:
                    pass

                self.client_socket.settimeout(None)

                print "File Received"
                        
            elif json_data["type"] == "unread":

                unread_messages = json_data["objects"]
                for message in unread_messages:
                    
                    sender = message["sender"]
                    date = message["date"]
                    message_text = message["message"]
                    receipient = message["receipient"]
                    shared_key = self._encryption_engine.fetch_local_shared_key(sender)
                    decrypted_text = self._encryption_engine.decrypt_text(message_text,shared_key)
                    
                    print""
                    print"From: %s"%sender
                    print"Date: %s"%date
                    print"Message: %s"%decrypted_text
                    print""

                    conn = sqlite3.connect('local.db')
                    cursor = conn.cursor()
                    cursor.execute('INSERT INTO messages (message,sender,receipient,date) VALUES (?,?,?,?)',(decrypted_text,sender,receipient,date))
                    conn.commit()
                    conn.close()
                
            
            elif json_data["type"] == "alert":
                
                #Handles alerts like success and fails
                
                message = json_data["message"]
                
                #This helps throw an exception if encryption is tried
                #on a non existent key
                if json_data["success"] == False:
                    self._remote_user_key = 0
                    
                print""
                print"Alert: %s"%message
                print""


            elif json_data["type"] == "publickey":
                #Handles response when you fetch a public key remotely
                
                username = json_data["username"]
                public_key = json_data["public_key"]
                
                print""
                print"Public Key for %s: %s"%(username,public_key)
                print""
                
                self._remote_user_key = public_key

            elif json_data["type"] == "sharedkey":

                #Handle when a user sends you a shared key
                #Receives key and saves it to the database
                
                message = json_data["message"]
                sender = json_data["sender"]
                
                private_key = self._encryption_engine.fetch_private_key()
                decrypted_shared_key = self._encryption_engine.decrypt_key(message,private_key)
                self._encryption_engine.save_shared_key(sender,decrypted_shared_key)
                
                print""             


            self._user_option_menu_header()
            
        except:
            if response == 'sent':
                print""
                print"Success"
                print""
            else:
                print""
                print"Failed"
                print""
            raise
            
            self._user_option_menu_header()

    def _user_option_menu_header(self):
        print ""
        print "1. Send Message "
        print "2. View Conversation "
        print "3. View Inbox "
        print "4. View Outbox "
        print "5. View Unread "
        print "6. Exit "
        print ""
        print "> ",
        
    def _user_option_menu(self):
        
        self._user_option_menu_header()

        while True:

            menu_option = raw_input("")

            if menu_option == "1":
                
                self._send_message_method()
                
            elif menu_option == "2":
                
                self._view_conversation_method()
                
            elif menu_option == "3":
                
                self._view_inbox_method()
                
            elif menu_option == "4":
                
                self._view_outbox_method()

            elif menu_option == "5":

                self._view_unread_method()

            elif menu_option == "6":
                sys.exit(0)

    def _fetch_remote_public_key(self,user):
        json_request = '{"username":"%s","logged_user":"%s","api_key":"%s","type":"publickey"}'%(user,self._logged_user,self._logged_user_api_key)
        self.client_socket.sendall(json_request)
        self.client_socket.send("/0")

        timeout = 5
        count = 0

        while self._remote_user_key is None:
            #Check every second if the remote key was fetched
            time.sleep(1)
            #If server responds with code 0 (from the receiving thread) set key to None
            #The try catch will throw an exception an fail gracefully
            if self._remote_user_key == 0:
                self._remote_user_key = None
                break

        remote_key = self._remote_user_key
        self._remote_user_key = None
        
        return remote_key

    def _send_message_method(self):

        IS_FILE = False
        
        print ""
        message = raw_input("Enter message: ")
        receipient = raw_input("Enter recipient: ")
        print ""

        if message[:6] == "file: ":
            IS_FILE = True
            message_list = message.split("file: ")
            message = message_list[1]
            filename = message

            

        sender = self._logged_user
        shared_key = self._encryption_engine.fetch_local_shared_key(receipient)

        try:
            if shared_key is not None:
                #The user has a shared key stored for recipient, so head straight to encryption
                encrypted_text = self._encryption_engine.encrypt_text(message,shared_key)
                
            else:
                #The user has no shared key stored for the recipient, 
                #so generate and send them a shared key
                
                #fetch remote public key
                
                public_key = self._fetch_remote_public_key(receipient)
                #print "Public key fetched"
                
                #generate shared key
                
                shared_key = self._encryption_engine.generate_shared_key()
                #print "Shared key generated"
                
                #encrypt shared key with public key
                
                encrypted_shared_key = self._encryption_engine.encrypt_key(shared_key,public_key)
                #print"Shared key encrypted"
                #save shared key and username to database

                self._encryption_engine.save_shared_key(receipient,shared_key)
                #print "Shared key saved"
                #send to receipient

                request_json = '{"sender":"%s","receipient":"%s","logged_user":"%s","message":"%s","api_key":"%s","type":"sharedkey"}'%(sender,receipient,sender,encrypted_shared_key,self._logged_user_api_key)
                self.client_socket.sendall(request_json)
                self.client_socket.send("/0")

                #This wait is just so the recipient of the message can do all necessary calculations and store the key
                time.sleep(5)
                encrypted_text = self._encryption_engine.encrypt_text(message,shared_key)

            #Finally send the (encrypted) message

            if IS_FILE == False:                
                message_json = '{"message":"%s", "receipient":"%s", "sender":"%s", "logged_user":"%s", "api_key":"%s","type":"message"}'%(encrypted_text,receipient,sender,self._logged_user,self._logged_user_api_key)
                self.client_socket.sendall(message_json)
                self.client_socket.send("/0")
                

                current_time_epoch = time.time()
                time_format = '%Y/%m/%d %H:%M:%S'
                date = time.strftime(time_format,time.localtime(current_time_epoch))
            
                conn = sqlite3.connect('local.db')
                cursor = conn.cursor()
                cursor.execute('INSERT INTO messages (message,sender,receipient,date) VALUES (?,?,?,?)',(message,sender,receipient,date))
                conn.commit()
                conn.close()
                
            else:

                try:

                    with open(filename,"rb") as f:
                        
                        print "Sending file to %s...."%receipient
                        message_json = '{"message":"%s", "receipient":"%s", "sender":"%s", "logged_user":"%s", "api_key":"%s","type":"file"}'%(encrypted_text,receipient,sender,self._logged_user,self._logged_user_api_key)
                        self.client_socket.sendall(message_json)
                        self.client_socket.send("/0")
                        
                        data = f.read()
                        encrypted_data = self._encryption_engine.encrypt_file(data,shared_key)
                        self.client_socket.sendall(encrypted_data+"/0end")

                    print "Done!"    
                except:
                    print "There was an error... Check that file exists"
                    self._user_option_menu_header()
                    pass
                

            
        except:
            #"There was an error!"
            pass    

    def _view_conversation_method(self):
        
        print ""
        user1 = raw_input("View conversation with: ")
        print""
        user2 = self._logged_user

        conn = sqlite3.connect("local.db")
        cursor = conn.cursor()

        conversation_sql = 'SELECT message,sender,receipient,date FROM messages WHERE (sender=? AND receipient=?) OR (sender=? AND receipient=?)'
        messages = cursor.execute(conversation_sql,(user1,user2,user2,user1))

        for message in messages:
            print""
            print"From: %s"%message[1]
            print"To: %s"%message[2]
            print"Date: %s"%message[3]
            print"Message: %s"%message[0]
            print""

        self._user_option_menu_header()
            

    def _view_inbox_method(self):
        conn = sqlite3.connect("local.db")
        cursor = conn.cursor()
        
        receipient = self._logged_user
        
        view_received_messages_sql = 'SELECT message,sender,receipient,date FROM messages WHERE receipient=?'
        messages = cursor.execute(view_received_messages_sql,(receipient,))

        for message in messages:
            print""
            print"From: %s"%message[1]
            print"Date: %s"%message[3]
            print"Message: %s"%message[0]
            print""

        conn.close()

        self._user_option_menu_header()

    def _view_unread_method(self):
        
        request = '{"logged_user":"%s","api_key":"%s","type":"unread"}'%(self._logged_user,self._logged_user_api_key)
        self.client_socket.sendall(request)
        self.client_socket.send("/0")

    
    def _view_outbox_method(self):
        conn = sqlite3.connect("local.db")
        cursor = conn.cursor()
        
        sender = self._logged_user
        
        view_received_messages_sql = 'SELECT message,sender,receipient,date FROM messages WHERE sender=?'
        messages = cursor.execute(view_received_messages_sql,(sender,))
        
        for message in messages:
            print""
            print"To: %s"%message[2]
            print"Date: %s"%message[3]
            print"Message: %s"%message[0]
            print""
        conn.close()
        self._user_option_menu_header()
                
            
if __name__=="__main__":
    HOST = '127.0.0.1'
    PORT = 1000
    client = Client(HOST,PORT)
        

    

## MESSAGING CLIENT 1.0
 ## 
 ## This is a simple client side app that connects to the messaging server and provides End to End Encryption
 ## All messages tranfered between clients is indecipherable to all except communicating parties
 ## Users can register for accounts with the chosen server.
 ## You can also transfer files which will also be encrypted during transit.
 ## To transfer files, in the "Enter message: " prompt type "file: (filename)" without quotes,
 ## ...File must be within the same folder as this file (m_client.py) in order for it to work.
 ## 
 ## Messages use a json based api
 ##
 ## IMPORTANT: If you wish to have multiple accounts, you must create a separate folder for
 ## each user, each containing the m_client.py and encryption_engine.py files.
 ## ...DO NOT login to another account from the wrong account folder.
 ## ...This is because of how the encryption keys are stored
 ##
 ## Author: <NAME> #Keep trying to connect every 5 seconds until server is found and online #This generates the public and private keys and creates the local database #in SQLite #Create Tables if none exist #Check if you have generated your private keys #This thread waits to receive messages from the server and other users #Handles when user receives a message #Handles receiving of a file, after receiving a filename #Prevent recv from taking too long #Handles alerts like success and fails #This helps throw an exception if encryption is tried #on a non existent key #Handles response when you fetch a public key remotely #Handle when a user sends you a shared key #Receives key and saves it to the database #Check every second if the remote key was fetched #If server responds with code 0 (from the receiving thread) set key to None #The try catch will throw an exception an fail gracefully #The user has a shared key stored for recipient, so head straight to encryption #The user has no shared key stored for the recipient, #so generate and send them a shared key #fetch remote public key #print "Public key fetched" #generate shared key #print "Shared key generated" #encrypt shared key with public key #print"Shared key encrypted" #save shared key and username to database #print "Shared key saved" #send to receipient #This wait is just so the recipient of the message can do all necessary calculations and store the key #Finally send the (encrypted) message #"There was an error!"