import socket
import sys
import cPickle
from abc import ABCMeta, abstractmethod

import proxyhack

HOST = 'localhost'
PORT = 25375
MSG_SIZE = 1024

__SERVER_NAME__ = 'FuckNeulionServer'

__server_socket__ = None


class Response:
    """
    Response generated by the server
    """
    def __init__(self, success, msg):
        self.success = success
        self.msg = msg

    def to_string(self):
        """
        Returns a string representation of the response
        """
        packet = {
            'status': self.success,
            'msg': self.msg,
        }
        return cPickle.dumps(packet)

    @staticmethod
    def accept(response_string):
        """
        Returns Response object from a string, None if invalid string
        """
        try:
            packet = cPickle.loads(response_string)
            return Response(bool(packet['status']), packet['msg'])
        except:
            return None

    def __repr__(self):
        return self.__str__()

    def __str__(self):
        return "Response: %s %s" % (self.success, self.msg)


class Command:
    """
    Abstract class for server commands
    """
    __metaclass__ = ABCMeta

    @abstractmethod
    def to_string(self):
        """
        Returns string representation of the command
        """
        pass

    @staticmethod
    @abstractmethod
    def accept(command_string):
        """
        Returns command object if command_string is valid, None otherwise
        """
        pass

    @abstractmethod
    def run(self):
        """
        Command implementation
        Returns a Response object
        """
        pass


class RunProxyHackCommand(Command):
    """
    Command to run the proxy hack
    """
    def __init__(self, game_id, team_type):
        self.game_id = game_id
        self.team_type = team_type

    def to_string(self):
        packet = {
            'command': self.__class__.__name__,
            'game_id': self.game_id,
            'team_type': self.team_type
        }
        return cPickle.dumps(packet)

    @staticmethod
    def accept(command_string):
        try:
            packet = cPickle.loads(command_string)
            if packet['command'] != RunProxyHackCommand.__name__:
                return None
            return RunProxyHackCommand(packet['game_id'], packet['team_type'])
        except:
            return None

    def run(self):
        log('Running proxy hack command ' + self.game_id + ' ' + self.team_type)
        success, output = proxyhack.run_proxy_hack(self.game_id, self.team_type)
        return Response(success, output)


class ShutdownServerCommand(Command):
    """
    Command to request server shutdown
    """
    def __init__(self):
        pass

    def to_string(self):
        packet = {
            'command': self.__class__.__name__
        }
        return cPickle.dumps(packet)

    @staticmethod
    def accept(command_string):
        try:
            packet = cPickle.loads(command_string)
            if packet['command'] != ShutdownServerCommand.__name__:
                return None
            return ShutdownServerCommand()
        except:
            return None

    def run(self):
        log('Running server shutdown command')
        proxyhack.kill_proxy_hack()
        return Response(True, None)


def start_server():
    """
    Starts the server and listens for commands
    :return:
    """
    log('Starting Server')

    global __server_socket__
    __server_socket__ = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    log('Server socket created')

    try:
        __server_socket__.bind((HOST, PORT))
    except socket.error, msg:
        log('Server socket bind failed. Error: ' + str(msg[0]))
        sys.exit()
    log('Server socket bind complete')

    __server_socket__.listen(1)

    while True:
        log('Server socket waiting for connection')

        try:
            (conn, addr) = __server_socket__.accept()
        except socket.timeout:
            log('Server socket timed out')
            continue

        log('Server connected with ' + addr[0] + ':' + str(addr[1]))

        if __receive_commands(conn):
            # Server shutdown requested
            break

    __server_socket__.close()
    log('Server socket closed')


def __receive_commands(conn):
    """
    Listens and process incoming commands
    Returns when the connection is closed
    :param conn:        the connection to listen on
    :return:            true if server shutdown is requested
    """
    log('Server now processing commands')
    while True:
        # Receive command data
        data = conn.recv(MSG_SIZE)

        if data == '':
            # Connection closed
            log('Server socket connection closed')
            return False

        command = __parse_command_string(data)
        if command is not None:
            # Run the command
            response = command.run()

            # Send the response back
            conn.send(response.to_string())

            if command.__class__ == ShutdownServerCommand:
                # Shutdown command
                return True
            return False
        else:
            log('Received unknown command string: ' + data)
            return False


def __parse_command_string(command_string):
    """
    Returns a command from a command_string
    :param command_string:
    :return:        Command, None if not a valid command string
    """
    command = RunProxyHackCommand.accept(command_string)
    if command: return command

    command = ShutdownServerCommand.accept(command_string)
    if command: return command

    return None


def log(msg):
    print __SERVER_NAME__ + ': ' + msg


if __name__ == "__main__":
    start_server()
