import ctypes
import os
import sys
import time
import platform
from logging import DEBUG, WARNING

import flatbuffers
from rlbot.messages.flat.MatchSettings import MatchSettings as MatchSettingsPacket
from rlbot.messages.flat.BallPrediction import BallPrediction as BallPredictionPacket
from rlbot.messages.flat.FieldInfo import FieldInfo
from rlbot.messages.flat.QuickChatMessages import QuickChatMessages
from rlbot.utils.file_util import get_python_root
from rlbot.utils.game_state_util import GameState, CarState, Physics, Vector3
from rlbot.utils.rendering.rendering_manager import RenderingManager
from rlbot.utils.rlbot_exception import get_exception_from_error_code, EmptyDllResponse
from rlbot.utils.structures import game_data_struct
from rlbot.utils.structures.ball_prediction_struct import BallPrediction
from rlbot.utils.structures.bot_input_struct import PlayerInput
from rlbot.utils.structures.game_data_struct import GameTickPacket, ByteBuffer, FieldInfoPacket
from rlbot.utils.structures.game_status import RLBotCoreStatus
from rlbot.utils.structures.rigid_body_struct import RigidBodyTick
from rlbot.utils.structures.start_match_structures import MatchSettings

if platform.system() == 'Windows':
    dll_name_64 = 'RLBot_Core_Interface.dll'
    dll_name_32 = 'RLBot_Core_Interface_32.dll'
elif platform.system() == 'Linux':
    dll_name_64 = 'libRLBotInterface.so'
    dll_name_32 = 'libRLBotInterface32.so'
elif platform.system() == 'Darwin':
    dll_name_64 = 'libRLBotInterface.dylib'
    dll_name_32 = 'libRLBotInterface32.dylib'

def wrap_callback(callback_func):
    def caller(id, status):
        callback_func(id, status)

    return caller


def get_dll_location():
    return os.path.join(get_dll_directory(), dll_name_64)


def get_dll_32_location():
    return os.path.join(get_dll_directory(), dll_name_32)


def is_32_bit_python():
    return sys.maxsize <= 2**32


def get_dll_directory():
    return os.path.join(get_python_root(), 'rlbot', 'dll')


class GameInterface:
    game = None
    start_match_configuration = None
    game_status_callback_type = None
    callback_func = None
    extension = None

    def __init__(self, logger):
        self.logger = logger
        self.dll_path = get_dll_32_location() if is_32_bit_python() else get_dll_location()
        self.renderer = RenderingManager()
        # wait for the dll to load

    def setup_function_types(self):
        self.wait_until_loaded()
        # update live data packet
        func = self.game.UpdateLiveDataPacket
        func.argtypes = [ctypes.POINTER(GameTickPacket)]
        func.restype = ctypes.c_int

        func = self.game.UpdateFieldInfo
        func.argtypes = [ctypes.POINTER(FieldInfoPacket)]
        func.restype = ctypes.c_int

        func = self.game.UpdateLiveDataPacketFlatbuffer
        func.argtypes = []
        func.restype = ByteBuffer

        func = self.game.UpdateRigidBodyTick
        func.argtypes = [ctypes.POINTER(RigidBodyTick)]
        func.restype = ctypes.c_int

        func = self.game.UpdateFieldInfoFlatbuffer
        func.argtypes = []
        func.restype = ByteBuffer

        func = self.game.GetBallPredictionStruct
        func.argtypes = [ctypes.POINTER(BallPrediction)]
        func.restype = ctypes.c_int

        func = self.game.GetBallPrediction
        func.argtypes = []
        func.restype = ByteBuffer

        # start match
        func = self.game.StartMatch
        func.argtypes = [MatchSettings]
        func.restype = ctypes.c_int

        # update player input
        func = self.game.UpdatePlayerInput
        func.argtypes = [PlayerInput, ctypes.c_int]
        func.restype = ctypes.c_int

        # update player input
        func = self.game.UpdatePlayerInputFlatbuffer
        func.argtypes = [ctypes.c_void_p, ctypes.c_int]
        func.restype = ctypes.c_int

        # send chat
        func = self.game.SendChat
        func.argtypes = [ctypes.c_uint, ctypes.c_int, ctypes.c_bool, self.game_status_callback_type, ctypes.c_void_p]
        func.restype = ctypes.c_int

        func = self.game.SendQuickChat
        func.argtypes = [ctypes.c_void_p, ctypes.c_int]
        func.restype = ctypes.c_int

        func = self.game.ReceiveChat
        func.argtypes = [ctypes.c_int, ctypes.c_int, ctypes.c_int]
        func.restype = ByteBuffer

        # set game state
        func = self.game.SetGameState
        func.argtypes = [ctypes.c_void_p, ctypes.c_int]
        func.restype = ctypes.c_int

        func = self.game.GetMatchSettings
        func.argtypes = []
        func.restype = ByteBuffer

        func = self.game.FreshLiveDataPacket
        func.argtypes = [ctypes.POINTER(GameTickPacket), ctypes.c_int, ctypes.c_int]
        func.restype = ctypes.c_int

        func = self.game.FreshLiveDataPacketFlatbuffer
        func.argtypes = [ctypes.c_int, ctypes.c_int]
        func.restype = ByteBuffer

        self.renderer.setup_function_types(self.game)
        self.logger.debug('game interface functions are setup')

        # free the memory at the given pointer
        func = self.game.Free
        func.argtypes = [ctypes.c_void_p]

    def update_live_data_packet(self, game_tick_packet: GameTickPacket):
        rlbot_status = self.game.UpdateLiveDataPacket(game_tick_packet)
        self.game_status(None, rlbot_status)
        return game_tick_packet

    def fresh_live_data_packet(self, game_tick_packet: GameTickPacket, timeout_millis: int, key: int):
        rlbot_status = self.game.FreshLiveDataPacket(game_tick_packet, timeout_millis, key)
        self.game_status(None, rlbot_status)
        return game_tick_packet

    def update_field_info_packet(self, field_info_packet: FieldInfoPacket):
        rlbot_status = self.game.UpdateFieldInfo(field_info_packet)
        self.game_status(None, rlbot_status)
        return field_info_packet

    def update_match_data_packet(self):
        pass

    def start_match(self):
        self.wait_until_loaded()
        # self.game_input_packet.bStartMatch = True
        rlbot_status = self.game.StartMatch(self.start_match_configuration)

        if rlbot_status != 0: