from omegaconf import OmegaConf

from pymasep.common import Game
from pymasep.common.template import Template
from pymasep.common.initializer import InitializerFixed, Initializer, InitializerObject, InitializerObjectState, \
    InitializerCharacInterface, InitializerObjectStateInterface
from pymasep.common import Environment, State


class GameMock(Game):
    ACTION_TYPE_1 = 1
    ACTION_TYPE_2 = 2
    ACTION_TYPE_3 = 3
    ACTION_TYPE_4 = 4

    def __init__(self, cfg=None):
        if cfg is None:
            cfg = OmegaConf.create()
        if cfg.get('root_path') is None:
            cfg['root_path'] = '.'
        super().__init__(cfg=cfg)

    def add_templates(self):
        super().add_templates()
        # Templates for testing
        charac1_tmpl = Template(self, {"name": "CharacInt", "created_class": "Characteristic",
                                       "value_type": "int"})
        self.add_template(charac1_tmpl)

        charac2_tmpl = Template(self, {"name": "CharacStr", "created_class": "Characteristic",
                                       "value_type": "str"})
        self.add_template(charac2_tmpl)

        charac3_tmpl = Template(self, {"name": "CharacTuple", "created_class": "Characteristic",
                                       "value_type": "tuple"})
        self.add_template(charac3_tmpl)

        dflt_objectstate_tmpl = Template(self, {"name": "DefaultObjectStateTemplate",
                                                "created_class": "ObjectState",
                                                "subclass_templates": ["CharacInt", "CharacStr"]})
        self.add_template(dflt_objectstate_tmpl)

        empty_objectstate_tmpl = Template(self, {"name": "EmptyObjectStateTemplate",
                                                 "created_class": "ObjectState",
                                                 "subclass_templates": []})
        self.add_template(empty_objectstate_tmpl)

        dflt_object_tmpl = Template(self, {"name": "DefaultObjectTemplate",
                                           "created_class": "Object",
                                           "subclass_templates": ["DefaultObjectStateTemplate"]})
        self.add_template(dflt_object_tmpl)

        empty_object_tmpl = Template(self, {"name": "EmptyObjectTemplate",
                                            "created_class": "Object",
                                            "subclass_templates": ["EmptyObjectStateTemplate"]})
        self.add_template(empty_object_tmpl)

        dflt_agent_tmpl = Template(self, {"name": "DefaultAgentTemplate",
                                          "created_class": "Agent",
                                          "controller": "test.unit.mocks.controller_mock.ControllerMock",
                                          "subclass_templates": ["DefaultObjectStateTemplate"]})
        self.add_template(dflt_agent_tmpl)

        empty_agent_tmpl = Template(self, {"name": "EmptyAgentTemplate",
                                           "created_class": "Agent",
                                           "controller": "test.unit.mocks.controller_mock.ControllerMock",
                                           "subclass_templates": ["EmptyObjectStateTemplate"]})
        self.add_template(empty_agent_tmpl)

        dflt_bo_tmpl = Template(self, {"name": "DefaultBaseObjectTemplate",
                                       "nature": "baseobject",
                                       "created_class": "BaseObject"})
        self.add_template(dflt_bo_tmpl)

    def add_initializers(self):
        super().add_initializers()
        dummy_initializer = Initializer(self, {'name': 'DummyInitializer'})
        self.add_initializer(dummy_initializer)

        init_fixed_str_value = InitializerFixed(self,
                                                {'name': 'init_fixed_str', 'value_type': "str", 'value': 'OneValue'})
        self.add_initializer(init_fixed_str_value)
        init_fixed_int_value = InitializerFixed(self, {'name': 'init_fixed_int', 'value_type': "int", 'value': 1})
        self.add_initializer(init_fixed_int_value)

        interface_initializer = InitializerCharacInterface(self,
                                                           {'name': 'initializer_interface_int', 'value_type': "int",
                                                            'init_type': InitializerCharacInterface.INIT_TYPE_VALUE})
        self.add_initializer(interface_initializer)

        interface_initializer = InitializerCharacInterface(self,
                                                           {'name': 'initializer_interface_str', 'value_type': "str",
                                                            'init_type': InitializerCharacInterface.INIT_TYPE_VALUE})
        self.add_initializer(interface_initializer)

        initiliazer_obj_state = InitializerObjectState(self, {'name': 'ObjStInitializer',
                                                              'subclass_initializer': {'CharacStr': 'init_fixed_str',
                                                                                       'CharacInt': 'init_fixed_int'}})
        self.add_initializer(initiliazer_obj_state)

        initiliazer_os_interface = InitializerObjectStateInterface(self, {'name': 'ObjStInitializerInterface',
                                                                          'subclass_initializer': {
                                                                              'CharacStr': 'initializer_interface_str',
                                                                              'CharacInt': 'init_fixed_int'}})
        self.add_initializer(initiliazer_os_interface)

        object_initializer = InitializerObject(self, {'name': 'objectinitializer',
                                                      'subclass_initializer': {'objectstate': 'ObjStInitializer'}})
        self.add_initializer(object_initializer)

        object_initializer_interface = InitializerObject(self, {'name': 'objectinitializerinterface',
                                                                'subclass_initializer': {
                                                                    'objectstate': 'ObjStInitializerInterface'}})
        self.add_initializer(object_initializer_interface)

    def init_state(self, env: Environment) -> State:
        """
        Create the initial state of the game
        :return: the initial state
        """
        result = env.create_object(name='State', template=self.templates['SystemStateTemplate'], parent=None)
        obj = env.create_object(name='object1',
                                template=self.templates['EmptyObjectTemplate'],
                                parent=result)
        result.add_object(obj)
        ag = env.create_object(name='agent1',
                               template=self.templates['DefaultAgentTemplate'],
                               parent=result)
        ag.initializer = self.initializers['objectinitializerinterface']
        ag.initializer.apply(ag)
        result.add_object(ag)
        c1 = env.create_object(name='Step0', template=self.templates['CharacInt'], parent=obj.object_state)
        c1.value = 0
        obj.object_state.add_characteristic(c1)
        c2 = env.create_object(name='StrCharac', template=self.templates['CharacStr'], parent=obj.object_state)
        self.initializers['init_fixed_str'].apply(c2)
        obj.object_state.add_characteristic(c2)
        return result

    def init_state_empty(self, env: Environment) -> State:
        result = env.create_object(name='State', template=self.templates['DefaultStateTemplate'], parent=None)
        return result

    # def create_external_agent(self, env, external_id) -> Agent:
    #     """
    #     Create an agent for an external thread. Initialized as best as can the game do without external inputs
    #     :param: env the environment
    #     :param: external_id: uuid of the external thread/sub_app
    #     :return the agent
    #     """
    #     existing_agents = [ag.name for ag in env.agents]
    #     agent_name = [x for x in self.possible_players if x not in existing_agents]
    #     ag = env.create_object(name=agent_name[0],
    #                            template=self.templates['DefaultAgentTemplate'],
    #                            control='interface',
    #                            parent=env.current_state)
    #     ag.initializer = self.initializers['objectinitializerinterface']
    #     ag.initializer.apply(ag)
    #     ag.control = external_id
    #     ag.name = agent_name[0]
    #     return ag

    def is_final(self, state):
        """
        Is the state final ?
        :param state: the state
        :return: True if final, False if not
        """
        # all agent must have there CharacInt at 10 to end the game
        final = (len(state.agents) == self.config.game.expected_agent_nb)
        for ag in state.agents:
            if ag.object_state.characteristics['CharacInt'].value != 11:
                final = False
        return final

    def next_state(self, current_state, actions):
        """
        Calculates the new state according to the current state and the action of the agent.
        :param current_state the current state
        :param actions action of the agents
        :return: the new state
        """
        result = current_state
        result_additional_info = {}
        agent_play_order = self.get_system_value(result, 'AgentOrder')
        current_ag_name = agent_play_order.head
        ag = [ag for ag in result.agents if ag.get_fullname() == current_ag_name][0]
        if current_ag_name in actions:
            action = actions[current_ag_name]
            if ag.object_state.characteristics['CharacInt'].value < 11:
                ag.object_state.characteristics['CharacInt'].value += action.type
            result.objects['State.object1'].object_state.characteristics['Step0'].value += 1
            result_additional_info['info'] = 'Step=Step+1'
            self.get_system_value(result, 'AgentOrder').move_head_next()
        return result, result_additional_info

    def reward(self, previous_state, actions, next_state):
        """
        Return the reward for all agents depending of the previous_state, actions and the next state.
        This is the general form but each game car implement it as they want.
        @param previous_state the previous state before actions
        @param actions actions of the agents
        @param next_state next state usually calculated by next_state()
        @return a dictionary of the reward by agent.
        """
        result = {}
        for ag in next_state.agents:
            result[ag.get_fullname()] = 1
        return result
