import select
import socket
import struct

from core.aochat.client_packets import LoginRequest, LoginSelect
from core.aochat.crypt import generate_login_key
from core.aochat.server_packets import ServerPacket, LoginOK, LoginError, LoginCharacterList
from core.logger import Logger


class Bot:
    def __init__(self):
        self.socket = None
        self.char_id = None
        self.char_name = None
        self.logger = Logger(__name__)

    def connect(self, host, port):
        self.logger.info("Connecting to '%s:%d'" % (host, port))
        self.socket = socket.create_connection((host, port), 10)

    def disconnect(self):
        if self.socket:
            self.socket.shutdown(socket.SHUT_RDWR)
            self.socket.close()
            self.socket = None

    def login(self, username, password, character):
        character = character.capitalize()

        # read seed packet
        self.logger.info("Logging in as '%s'" % character)
        seed_packet = self.read_packet(10)
        seed = seed_packet.seed

        # send back challenge
        key = generate_login_key(seed, username, password)
        login_request_packet = LoginRequest(0, username, key)
        self.send_packet(login_request_packet)

        # read character list
        character_list_packet: LoginCharacterList = self.read_packet()
        if isinstance(character_list_packet, LoginError):
            self.logger.error(f"Error logging in: {character_list_packet.message}")
            return False
        if character not in character_list_packet.names:
            self.logger.error(f"Character '{character}' does not exist on this account")
            return False
        index = character_list_packet.names.index(character)

        # select character
        self.char_id = character_list_packet.char_ids[index]
        self.char_name = character_list_packet.names[index]
        login_select_packet = LoginSelect(self.char_id)
        self.send_packet(login_select_packet)

        # wait for OK
        packet = self.read_packet()
        if packet.id == LoginOK.id:
            self.logger.info("Connected!")
            return packet
        else:
            self.logger.error("Error logging in: %s" % packet.message)
            return False

    def read_packet(self, max_delay_time=1):
        """
        Wait for packet from server.
        """

        read, write, error = select.select([self.socket], [], [], max_delay_time)
        if not read:
            return None
        else:
            # Read data from server
            head = self.read_bytes(4)
            packet_type, packet_length = struct.unpack(">2H", head)
            data = self.read_bytes(packet_length)

            try:
                return ServerPacket.get_instance(packet_type, data)
            except Exception as e:
                self.logger.error(f"Error parsing packet parameters for packet_type {packet_type} and payload: {data}",
                                  e)
                return None

    def send_packet(self, packet):
        data = packet.to_bytes()
        data = struct.pack(">2H", packet.id, len(data)) + data

        self.write_bytes(data)

    def read_bytes(self, num_bytes):
        data = bytes()

        while num_bytes > 0:
            chunk = self.socket.recv(num_bytes)

            if len(chunk) == 0:
                raise EOFError

            num_bytes -= len(chunk)
            data = data + chunk

        return data

    def write_bytes(self, data):
        num_bytes = len(data)

        while num_bytes > 0:
            sent = self.socket.send(data)

            if sent == 0:
                raise EOFError

            data = data[sent:]
            num_bytes -= sent
