import pytest
import os
from xml.etree.ElementTree import tostring

from omegaconf import OmegaConf

import pymasep as pm
from pymasep.communication import Message

from test.unit.mocks.game_mock import GameMock
from test.unit.mocks.app_mock import AppMock


@pytest.fixture(scope="function")
def relative_path():
    yield '../../'


def _init_config(nb_agent, base_dir):
    cfg = OmegaConf.load(os.path.join(base_dir, 'config', nb_agent, 'app.yaml'))
    cfg['root_path'] = base_dir
    return cfg


@pytest.fixture(scope='function')
def configuration_one_agent(base_directory_unit):
    cfg = _init_config('game_one_agent', base_directory_unit)
    yield cfg


@pytest.fixture(scope='function')
def configuration_two_agents(base_directory_unit):
    cfg = _init_config('game_two_agents', base_directory_unit)
    yield cfg


def _send_define(engine, engine_queue):
    init_message = Message(Message.MESSAGE_TYPE_DEFINE, {'game': GameMock}, engine.id)
    engine.send_message_to_queue(init_message, engine_queue)


def _send_register(engine, engine_queue, interface_queue_id, role):
    register_message = _register_message(interface_queue_id, role, 'id_interface')
    engine.send_message_to_queue(register_message, engine_queue)


def _register_message(interface_queue_id, role, src_id):
    register_message = Message(Message.MESSAGE_TYPE_REGISTER,
                               {'id': 'id_interface',
                                'queue_id': interface_queue_id,
                                'role': role},
                               src_id)
    return register_message


def _send_action(engine, engine_queue):
    action_message = Message(Message.MESSAGE_TYPE_ACTION,
                             {'action': pm.common.Action(None, GameMock.ACTION_TYPE_1)},
                             'id_interface')
    engine.send_message_to_queue(action_message, engine_queue)


def _send_quit(engine, engine_queue):
    quit_message = Message(Message.MESSAGE_TYPE_QUIT_GAME, None, 'id_interface')
    engine.send_message_to_queue(quit_message, engine_queue)


def test_constructor():
    app = AppMock()
    cfg = OmegaConf.create()

    engine = pm.engine.Engine(app=app, received_q_id=0, cfg=cfg)

    assert isinstance(engine.environment, pm.common.Environment)
    assert engine.connected_sub_app == dict()
    assert engine.remote_communication is None


def test_constructor_no_remote_listen():
    app = AppMock()
    cfg = OmegaConf.create()
    engine = pm.engine.Engine(app=app, received_q_id=0, max_connection=-1, cfg=cfg)
    assert isinstance(engine.environment, pm.common.Environment)
    assert engine.connected_sub_app == dict()
    assert engine.server is None


def test_define():
    app = AppMock()
    cfg = OmegaConf.create()
    engine = pm.engine.Engine(app=app, received_q_id=0, cfg=cfg)
    _send_define(engine, app.queues[0])

    engine.define()

    assert isinstance(engine.environment.game, GameMock)
    assert app.queues[0].qsize() == 0


def test_init():
    app = AppMock()
    cfg = OmegaConf.create()
    engine = pm.engine.Engine(app=app, received_q_id=0, cfg=cfg)
    engine.environment.game = GameMock()

    engine.init()

    assert isinstance(engine.environment.current_state, pm.common.State)


def test_register_observer():
    app = AppMock()
    cfg = OmegaConf.create()
    engine = pm.engine.Engine(app=app, received_q_id=0, cfg=cfg)
    engine.environment.game = GameMock()
    msg = _register_message(1, pm.application.ConnectedSubAppInfo.ROLE_OBSERVER, 'id_interface')
    engine.init()

    engine.register(msg)

    assert engine.connected_sub_app['id_interface'].id == 'id_interface'
    assert engine.connected_sub_app['id_interface'].role == pm.application.ConnectedSubAppInfo.ROLE_OBSERVER
    assert app.queues[0].qsize() == 0
    assert 'agentInterface' not in engine.environment.current_state.objects


def test_register_actor(configuration_one_agent):
    app = AppMock()
    engine = pm.engine.Engine(app=app, received_q_id=0, cfg=configuration_one_agent)
    engine.environment.game = GameMock(cfg=configuration_one_agent)
    msg = _register_message(1, pm.application.ConnectedSubAppInfo.ROLE_ACTOR, 'id_interface')
    engine.init()

    engine.register(msg)

    assert engine.connected_sub_app['id_interface'].id == 'id_interface'
    assert engine.connected_sub_app['id_interface'].role == pm.application.ConnectedSubAppInfo.ROLE_ACTOR
    assert app.queues[0].qsize() == 0
    assert 'State.player0' in engine.environment.current_state.objects
    assert isinstance(engine.environment.current_state.objects['State.player0'], pm.common.Agent)


def test_render(configuration_two_agents):
    app = AppMock()
    engine = pm.engine.Engine(app=app, received_q_id=0, cfg=configuration_two_agents)
    _send_define(engine, app.queues[0])
    msg_register = _register_message(1, pm.application.ConnectedSubAppInfo.ROLE_ACTOR, 'id_interface')
    engine.define()
    engine.init()
    engine.register(msg_register)

    engine.render()

    assert isinstance(engine.environment.current_state.objects['State.agent1'].observation,
                      pm.common.State)
    msg_byte = app.queues[1].get_nowait()
    msg = Message(None, None, '')
    msg.from_bytes(msg_byte)
    assert msg.msg_type == Message.MESSAGE_TYPE_OBSERVATION
    engine_obs_str = tostring(engine.environment.current_state.objects['State.agent1'].observation.to_xml())
    msg_obs_str = tostring(msg.params['observation'])
    assert msg_obs_str == engine_obs_str


def test_handle_event_register_actor(configuration_one_agent):
    app = AppMock()
    engine = pm.engine.Engine(app=app, received_q_id=0, cfg=configuration_one_agent)
    _send_define(engine, app.queues[0])
    engine.define()
    engine.init()
    _send_register(engine, app.queues[0], 1, pm.application.ConnectedSubAppInfo.ROLE_ACTOR)

    engine.handle_event()

    # registered subapp id is 'id_interface'
    assert engine.connected_sub_app['id_interface'].id == 'id_interface'
    # registered subapp role is Actor
    assert engine.connected_sub_app['id_interface'].role == pm.application.ConnectedSubAppInfo.ROLE_ACTOR
    # the agent linked to the registered subapp is the first external agent
    assert engine.connected_sub_app['id_interface'].agent_fname == 'State.player0'
    # No event in the engine queue
    assert app.queues[0].qsize() == 0
    # 2 agents are created in the environement
    assert len(engine.environment.agents) == 2
    # the acting agent was agent1
    assert engine.environment.next_action['State.agent1'].type == GameMock.ACTION_TYPE_2
    # player1 is created in the environment
    assert 'State.player0' in engine.environment.current_state.objects
    # player1 is created in an agent
    assert isinstance(engine.environment.current_state.objects['State.player0'], pm.common.Agent)


def test_handle_event_action_actor_turn(configuration_one_agent):
    app = AppMock()
    engine = pm.engine.Engine(app=app, received_q_id=0, cfg=configuration_one_agent)
    _send_define(engine, app.queues[0])
    engine.define()
    engine.init()
    ag1 = engine.environment.current_state.objects['State.agent1']
    ag1.observation = engine.environment.current_state
    _send_register(engine, app.queues[0], 1, pm.application.ConnectedSubAppInfo.ROLE_ACTOR)

    # handling agent1 and register
    engine.handle_event()
    assert len(engine.environment.next_action) == 1
    assert 'State.agent1' in engine.environment.next_action.keys()
    assert engine.environment.next_action['State.agent1'].type == GameMock.ACTION_TYPE_2

    # Change the agent turn
    engine.environment.game.get_system_value(engine.environment.current_state, 'AgentOrder').move_head_next()
    pl11 = engine.environment.current_state.objects['State.player0']
    pl11.observation = engine.environment.current_state
    _send_action(engine, app.queues[0])

    # handling player1 action
    engine.handle_event()
    assert len(engine.environment.next_action) == 1
    assert 'State.player0' in engine.environment.next_action.keys()
    assert engine.environment.next_action['State.player0'].type == GameMock.ACTION_TYPE_1


def test_handle_event_action_observer():
    app = AppMock()
    cfg = OmegaConf.create()
    engine = pm.engine.Engine(app=app, received_q_id=0, cfg=cfg)
    _send_define(engine, app.queues[0])
    engine.define()
    engine.init()
    _send_register(engine, app.queues[0], 1, pm.application.ConnectedSubAppInfo.ROLE_OBSERVER)

    engine.handle_event()

    assert len(engine.environment.next_action) == 1
    assert 'State.agent1' in engine.environment.next_action.keys()
    assert engine.environment.next_action['State.agent1'].type == GameMock.ACTION_TYPE_2


def test_handle_event_action_quit():
    app = AppMock()
    cfg = OmegaConf.create()
    engine = pm.engine.Engine(app=app, received_q_id=0, cfg=cfg)
    _send_define(engine, app.queues[0])
    engine.define()
    engine.init()
    _send_quit(engine, app.queues[0])

    engine.handle_event()

    assert engine.environment.end_episode is True
