import os
import pytest

from omegaconf import OmegaConf

import pymasep as pm
from test.unit.mocks.game_mock import GameMock
from test.conftest import new_environment


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


@pytest.fixture(scope='function')
def configuration(base_directory_unit):
    cfg = OmegaConf.load(os.path.join(base_directory_unit, 'config', 'game_one_agent', 'app.yaml'))
    cfg['root_path'] = base_directory_unit
    yield cfg


def _create_struct(environment):
    state = pm.common.State(environment=environment, name='State',
                            template=environment.game.templates['SystemStateTemplate'])
    state.add_object(environment.create_object('agent1',
                                               template=environment.game.templates['EmptyAgentTemplate'],
                                               parent=state))
    state.add_object(environment.create_object('object1',
                                               template=environment.game.templates['DefaultObjectTemplate']))
    charac = pm.common.Characteristic(environment=environment, name='Step0',
                                      template=environment.game.templates['CharacInt'])
    state.objects['State.object1'].object_state.add_characteristic(charac)
    charac_int = pm.common.Characteristic(environment=environment, name='CharacInt',
                                          template=environment.game.templates['CharacInt'])
    state.objects['State.agent1'].object_state.add_characteristic(charac_int)
    return state


def test_init(base_directory_unit):
    cfg = OmegaConf.create()
    cfg['root_path'] = base_directory_unit

    game = pm.common.Game(cfg=cfg)

    assert isinstance(game.initializers, dict)
    assert len(game.initializers) == 0
    assert isinstance(game.templates, dict)
    assert len(game.templates) == 6
    assert len(game.default_templates) == 0
    assert game.is_rewarded is None
    assert game.nb_episodes == 1


def test_config(base_directory_unit):
    cfg = OmegaConf.load(os.path.join(base_directory_unit, 'config', 'game_one_agent', 'app.yaml'))
    cfg['root_path'] = base_directory_unit
    game = pm.common.Game(cfg=cfg)
    assert game.config.game.expected_agent_nb == 1


def test_initialize_state(mock_environment, base_directory_unit):
    current_state = mock_environment.game.initialize_state(mock_environment)

    assert 'State.system' in current_state.objects
    assert current_state.objects['State.system'].object_state.characteristics['AgentOrder'].state == 'run'
    assert mock_environment.game.get_system_value(current_state, 'AgentOrder').head == 'State.agent1'


def test_next_state_state(mock_environment):
    current_state = mock_environment.game.initialize_state(mock_environment)
    action = {'State.agent1': pm.common.Action(mock_environment, GameMock.ACTION_TYPE_2)}

    new_state, additional_info = mock_environment.game.next_state(current_state, action)

    assert isinstance(new_state, pm.common.State)
    assert new_state.objects['State.agent1'].object_state.characteristics['CharacInt'].value == 3
    assert new_state.objects['State.object1'].object_state.characteristics['Step0'].value == 1
    assert additional_info == {'info': 'Step=Step+1'}


def test_is_final_state_final(configuration):
    env = new_environment(configuration)
    state = _create_struct(env)
    state.objects['State.agent1'].object_state.characteristics['CharacInt'].value = 11

    result = env.game.is_final(state)

    assert result is True


def test_is_final_state_not_final_with_max_step(mock_environment_conf):
    state = _create_struct(mock_environment_conf)
    state.objects['State.agent1'].object_state.characteristics['CharacInt'].value = 3

    result = mock_environment_conf.game.is_final(state)

    assert result is False


def test_observe_state(mock_environment):
    state = _create_struct(mock_environment)
    state.objects['State.object1'].object_state.characteristics['Step0'].value = 3

    result = mock_environment.game.observe_state(state)

    assert state == result


def test_add_initializer_initializer_should_be_added(base_directory_unit):
    cfg = OmegaConf.create()
    cfg['root_path'] = base_directory_unit
    game = pm.common.Game(cfg=cfg)
    initializer = pm.common.initializer.Initializer(game, {'name': 'TestInitializer'})

    game.add_initializer(initializer)

    assert game.initializers['TestInitializer'] == initializer


def test_add_template_template_should_be_added(base_directory_unit):
    cfg = OmegaConf.create()
    cfg['root_path'] = base_directory_unit
    game = pm.common.Game(cfg=cfg)
    template_dict = {"name": "nameCharac", "created_class": "Characteristic", "value_type": "int"}
    initializer = pm.common.template.Template(game, template_dict)

    game.add_template(initializer)

    assert game.templates['nameCharac'] == initializer


def test_add_default_template_default_template_should_be_added(base_directory_unit):
    cfg = OmegaConf.create()
    cfg['root_path'] = base_directory_unit
    game = pm.common.Game(cfg=cfg)
    template_dict = {"name": "nameCharac", "created_class": "Characteristic", "value_type": "int"}
    template = pm.common.template.Template(game, template_dict)
    game.add_template(template)

    game.add_default_template('Characteristic', 'nameCharac')

    assert game.default_templates['Characteristic'] == template


def test_order_ogents_empty(mock_environment):
    current_order = []
    ag_to_add = pm.common.Agent(name='agent_to_add',
                                environment=mock_environment,
                                template=mock_environment.game.templates['DefaultAgentTemplate'])

    result = mock_environment.game.order_agents(current_order, ag_to_add)

    assert result == [ag_to_add.get_fullname()]


def test_order_ogents_fifo_empty(mock_environment):
    ag1 = pm.common.Agent(name='agent1', environment=mock_environment,
                          template=mock_environment.game.templates['DefaultAgentTemplate'])
    current_order = [ag1.get_fullname()]
    ag_to_add = pm.common.Agent(name='agent_to_add',
                                environment=mock_environment,
                                template=mock_environment.game.templates['DefaultAgentTemplate'])

    result = mock_environment.game.order_agents(current_order, ag_to_add)

    assert result == [ag1.get_fullname(), ag_to_add.get_fullname()]


def test_create_external_agent_no_agent_existing(base_directory_unit, mock_environment):
    cfg = OmegaConf.create({'game': {'max_nb_players': 2}})
    cfg['root_path'] = base_directory_unit
    game = GameMock(cfg=cfg)
    mock_environment.game = game
    mock_environment.current_state = pm.common.State(environment=mock_environment, name='State',
                                                     template=mock_environment.game.templates['SystemStateTemplate'])

    result = game.create_external_agent(mock_environment, 'interface_id')

    assert isinstance(result, pm.common.Agent)
    assert result.name == 'player0'


def test_create_external_agent_one_agent_exists(base_directory_unit, mock_environment):
    cfg = OmegaConf.create({'game': {'max_nb_players': 2}})
    cfg['root_path'] = base_directory_unit
    game = GameMock(cfg=cfg)
    mock_environment.game = game
    mock_environment.current_state = pm.common.State(environment=mock_environment, name='State',
                                                     template=mock_environment.game.templates['SystemStateTemplate'])
    _ = mock_environment.create_object(name='agent1', template=game.templates['DefaultAgentTemplate'])

    result = game.create_external_agent(mock_environment, 'interface_id')

    assert isinstance(result, pm.common.Agent)
    assert result.name == 'player0'
