"""
The client side.
Bagrut rulez!

NOTES:
1) We do not handle all kind of unlikely situations,
as this is for learning purposes only.
For example, if the chat has User1 and User2, and User1 writes "hel" while
User2 sends "hi there", when User1 finishes writing "hello" it will seem bad.

2) The code can be much better coded if we use methods we have not yet learned, such as:
    a) Classes.
    b) Threads.
    
    Keep that in mind!

3) There is much more to implement - like all the other commands. This code shows
you all the basic stuff needed to finish the rest.

Hope you find it useful!

Omerr.
August 2012.
"""

# Imports
import socket
import sys
import select
import struct
import cPickle
import msvcrt

marshall = cPickle.dumps
unmarshall = cPickle.loads


# Constants
SERVER =    '127.0.0.1'    # TODO: Change this
PORT =      3344

# Commands
COMMAND_SIZE =          struct.calcsize("L")
SEND_MESSAGE_COMMAND =  1   # The command that represents sending a message
TIMEOUT =               0.1   # Timeout, in seconds, for select command

def send_size_and_argument(active_socket, argument):
    '''Helper function used to send the length of an argument and then the argument itself. For example: 5, hello.
    Receives:   active_socket - the socket on which the parameters should be sent.
                argument -      the argument to send as a string. For example - hello.
    Returns:    nothing.'''
    # Get the size to send it first
    value = socket.htonl(len(argument))
    # Pack it
    size = struct.pack("L",value)
    # Send the size
    active_socket.send(size)
    # Send the argument
    active_socket.send(argument)

def send_command(active_socket, command):
    '''Helper function used to send a command.
    Receives:   active_socket - the socket on which the parameters should be sent.
                command -       the command to send.
    Returns:    nothing.'''
    value = socket.htonl(command)
    send_command = struct.pack("L", value)
    active_socket.send(send_command)

def send_chat_message(active_socket, user_name, message):
    '''Sends the chat message to the server using the fomrat of:
        <user_name_length><user_name><SEND_MESSAGE_COMMAND><message_length><message>
    Receives:
        active_socket - the socket of connection with the server.
        user_name -     the user name of the writer.
        message -       the chat message to send.
    Returns: nothing.'''
    send_size_and_argument(active_socket, user_name)
    send_command(active_socket, SEND_MESSAGE_COMMAND)
    send_size_and_argument(active_socket, message)

def receive_from_server(active_socket):
    '''Function used to receive data from the server.
    Receives:   active_socket - the socket with the server.
    Returns:    data retrieved from the socket.
                In case of an error - returns an empty string.'''
    # Get the size of the data from the server
    size_of_size = struct.calcsize("L")
    size = active_socket.recv(size_of_size)

    try:
        # Try to parse the size of the given message.
        size = socket.ntohl(struct.unpack("L", size)[0])
    except struct.error, e:
        # In case of an error, return an empty string.
        return ''
    
    buf = ""

    # Attempt to receive the whole message!
    while len(buf) < size:
        buf = active_socket.recv(size - len(buf))

    return unmarshall(buf)[0]





# Create the main socket - connection with the server
try:
    # Create a TCP socket
    # AF_INET means IPv4.
    # SOCK_STREAM means a TCP connection.
    # SOCK_DGRAM would mean an UDP "connection".
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # Connect to the server
    sock.connect((SERVER, PORT))
    print 'Connected to the server!'
    # Grab user name from the user...
    user_name = raw_input('Please choose a user name --> ')

except socket.error, e:
    # An error occurred
    print 'Error connecting to server!'
    # Quit
    sys.exit(1)

# Run this loop forever...
input_from_user= ''
while True:
    try:
        # Wait for input from the server socket
        rlist, wlist, xlist = select.select( [sock], [], [], TIMEOUT)

        for current_socket in rlist:
            # We have a message from the server!
            data = receive_from_server(sock)
            if data == "":
                # We failed to receive data. The connection terminated.
                print 'Shutting down.'
                sys.exit(1)
            else:
                # We received the data successfully! Write it to the user!
                print data

        # Try to read from the user
        # Note that this code is non blocking, so a read operation on the socket
        # is also possible
        # If any key was pressed
        if msvcrt.kbhit():
            # Save the key that was pressed
            keypressed = msvcrt.getch()
            # Present the key on the screen for the user to see
            sys.stdout.write(keypressed)
            # if the key that was pressed was <Enter>:
            if keypressed == '\r' or keypressed == '\n':
                sys.stdout.write('\n')
                # input_from_user is ready, so we can use it!
                # We want to send the message to the server...
                send_chat_message(sock, user_name, input_from_user)
                # Initialize input_from_user
                input_from_user = ''
            
            else:
                # input_from_user is not ready, so concate the pressed key
                input_from_user += keypressed
                   
    except KeyboardInterrupt:
        print 'Interrupted.'
        sock.close()
        break
    
