class LogicStorageTests(testcase.TestCase):
    def setUp(self):
        super(LogicStorageTests, self).setUp()

        self.p1, self.p2, self.p3 = create_test_map()

        self.storage = LogicStorage()

        self.account_1 = self.accounts_factory.create_account()
        self.account_2 = self.accounts_factory.create_account()

        self.storage.load_account_data(self.account_1)
        self.storage.load_account_data(self.account_2)

        self.hero_1 = self.storage.accounts_to_heroes[self.account_1.id]
        self.hero_2 = self.storage.accounts_to_heroes[self.account_2.id]

        self.action_idl_1 = self.hero_1.actions.current_action
        self.action_idl_2 = self.hero_2.actions.current_action

        self.bundle_1_id = self.action_idl_1.bundle_id
        self.bundle_2_id = self.action_idl_2.bundle_id

    def test_load_account_data(self):
        self.assertEqual(len(self.storage.heroes), 2)
        self.assertEqual(len(self.storage.accounts_to_heroes), 2)
        self.assertEqual(
            self.storage.bundles_to_accounts, {
                self.hero_1.actions.current_action.bundle_id:
                set([self.account_1.id]),
                self.hero_2.actions.current_action.bundle_id:
                set([self.account_2.id])
            })

        action_regenerate = actions_prototypes.ActionRegenerateEnergyPrototype.create(
            hero=self.hero_1)

        self.assertEqual(self.action_idl_1.storage, self.storage)
        self.assertEqual(action_regenerate.storage, self.storage)

        storage = LogicStorage()
        storage.load_account_data(AccountPrototype.get_by_id(
            self.account_1.id))
        storage.load_account_data(AccountPrototype.get_by_id(
            self.account_2.id))
        self.assertEqual(len(storage.heroes), 2)
        self.assertEqual(len(storage.accounts_to_heroes), 2)
        self.assertEqual(
            storage.bundles_to_accounts, {
                self.hero_1.actions.current_action.bundle_id:
                set([self.account_1.id]),
                self.hero_2.actions.current_action.bundle_id:
                set([self.account_2.id])
            })

    def test_load_account_data_with_meta_action(self):
        bundle_id = 666

        meta_action_battle = meta_actions.ArenaPvP1x1.create(
            self.storage, self.hero_1, self.hero_2)

        proxy_action_1 = actions_prototypes.ActionMetaProxyPrototype.create(
            hero=self.hero_1,
            _bundle_id=bundle_id,
            meta_action=meta_action_battle)
        proxy_action_2 = actions_prototypes.ActionMetaProxyPrototype.create(
            hero=self.hero_2,
            _bundle_id=bundle_id,
            meta_action=meta_action_battle)

        self.assertEqual(len(self.storage.meta_actions), 1)
        self.assertEqual(len(self.storage.meta_actions_to_actions), 1)
        self.assertEqual(
            self.storage.meta_actions_to_actions[meta_action_battle.uid],
            set([
                LogicStorage.get_action_uid(proxy_action_1),
                LogicStorage.get_action_uid(proxy_action_2)
            ]))

        self.storage.save_changed_data()

        self.assertIs(self.hero_1.actions.current_action.meta_action,
                      self.hero_2.actions.current_action.meta_action)
        self.assertIs(self.hero_1.actions.current_action.saved_meta_action,
                      self.hero_2.actions.current_action.saved_meta_action)

        storage = LogicStorage()
        storage.load_account_data(AccountPrototype.get_by_id(
            self.account_1.id))
        storage.load_account_data(AccountPrototype.get_by_id(
            self.account_2.id))

        self.assertEqual(len(storage.meta_actions), 1)
        self.assertEqual(len(storage.meta_actions_to_actions), 1)
        self.assertEqual(
            storage.meta_actions_to_actions[meta_action_battle.uid],
            set([
                LogicStorage.get_action_uid(proxy_action_1),
                LogicStorage.get_action_uid(proxy_action_2)
            ]))

        self.assertEqual(
            storage.bundles_to_accounts, {
                self.hero_1.actions.current_action.bundle_id:
                set([self.account_1.id, self.account_2.id])
            })

        hero_1 = storage.accounts_to_heroes[self.account_1.id]
        hero_2 = storage.accounts_to_heroes[self.account_2.id]

        self.assertIs(hero_1.actions.current_action.meta_action,
                      hero_2.actions.current_action.meta_action)
        self.assertIsNot(hero_1.actions.current_action.saved_meta_action,
                         hero_2.actions.current_action.saved_meta_action)
        self.assertEqual(
            hero_1.actions.current_action.saved_meta_action.serialize(),
            hero_2.actions.current_action.saved_meta_action.serialize())

    def test_add_duplicate_hero(self):
        self.assertRaises(exceptions.HeroAlreadyRegisteredError,
                          self.storage._add_hero, self.hero_1)

    def test_action_release_account_data(self):

        actions_prototypes.ActionRegenerateEnergyPrototype.create(
            hero=self.hero_1)

        self.storage.skipped_heroes.add(self.hero_1.id)

        self.storage.release_account_data(self.account_1.id)

        self.assertEqual(len(self.storage.heroes), 1)
        self.assertEqual(len(self.storage.accounts_to_heroes), 1)
        self.assertEqual(self.storage.bundles_to_accounts, {
            self.hero_2.actions.current_action.bundle_id:
            set([self.account_2.id])
        })
        self.assertEqual(self.storage.heroes.values()[0].id, self.hero_2.id)
        self.assertFalse(self.storage.skipped_heroes)

    def test_save_hero_data(self):

        self.hero_1.health = 1
        self.hero_2.health = 1

        self.hero_1.actions.updated = True

        self.storage._save_hero_data(self.hero_1.id)

        self.assertEqual(self.hero_1.health,
                         HeroPrototype.get_by_id(self.hero_1.id).health)
        self.assertNotEqual(self.hero_2.health,
                            HeroPrototype.get_by_id(self.hero_2.id).health)

        self.assertFalse(self.hero_1.actions.updated)

    def test_save_all(self):

        self.hero_1.health = 1
        self.hero_2.health = 1

        self.hero_1.actions.updated = True

        self.storage.save_all()

        self.assertEqual(self.hero_1.health,
                         HeroPrototype.get_by_id(self.hero_1.id).health)
        self.assertEqual(self.hero_2.health,
                         HeroPrototype.get_by_id(self.hero_2.id).health)

        self.assertFalse(self.hero_1.actions.updated)

    def test_save_hero_data_with_meta_action(self):
        bundle_id = 666

        meta_action_battle = meta_actions.ArenaPvP1x1.create(
            self.storage, self.hero_1, self.hero_2)

        actions_prototypes.ActionMetaProxyPrototype.create(
            hero=self.hero_1,
            _bundle_id=bundle_id,
            meta_action=meta_action_battle)
        actions_prototypes.ActionMetaProxyPrototype.create(
            hero=self.hero_2,
            _bundle_id=bundle_id,
            meta_action=meta_action_battle)

        self.storage._save_hero_data(self.hero_1.id)
        self.storage._save_hero_data(self.hero_2.id)

        self.hero_1.reload()
        self.hero_2.reload()

        self.assertEqual(
            meta_action_battle.serialize(),
            self.hero_1.actions.current_action.saved_meta_action.serialize())
        self.assertEqual(
            meta_action_battle.serialize(),
            self.hero_2.actions.current_action.saved_meta_action.serialize())

    def test_switch_caches(self):
        self.assertEqual(self.storage.previous_cache, {})
        self.assertEqual(self.storage.current_cache, {})

        self.storage.previous_cache[1] = 2
        self.storage.current_cache[3] = 4

        self.storage.switch_caches()

        self.assertEqual(self.storage.previous_cache, {3: 4})
        self.assertEqual(self.storage.current_cache, {})

        self.storage.current_cache[5] = 6

        self.storage.switch_caches()

        self.assertEqual(self.storage.previous_cache, {5: 6})
        self.assertEqual(self.storage.current_cache, {})

    def test_process_cache_queue__with_update(self):
        self.assertEqual(self.storage.cache_queue, set())

        self.storage.cache_queue.add(self.hero_2.id)
        self.storage.cache_queue.add(self.hero_1.id)

        self.storage.process_cache_queue(update_cache=True)

        self.assertItemsEqual(
            self.storage.current_cache.keys(),
            (self.hero_1.cached_ui_info_key, self.hero_2.cached_ui_info_key))
        self.assertEqual(self.storage.cache_queue, set())

    def test_process_cache_queue__without_update(self):
        self.assertEqual(self.storage.cache_queue, set())

        self.storage.cache_queue.add(self.hero_2.id)
        self.storage.cache_queue.add(self.hero_1.id)

        self.storage.process_cache_queue(update_cache=False)

        self.assertItemsEqual(self.storage.current_cache.keys(), ())
        self.assertEqual(self.storage.cache_queue, set())

    def test_process_cache_queue__update_cache__with_update(self):
        self.assertEqual(self.storage.cache_queue, set())

        self.storage.current_cache[self.hero_1.cached_ui_info_key] = 1
        self.storage.current_cache[self.hero_2.cached_ui_info_key] = 2

        self.storage.cache_queue.add(self.hero_2.id)

        self.storage.process_cache_queue(update_cache=True)

        self.assertEqual(
            self.storage.current_cache[self.hero_1.cached_ui_info_key], 1)
        self.assertNotEqual(
            self.storage.current_cache[self.hero_2.cached_ui_info_key], 2)

    def test_process_cache_queue__update_cache__without_update(self):
        self.assertEqual(self.storage.cache_queue, set())

        self.storage.current_cache[self.hero_1.cached_ui_info_key] = 1
        self.storage.current_cache[self.hero_2.cached_ui_info_key] = 2

        self.storage.cache_queue.add(self.hero_2.id)

        self.storage.process_cache_queue(update_cache=False)

        self.assertEqual(
            self.storage.current_cache[self.hero_1.cached_ui_info_key], 1)
        self.assertEqual(
            self.storage.current_cache[self.hero_2.cached_ui_info_key], 2)

    @mock.patch('the_tale.game.heroes.conf.heroes_settings.DUMP_CACHED_HEROES',
                True)
    def test_process_turn(self):
        self.assertEqual(self.storage.skipped_heroes, set())
        self.storage.process_turn()
        self.assertEqual(self.storage.skipped_heroes, set())

        with mock.patch(
                'the_tale.game.logic_storage.LogicStorage._save_hero_data'
        ) as save_hero_data:
            self.storage.save_changed_data()

        self.assertEqual(save_hero_data.call_count, 2)

    @mock.patch('the_tale.game.heroes.conf.heroes_settings.DUMP_CACHED_HEROES',
                True)
    def test_process_turn__switch_caches(self):
        self.assertEqual(self.storage.previous_cache, {})
        self.assertEqual(self.storage.current_cache, {})

        self.storage.process_turn()
        self.storage.save_changed_data()

        self.assertEqual(self.storage.previous_cache, {})
        self.assertNotEqual(self.storage.current_cache, {})

        old_cache = self.storage.current_cache

        self.storage.process_turn()
        self.storage.save_changed_data()

        self.assertEqual(self.storage.previous_cache, old_cache)
        self.assertNotEqual(self.storage.current_cache, old_cache)

    def test_process_turn_single_hero__runned_outside_storage(self):
        action_1 = actions_prototypes.ActionRegenerateEnergyPrototype.create(
            hero=self.hero_1)
        action_1.state = action_1.STATE.PROCESSED

        action_2 = actions_prototypes.ActionMoveToPrototype.create(
            hero=self.hero_1, destination=self.p1)
        action_2.state = action_2.STATE.PROCESSED

        action_3 = actions_prototypes.ActionInPlacePrototype.create(
            hero=self.hero_1)
        action_3.state = action_3.STATE.PROCESSED

        self.assertEqual(self.hero_1.actions.number, 4)

        self.storage.process_turn__single_hero(hero=self.hero_1,
                                               logger=None,
                                               continue_steps_if_needed=True)

        self.assertEqual(self.hero_1.actions.number, 2)
        self.assertEqual(self.hero_1.actions.current_action.TYPE,
                         actions_prototypes.ActionQuestPrototype.TYPE)

        self.storage.process_turn()  # just nothing was broken

    def test_process_turn__process_action_chain(self):
        action_1 = actions_prototypes.ActionRegenerateEnergyPrototype.create(
            hero=self.hero_1)
        action_1.state = action_1.STATE.PROCESSED

        action_2 = actions_prototypes.ActionMoveToPrototype.create(
            hero=self.hero_1, destination=self.p1)
        action_2.state = action_2.STATE.PROCESSED

        action_3 = actions_prototypes.ActionInPlacePrototype.create(
            hero=self.hero_1)
        action_3.state = action_3.STATE.PROCESSED

        self.assertEqual(self.hero_1.actions.number, 4)

        self.storage.process_turn()

        self.assertEqual(self.hero_1.actions.number, 2)
        self.assertEqual(self.hero_1.actions.current_action.TYPE,
                         actions_prototypes.ActionQuestPrototype.TYPE)

    @mock.patch('the_tale.game.heroes.conf.heroes_settings.DUMP_CACHED_HEROES',
                False)
    def test_process_turn__without_dump(self):
        self.assertEqual(self.storage.skipped_heroes, set())
        self.storage.process_turn()
        self.assertEqual(self.storage.skipped_heroes, set())

        with mock.patch(
                'the_tale.game.logic_storage.LogicStorage._save_hero_data'
        ) as save_hero_data:
            self.storage.save_changed_data()

        self.assertEqual(
            save_hero_data.call_count, 1
        )  # save only game_settings.SAVED_UNCACHED_HEROES_FRACTION bundles number

    def test_process_turn__process_created_action(self):
        from the_tale.game.actions.prototypes import ActionMoveToPrototype

        place = self.p1

        def process_action(self):
            ActionMoveToPrototype.create(hero=self.hero, destination=place)

        with mock.patch(
                'the_tale.game.actions.prototypes.ActionIdlenessPrototype.process',
                process_action):
            with mock.patch(
                    'the_tale.game.actions.prototypes.ActionMoveToPrototype.process'
            ) as move_to_process:
                self.storage.process_turn()

        self.assertEqual(move_to_process.call_count, 2)

    @mock.patch('the_tale.game.heroes.conf.heroes_settings.DUMP_CACHED_HEROES',
                True)
    def test_process_turn_with_skipped_hero(self):
        # skipped heroes saved, but not processed
        self.storage.skipped_heroes.add(self.hero_1.id)

        with mock.patch(
                'the_tale.game.actions.prototypes.ActionBase.process_turn'
        ) as action_process_turn:
            self.storage.process_turn()

        self.assertEqual(action_process_turn.call_count, 1)

        with mock.patch(
                'the_tale.game.logic_storage.LogicStorage._save_hero_data'
        ) as save_hero_data:
            self.storage.save_changed_data()

        self.assertEqual(save_hero_data.call_count, 2)

    @mock.patch('the_tale.game.heroes.conf.heroes_settings.DUMP_CACHED_HEROES',
                False)
    def test_process_turn_with_skipped_hero__without_cache_dump(self):
        # skipped heroes saved, but not processed
        self.storage.skipped_heroes.add(self.hero_1.id)

        with mock.patch(
                'the_tale.game.actions.prototypes.ActionBase.process_turn'
        ) as action_process_turn:
            self.storage.process_turn()

        self.assertEqual(action_process_turn.call_count, 1)

        with mock.patch(
                'the_tale.game.logic_storage.LogicStorage._save_hero_data'
        ) as save_hero_data:
            self.storage.save_changed_data()

        self.assertEqual(save_hero_data.call_count, 1)

    @mock.patch(
        'the_tale.game.heroes.prototypes.HeroPrototype.can_process_turn',
        lambda self, turn: True)
    def test_process_turn__can_process_turn(self):
        with mock.patch(
                'the_tale.game.actions.prototypes.ActionBase.process_turn'
        ) as action_process_turn:
            self.storage.process_turn(continue_steps_if_needed=False)

        self.assertEqual(action_process_turn.call_count, 2)

    @mock.patch(
        'the_tale.game.heroes.prototypes.HeroPrototype.can_process_turn',
        lambda self, turn: False)
    def test_process_turn__can_not_process_turn(self):
        with mock.patch(
                'the_tale.game.actions.prototypes.ActionBase.process_turn'
        ) as action_process_turn:
            self.storage.process_turn(continue_steps_if_needed=False)

        self.assertEqual(action_process_turn.call_count, 0)

    def test_process_turn___exception_raises(self):
        def process_turn_raise_exception(action):
            if action.hero.id == self.hero_2.id:
                raise Exception('error')

        with mock.patch(
                'the_tale.game.actions.prototypes.ActionBase.process_turn',
                process_turn_raise_exception):
            with mock.patch(
                    'the_tale.game.logic_storage.LogicStorage._save_on_exception'
            ) as _save_on_exception:
                with mock.patch('django.conf.settings.TESTS_RUNNING', False):
                    self.storage.process_turn()

        self.assertIn(self.hero_2.actions.current_action.bundle_id,
                      self.storage.ignored_bundles)

        self.assertEqual(_save_on_exception.call_count, 1)
        self.assertEqual(_save_on_exception.call_args, mock.call())

    @mock.patch('the_tale.game.conf.game_settings.SAVE_ON_EXCEPTION_TIMEOUT',
                0)
    def test_save_on_exception(self):
        # hero 1 not saved due to one bundle with hero 3
        # hero 2 saved
        # hero 3 not saved
        # hero 4 saved

        result, account_3_id, bundle_3_id = register_user(
            'test_user_3', '*****@*****.**', '111111')
        self.storage.load_account_data(
            AccountPrototype.get_by_id(account_3_id))
        hero_3 = self.storage.accounts_to_heroes[account_3_id]

        result, account_4_id, bundle_4_id = register_user(
            'test_user_4', '*****@*****.**', '111111')
        self.storage.load_account_data(
            AccountPrototype.get_by_id(account_4_id))
        hero_4 = self.storage.accounts_to_heroes[account_4_id]

        self.hero_1.actions.current_action.bundle_id = hero_3.actions.current_action.bundle_id

        saved_heroes = set()

        def save_hero_data(storage, hero_id, **kwargs):
            saved_heroes.add(hero_id)

        self.storage.ignored_bundles.add(
            hero_3.actions.current_action.bundle_id)

        with mock.patch(
                'the_tale.game.logic_storage.LogicStorage._save_hero_data',
                save_hero_data):
            self.storage._save_on_exception()

        self.assertEqual(saved_heroes, set([self.hero_2.id, hero_4.id]))

    def test_save_on_exception__time_border(self):
        # hero 1 not saved due to one bundle with hero 3
        # hero 2 saved
        # hero 3 not saved
        # hero 4 saved

        result, account_3_id, bundle_3_id = register_user(
            'test_user_3', '*****@*****.**', '111111')
        self.storage.load_account_data(
            AccountPrototype.get_by_id(account_3_id))
        hero_3 = self.storage.accounts_to_heroes[account_3_id]

        result, account_4_id, bundle_4_id = register_user(
            'test_user_4', '*****@*****.**', '111111')
        self.storage.load_account_data(
            AccountPrototype.get_by_id(account_4_id))
        hero_4 = self.storage.accounts_to_heroes[account_4_id]

        self.hero_1.actions.current_action.bundle_id = hero_3.actions.current_action.bundle_id

        saved_heroes = set()

        self.hero_2._model.saved_at = datetime.datetime.now(
        ) - datetime.timedelta(
            seconds=conf.game_settings.SAVE_ON_EXCEPTION_TIMEOUT + 1)

        def save_hero_data(storage, hero_id, **kwargs):
            saved_heroes.add(hero_id)

        self.storage.ignored_bundles.add(
            hero_3.actions.current_action.bundle_id)

        with mock.patch(
                'the_tale.game.logic_storage.LogicStorage._save_hero_data',
                save_hero_data):
            self.storage._save_on_exception()

        self.assertEqual(saved_heroes, set([self.hero_2.id]))

    def test_save_changed_data(self):
        self.storage.process_turn()

        with mock.patch('dext.common.utils.cache.set_many') as set_many:
            with mock.patch(
                    'the_tale.game.heroes.prototypes.HeroPrototype.ui_info'
            ) as ui_info:
                self.storage.save_changed_data()

        self.assertEqual(set_many.call_count, 1)
        self.assertEqual(ui_info.call_count, 2)
        self.assertEqual(ui_info.call_args_list, [
            mock.call(actual_guaranteed=True, old_info=None),
            mock.call(actual_guaranteed=True, old_info=None)
        ])

    def test_old_info(self):
        self.storage.process_turn()

        calls = []

        def ui_info(hero, **kwargs):
            calls.append(kwargs)
            return {'hero': hero.id}

        with mock.patch(
                'the_tale.game.heroes.prototypes.HeroPrototype.ui_info',
                ui_info):
            self.storage.save_changed_data()
            self.storage.process_turn()
            self.storage.save_changed_data()

        self.assertEqual(calls, [{
            'actual_guaranteed': True,
            'old_info': None
        }, {
            'actual_guaranteed': True,
            'old_info': None
        }, {
            'actual_guaranteed': True,
            'old_info': {
                'hero': self.hero_1.id
            }
        }, {
            'actual_guaranteed': True,
            'old_info': {
                'hero': self.hero_2.id
            }
        }])

    def test_save_changed_data__old_info(self):
        self.storage.process_turn()
        self.storage.save_changed_data()

        self.storage.process_turn()

        with mock.patch('dext.common.utils.cache.set_many') as set_many:
            with mock.patch(
                    'the_tale.game.heroes.prototypes.HeroPrototype.ui_info'
            ) as ui_info:
                self.storage.save_changed_data()

        self.assertEqual(set_many.call_count, 1)
        self.assertEqual(ui_info.call_count, 2)
        self.assertNotEqual(ui_info.call_args_list[0][1]['old_info'], None)
        self.assertNotEqual(ui_info.call_args_list[1][1]['old_info'], None)

    @mock.patch('the_tale.game.heroes.conf.heroes_settings.DUMP_CACHED_HEROES',
                True)
    def test_save_changed_data__with_unsaved_bundles(self):
        self.storage.process_turn()

        self.assertEqual(len(self.storage.heroes), 2)

        with mock.patch(
                'the_tale.game.logic_storage.LogicStorage._get_bundles_to_save',
                lambda x: [self.bundle_2_id]):
            with mock.patch(
                    'the_tale.game.logic_storage.LogicStorage._save_hero_data'
            ) as save_hero_data:
                with mock.patch(
                        'the_tale.game.heroes.prototypes.HeroPrototype.ui_info'
                ) as ui_info:
                    self.storage.save_changed_data()

        self.assertEqual(ui_info.call_count,
                         2)  # cache all heroes, since they are new
        self.assertEqual(ui_info.call_args_list, [
            mock.call(actual_guaranteed=True, old_info=None),
            mock.call(actual_guaranteed=True, old_info=None)
        ])
        self.assertEqual(save_hero_data.call_args, mock.call(self.hero_2.id))

    def test_save_changed_data__with_unsaved_bundles__without_dump(self):
        self.storage.process_turn()

        self.assertEqual(len(self.storage.heroes), 2)

        self.hero_2.ui_caching_started_at = datetime.datetime.fromtimestamp(0)

        with mock.patch(
                'the_tale.game.logic_storage.LogicStorage._get_bundles_to_save',
                lambda x: [self.bundle_2_id]):
            with mock.patch(
                    'the_tale.game.logic_storage.LogicStorage._save_hero_data'
            ) as save_hero_data:
                with mock.patch(
                        'the_tale.game.heroes.prototypes.HeroPrototype.ui_info'
                ) as ui_info:
                    self.storage.save_changed_data()

        self.assertEqual(ui_info.call_count, 1)  # cache only first hero
        self.assertEqual(ui_info.call_args,
                         mock.call(actual_guaranteed=True, old_info=None))
        self.assertEqual(save_hero_data.call_args, mock.call(self.hero_2.id))

    def test_remove_action__from_middle(self):
        actions_prototypes.ActionRegenerateEnergyPrototype.create(
            hero=self.hero_1)
        self.assertRaises(exceptions.RemoveActionFromMiddleError,
                          self.storage.remove_action, self.action_idl_1)

    def test_remove_action__metaaction(self):
        bundle_id = 666

        meta_action_battle = meta_actions.ArenaPvP1x1.create(
            self.storage, self.hero_1, self.hero_2)

        proxy_action_1 = actions_prototypes.ActionMetaProxyPrototype.create(
            hero=self.hero_1,
            _bundle_id=bundle_id,
            meta_action=meta_action_battle)
        proxy_action_2 = actions_prototypes.ActionMetaProxyPrototype.create(
            hero=self.hero_2,
            _bundle_id=bundle_id,
            meta_action=meta_action_battle)

        self.assertEqual(len(self.storage.meta_actions), 1)
        self.assertEqual(len(self.storage.meta_actions_to_actions), 1)
        self.assertEqual(
            self.storage.meta_actions_to_actions[meta_action_battle.uid],
            set([
                LogicStorage.get_action_uid(proxy_action_1),
                LogicStorage.get_action_uid(proxy_action_2)
            ]))

        self.storage.remove_action(proxy_action_2)

        self.assertEqual(len(self.storage.meta_actions), 1)
        self.assertEqual(len(self.storage.meta_actions_to_actions), 1)
        self.assertEqual(
            self.storage.meta_actions_to_actions[meta_action_battle.uid],
            set([LogicStorage.get_action_uid(proxy_action_1)]))

        self.storage.remove_action(proxy_action_1)

        self.assertEqual(len(self.storage.meta_actions), 0)
        self.assertEqual(len(self.storage.meta_actions_to_actions), 0)

    @mock.patch('the_tale.game.heroes.conf.heroes_settings.DUMP_CACHED_HEROES',
                True)
    @mock.patch(
        'the_tale.game.conf.game_settings.SAVED_UNCACHED_HEROES_FRACTION', 0)
    def test_get_bundles_to_save(self):
        # hero 1 not saved
        # hero 2 saved by quota
        # hero 3 saved by caching
        # hero 4 not saved

        result, account_3_id, bundle_3_id = register_user(
            'test_user_3', '*****@*****.**', '111111')
        result, account_4_id, bundle_4_id = register_user(
            'test_user_4', '*****@*****.**', '111111')
        result, account_5_id, bundle_5_id = register_user(
            'test_user_5', '*****@*****.**', '111111')

        self.storage.load_account_data(
            AccountPrototype.get_by_id(account_3_id))
        self.storage.load_account_data(
            AccountPrototype.get_by_id(account_4_id))
        self.storage.load_account_data(
            AccountPrototype.get_by_id(account_5_id))

        hero_3 = self.storage.accounts_to_heroes[account_3_id]
        hero_4 = self.storage.accounts_to_heroes[account_4_id]

        self.hero_1._model.saved_at = datetime.datetime.now()
        self.hero_1.ui_caching_started_at = datetime.datetime.fromtimestamp(0)
        self.hero_2.ui_caching_started_at = datetime.datetime.fromtimestamp(0)
        hero_4.ui_caching_started_at = datetime.datetime.fromtimestamp(0)

        self.assertTrue(self.hero_1.saved_at > self.hero_2.saved_at)

        self.assertFalse(self.hero_1.is_ui_caching_required)
        self.assertFalse(self.hero_2.is_ui_caching_required)
        self.assertTrue(hero_3.is_ui_caching_required)
        self.assertFalse(hero_4.is_ui_caching_required)

        self.assertEqual(self.storage._get_bundles_to_save(),
                         set([self.bundle_2_id, bundle_3_id, bundle_5_id]))

    @mock.patch('the_tale.game.heroes.conf.heroes_settings.DUMP_CACHED_HEROES',
                True)
    @mock.patch(
        'the_tale.game.conf.game_settings.SAVED_UNCACHED_HEROES_FRACTION', 0)
    def test_get_bundles_to_save__force_save_required(self):
        # hero 1 not saved
        # hero 2 saved by quota
        # hero 3 saved by caching
        # hero 4 saved by force

        result, account_3_id, bundle_3_id = register_user(
            'test_user_3', '*****@*****.**', '111111')
        result, account_4_id, bundle_4_id = register_user(
            'test_user_4', '*****@*****.**', '111111')
        result, account_5_id, bundle_5_id = register_user(
            'test_user_5', '*****@*****.**', '111111')

        self.storage.load_account_data(
            AccountPrototype.get_by_id(account_3_id))
        self.storage.load_account_data(
            AccountPrototype.get_by_id(account_4_id))
        self.storage.load_account_data(
            AccountPrototype.get_by_id(account_5_id))

        hero_3 = self.storage.accounts_to_heroes[account_3_id]
        hero_4 = self.storage.accounts_to_heroes[account_4_id]

        hero_4.force_save_required = True

        self.hero_1._model.saved_at = datetime.datetime.now()
        self.hero_1.ui_caching_started_at = datetime.datetime.fromtimestamp(0)
        self.hero_2.ui_caching_started_at = datetime.datetime.fromtimestamp(0)
        hero_4.ui_caching_started_at = datetime.datetime.fromtimestamp(0)

        self.assertTrue(self.hero_1.saved_at > self.hero_2.saved_at)

        self.assertFalse(self.hero_1.is_ui_caching_required)
        self.assertFalse(self.hero_2.is_ui_caching_required)
        self.assertTrue(hero_3.is_ui_caching_required)
        self.assertFalse(hero_4.is_ui_caching_required)

        self.assertEqual(
            self.storage._get_bundles_to_save(),
            set([self.bundle_2_id, bundle_3_id, bundle_4_id, bundle_5_id]))

        self.assertFalse(hero_4.force_save_required)

    @mock.patch('the_tale.game.heroes.conf.heroes_settings.DUMP_CACHED_HEROES',
                False)
    @mock.patch(
        'the_tale.game.conf.game_settings.SAVED_UNCACHED_HEROES_FRACTION', 0)
    def test_get_bundles_to_save__without_cache_dump(self):
        # hero 1 not saved
        # hero 2 saved by quota
        # hero 3 does not saved by caching
        # hero 4 not saved

        result, account_3_id, bundle_3_id = register_user(
            'test_user_3', '*****@*****.**', '111111')
        result, account_4_id, bundle_4_id = register_user(
            'test_user_4', '*****@*****.**', '111111')

        self.storage.load_account_data(
            AccountPrototype.get_by_id(account_3_id))
        self.storage.load_account_data(
            AccountPrototype.get_by_id(account_4_id))

        hero_3 = self.storage.accounts_to_heroes[account_3_id]
        hero_4 = self.storage.accounts_to_heroes[account_4_id]

        self.hero_1._model.saved_at = datetime.datetime.now()
        self.hero_1.ui_caching_started_at = datetime.datetime.fromtimestamp(0)
        self.hero_2.ui_caching_started_at = datetime.datetime.fromtimestamp(0)
        hero_4.ui_caching_started_at = datetime.datetime.fromtimestamp(0)

        self.assertTrue(self.hero_1.saved_at > self.hero_2.saved_at)

        self.assertFalse(self.hero_1.is_ui_caching_required)
        self.assertFalse(self.hero_2.is_ui_caching_required)
        self.assertTrue(hero_3.is_ui_caching_required)
        self.assertFalse(hero_4.is_ui_caching_required)

        self.assertEqual(self.storage._get_bundles_to_save(),
                         set([self.bundle_2_id]))

    @mock.patch('the_tale.game.heroes.conf.heroes_settings.DUMP_CACHED_HEROES',
                True)
    @mock.patch(
        'the_tale.game.conf.game_settings.SAVED_UNCACHED_HEROES_FRACTION', 0)
    def test_save_changed_data__with_multiple_heroes_to_bundle(self):
        # hero 1 saved by bundle from hero 3
        # hero 2 saved by quota
        # hero 3 saved by caching

        result, account_3_id, bundle_3_id = register_user(
            'test_user_3', '*****@*****.**', '111111')

        self.storage.load_account_data(
            AccountPrototype.get_by_id(account_3_id))

        hero_3 = self.storage.accounts_to_heroes[account_3_id]

        self.hero_1._model.saved_at = datetime.datetime.now()
        self.hero_1.ui_caching_started_at = datetime.datetime.fromtimestamp(0)
        self.hero_1.actions.current_action.bundle_id = hero_3.actions.current_action.bundle_id
        self.hero_2.ui_caching_started_at = datetime.datetime.fromtimestamp(0)

        self.assertTrue(self.hero_1.saved_at > self.hero_2.saved_at)
        self.assertFalse(self.hero_1.is_ui_caching_required)
        self.assertFalse(self.hero_2.is_ui_caching_required)
        self.assertTrue(hero_3.is_ui_caching_required)

        self.assertEqual(self.storage._get_bundles_to_save(),
                         set([self.bundle_2_id, bundle_3_id]))

        self.storage.process_turn()

        with mock.patch('dext.common.utils.cache.set_many') as set_many:
            with mock.patch(
                    'the_tale.game.logic_storage.LogicStorage._save_hero_data'
            ) as save_hero_data:
                with mock.patch(
                        'the_tale.game.heroes.prototypes.HeroPrototype.ui_info'
                ) as ui_info:
                    self.storage.save_changed_data()

        self.assertEqual(set_many.call_count, 1)
        self.assertEqual(save_hero_data.call_count, 3)
        self.assertEqual(ui_info.call_count, 2)
        self.assertEqual(ui_info.call_args_list, [
            mock.call(actual_guaranteed=True, old_info=None),
            mock.call(actual_guaranteed=True, old_info=None)
        ])

    @mock.patch('the_tale.game.heroes.conf.heroes_settings.DUMP_CACHED_HEROES',
                False)
    @mock.patch(
        'the_tale.game.conf.game_settings.SAVED_UNCACHED_HEROES_FRACTION', 0)
    def test_save_changed_data__with_multiple_heroes_to_bundle__without_cache_dump(
            self):
        # hero 1 saved by bundle from hero 2
        # hero 2 saved by quota
        # hero 3 does not saved by caching

        result, account_3_id, bundle_3_id = register_user(
            'test_user_3', '*****@*****.**', '111111')

        self.storage.load_account_data(
            AccountPrototype.get_by_id(account_3_id))

        hero_3 = self.storage.accounts_to_heroes[account_3_id]

        self.hero_1._model.saved_at = datetime.datetime.now()
        self.hero_1.ui_caching_started_at = datetime.datetime.fromtimestamp(0)
        self.hero_1.actions.current_action.bundle_id = self.hero_2.actions.current_action.bundle_id
        self.hero_2.ui_caching_started_at = datetime.datetime.fromtimestamp(0)

        self.assertTrue(self.hero_1.saved_at > self.hero_2.saved_at)
        self.assertFalse(self.hero_1.is_ui_caching_required)
        self.assertFalse(self.hero_2.is_ui_caching_required)
        self.assertTrue(hero_3.is_ui_caching_required)

        self.assertEqual(self.storage._get_bundles_to_save(),
                         set([self.bundle_2_id]))

        self.storage.process_turn()

        with mock.patch('dext.common.utils.cache.set_many') as set_many:
            with mock.patch(
                    'the_tale.game.logic_storage.LogicStorage._save_hero_data'
            ) as save_hero_data:
                with mock.patch(
                        'the_tale.game.heroes.prototypes.HeroPrototype.ui_info'
                ) as ui_info:
                    self.storage.save_changed_data()

        self.assertEqual(set_many.call_count, 1)
        self.assertEqual(save_hero_data.call_count, 2)
        self.assertEqual(ui_info.call_count, 1)
        self.assertEqual(ui_info.call_args,
                         mock.call(actual_guaranteed=True, old_info=None))

    def test_merge_bundles(self):

        storage = LogicStorage()

        storage.bundles_to_accounts[555] = set([1, 2])
        storage.bundles_to_accounts[666] = set([3])

        storage.merge_bundles([555, 666], 777)

        self.assertEqual(storage.bundles_to_accounts, {777: set([1, 2, 3])})

    def test_merge_bundles__in_existed_bundle(self):

        storage = LogicStorage()

        storage.bundles_to_accounts[555] = set([1, 2])
        storage.bundles_to_accounts[666] = set([3])
        storage.bundles_to_accounts[777] = set([4, 5])

        storage.merge_bundles([555, 666], 777)

        self.assertEqual(storage.bundles_to_accounts,
                         {777: set([1, 2, 3, 4, 5])})

    def test_unmerge_bundles__in_existed_bundle(self):
        storage = LogicStorage()

        storage.bundles_to_accounts[555] = set([1, 2])
        storage.bundles_to_accounts[666] = set([3])
        storage.bundles_to_accounts[777] = set([4, 5])

        storage.unmerge_bundles(4, 777, 666)

        self.assertEqual(storage.bundles_to_accounts, {
            555: set([1, 2]),
            666: set([3, 4]),
            777: set([5])
        })

    def test_unmerge_bundles__last_account_in_bundle(self):
        storage = LogicStorage()

        storage.bundles_to_accounts[555] = set([1, 2])
        storage.bundles_to_accounts[666] = set([3])
        storage.bundles_to_accounts[777] = set([4, 5])

        storage.unmerge_bundles(3, 666, 555)

        self.assertEqual(storage.bundles_to_accounts, {
            555: set([1, 2, 3]),
            777: set([4, 5])
        })

    def test_unmerge_bundles(self):
        storage = LogicStorage()

        storage.bundles_to_accounts[555] = set([1, 2])
        storage.bundles_to_accounts[666] = set([3])
        storage.bundles_to_accounts[777] = set([4, 5])

        storage.unmerge_bundles(4, 777, 888)

        self.assertEqual(storage.bundles_to_accounts, {
            555: set([1, 2]),
            666: set([3]),
            777: set([5]),
            888: set([4])
        })

    def test_save_bundle_data(self):

        storage = LogicStorage()

        storage.bundles_to_accounts[555] = set([1, 2])
        storage.bundles_to_accounts[666] = set([3, 7, 9])
        storage.bundles_to_accounts[777] = set([4, 5])

        storage.accounts_to_heroes = {
            1: mock.Mock(id=1),
            2: mock.Mock(id=2),
            3: mock.Mock(id=3),
            4: mock.Mock(id=4),
            5: mock.Mock(id=5),
            7: mock.Mock(id=7),
            9: mock.Mock(id=9)
        }

        with mock.patch(
                'the_tale.game.logic_storage.LogicStorage._save_hero_data'
        ) as _save_hero_data:
            with mock.patch(
                    'the_tale.game.logic_storage.LogicStorage.process_cache_queue'
            ) as process_cache_queue:
                storage.save_bundle_data(666)

        self.assertEqual(_save_hero_data.call_count, 3)
        self.assertEqual(storage.cache_queue, set([3, 7, 9]))
        self.assertEqual(process_cache_queue.call_count, 1)

        self.assertEqual(
            set(call[0][0] for call in _save_hero_data.call_args_list),
            set([3, 7, 9]))
Example #2
0
class Worker(workers.BaseWorker):
    STOP_SIGNAL_REQUIRED = False

    def initialize(self):
        # worker initialized by supervisor
        pass

    def cmd_initialize(self, turn_number, worker_id):
        self.send_cmd('initialize', {
            'turn_number': turn_number,
            'worker_id': worker_id
        })

    def process_initialize(self, turn_number, worker_id):

        if self.initialized:
            self.logger.warn(
                'WARNING: game already initialized, do reinitialization')

        self.storage = LogicStorage()

        self.initialized = True
        self.turn_number = turn_number
        self.queue = []
        self.worker_id = worker_id

        self.logger.info('GAME INITIALIZED')

        environment.workers.supervisor.cmd_answer('initialize', self.worker_id)

    def cmd_next_turn(self, turn_number):
        return self.send_cmd('next_turn', data={'turn_number': turn_number})

    # @profile.profile_decorator('/home/tie/repos/mine/the-tale/profile.info')
    def process_next_turn(self, turn_number):

        self.turn_number += 1

        if turn_number != self.turn_number:
            raise LogicException(
                'dessinchonization: workers turn number (%d) not equal to command turn number (%d)'
                % (self.turn_number, turn_number))

        if TimePrototype.get_current_turn_number() != self.turn_number:
            raise LogicException(
                'dessinchonization: workers turn number (%d) not equal to saved turn number (%d)'
                % (self.turn_number, TimePrototype.get_current_turn_number()))

        self.storage.process_turn(logger=self.logger)
        self.storage.save_changed_data(logger=self.logger)

        for hero_id in self.storage.skipped_heroes:
            hero = self.storage.heroes[hero_id]
            if hero.actions.current_action.bundle_id in self.storage.ignored_bundles:
                continue
            environment.workers.supervisor.cmd_account_release_required(
                hero.account_id)

        environment.workers.supervisor.cmd_answer('next_turn', self.worker_id)

        if game_settings.COLLECT_GARBAGE and self.turn_number % game_settings.COLLECT_GARBAGE_PERIOD == 0:
            self.logger.info('GC: start')
            gc.collect()
            self.logger.info('GC: end')

    def release_account(self, account_id):
        if account_id not in self.storage.accounts_to_heroes:
            environment.workers.supervisor.cmd_account_released(account_id)
            return

        hero = self.storage.accounts_to_heroes[account_id]
        bundle_id = hero.actions.current_action.bundle_id

        if bundle_id in self.storage.ignored_bundles:
            return

        with self.storage.on_exception(
                self.logger,
                message=
                'LogicWorker.process_release_account catch exception, while processing hero %d, try to save all bundles except %d',
                data=(hero.id, bundle_id),
                excluded_bundle_id=bundle_id):
            self.storage.release_account_data(account_id)
            environment.workers.supervisor.cmd_account_released(account_id)

    def cmd_stop(self):
        return self.send_cmd('stop')

    def process_stop(self):
        # no need to save data, since they automaticaly saved on every turn
        self.initialized = False
        self.storage.save_all(logger=self.logger)
        environment.workers.supervisor.cmd_answer('stop', self.worker_id)
        self.stop_required = True
        self.logger.info('LOGIC STOPPED')

    def cmd_register_account(self, account_id):
        return self.send_cmd('register_account', {'account_id': account_id})

    def process_register_account(self, account_id):
        from the_tale.accounts.prototypes import AccountPrototype
        account = AccountPrototype.get_by_id(account_id)
        if account is None:
            raise LogicException('can not get account with id "%d"' %
                                 (account_id, ))
        self.storage.load_account_data(account)

    def cmd_release_account(self, account_id):
        return self.send_cmd('release_account', {'account_id': account_id})

    def process_release_account(self, account_id):
        self.release_account(account_id)

    def cmd_logic_task(self, account_id, task_id):
        return self.send_cmd('logic_task', {
            'task_id': task_id,
            'account_id': account_id
        })

    def process_logic_task(self, account_id, task_id):  # pylint: disable=W0613
        hero = self.storage.accounts_to_heroes[account_id]
        bundle_id = hero.actions.current_action.bundle_id

        if bundle_id in self.storage.ignored_bundles:
            return

        with self.storage.on_exception(
                self.logger,
                message=
                'LogicWorker.process_logic_task catch exception, while processing hero %d, try to save all bundles except %d',
                data=(hero.id, bundle_id),
                excluded_bundle_id=bundle_id):
            task = postponed_tasks.PostponedTaskPrototype.get_by_id(task_id)
            task.process(self.logger, storage=self.storage)
            task.do_postsave_actions()

            self.storage.recache_bundle(bundle_id)

    def cmd_force_save(self, account_id):
        return self.send_cmd('force_save', {'account_id': account_id})

    def process_force_save(self, account_id):  # pylint: disable=W0613
        hero = self.storage.accounts_to_heroes[account_id]
        bundle_id = hero.actions.current_action.bundle_id
        if bundle_id in self.storage.ignored_bundles:
            return
        self.storage.save_bundle_data(bundle_id=bundle_id)

    def cmd_start_hero_caching(self, account_id):
        self.send_cmd('start_hero_caching', {'account_id': account_id})

    def process_start_hero_caching(self, account_id):
        hero = self.storage.accounts_to_heroes[account_id]

        if hero.actions.current_action.bundle_id in self.storage.ignored_bundles:
            return

        hero.ui_caching_started_at = datetime.datetime.now()
        self.storage.recache_bundle(hero.actions.current_action.bundle_id)

    def cmd_update_hero_with_account_data(self, account_id, is_fast,
                                          premium_end_at, active_end_at,
                                          ban_end_at, might, actual_bills):
        self.send_cmd(
            'update_hero_with_account_data', {
                'account_id': account_id,
                'is_fast': is_fast,
                'premium_end_at': premium_end_at,
                'active_end_at': active_end_at,
                'ban_end_at': ban_end_at,
                'might': might,
                'actual_bills': actual_bills
            })

    def process_update_hero_with_account_data(self, account_id, is_fast,
                                              premium_end_at, active_end_at,
                                              ban_end_at, might, actual_bills):
        hero = self.storage.accounts_to_heroes[account_id]

        if hero.actions.current_action.bundle_id in self.storage.ignored_bundles:
            return

        hero.update_with_account_data(
            is_fast=is_fast,
            premium_end_at=datetime.datetime.fromtimestamp(premium_end_at),
            active_end_at=datetime.datetime.fromtimestamp(active_end_at),
            ban_end_at=datetime.datetime.fromtimestamp(ban_end_at),
            might=might,
            actual_bills=actual_bills)
        self.storage.save_bundle_data(hero.actions.current_action.bundle_id)

    def cmd_highlevel_data_updated(self):
        self.send_cmd('highlevel_data_updated')

    def process_highlevel_data_updated(self):
        self.storage.on_highlevel_data_updated()

    def cmd_setup_quest(self, account_id, knowledge_base):
        return self.send_cmd('setup_quest', {
            'account_id': account_id,
            'knowledge_base': knowledge_base
        })

    def process_setup_quest(self, account_id, knowledge_base):
        hero = self.storage.accounts_to_heroes[account_id]
        bundle_id = hero.actions.current_action.bundle_id

        if bundle_id in self.storage.ignored_bundles:
            return

        with self.storage.on_exception(
                self.logger,
                message=
                'LogicWorker.process_logic_task catch exception, while processing hero %d, try to save all bundles except %d',
                data=(hero.id, bundle_id),
                excluded_bundle_id=bundle_id):
            quests_logic.setup_quest_for_hero(hero, knowledge_base)
            self.storage.recache_bundle(bundle_id)
class LogicStorageTests(testcase.TestCase):

    def setUp(self):
        super(LogicStorageTests, self).setUp()

        self.p1, self.p2, self.p3 = create_test_map()

        self.storage = LogicStorage()

        self.account_1 = self.accounts_factory.create_account()
        self.account_2 = self.accounts_factory.create_account()

        self.storage.load_account_data(self.account_1)
        self.storage.load_account_data(self.account_2)

        self.hero_1 = self.storage.accounts_to_heroes[self.account_1.id]
        self.hero_2 = self.storage.accounts_to_heroes[self.account_2.id]

        self.action_idl_1 = self.hero_1.actions.current_action
        self.action_idl_2 = self.hero_2.actions.current_action

        self.bundle_1_id = self.action_idl_1.bundle_id
        self.bundle_2_id = self.action_idl_2.bundle_id


    def test_load_account_data(self):
        self.assertEqual(len(self.storage.heroes), 2)
        self.assertEqual(len(self.storage.accounts_to_heroes), 2)
        self.assertEqual(self.storage.bundles_to_accounts, {self.hero_1.actions.current_action.bundle_id: set([self.account_1.id]),
                                                            self.hero_2.actions.current_action.bundle_id: set([self.account_2.id])})

        action_regenerate = actions_prototypes.ActionRegenerateEnergyPrototype.create(hero=self.hero_1)

        self.assertEqual(self.action_idl_1.storage, self.storage)
        self.assertEqual(action_regenerate.storage, self.storage)

        storage = LogicStorage()
        storage.load_account_data(AccountPrototype.get_by_id(self.account_1.id))
        storage.load_account_data(AccountPrototype.get_by_id(self.account_2.id))
        self.assertEqual(len(storage.heroes), 2)
        self.assertEqual(len(storage.accounts_to_heroes), 2)
        self.assertEqual(storage.bundles_to_accounts, {self.hero_1.actions.current_action.bundle_id: set([self.account_1.id]),
                                                       self.hero_2.actions.current_action.bundle_id: set([self.account_2.id])})


    def test_load_account_data_with_meta_action(self):
        bundle_id = 666

        meta_action_battle = meta_actions.ArenaPvP1x1.create(self.storage, self.hero_1, self.hero_2)

        proxy_action_1 = actions_prototypes.ActionMetaProxyPrototype.create(hero=self.hero_1, _bundle_id=bundle_id, meta_action=meta_action_battle)
        proxy_action_2 = actions_prototypes.ActionMetaProxyPrototype.create(hero=self.hero_2, _bundle_id=bundle_id, meta_action=meta_action_battle)

        self.assertEqual(len(self.storage.meta_actions), 1)
        self.assertEqual(len(self.storage.meta_actions_to_actions), 1)
        self.assertEqual(self.storage.meta_actions_to_actions[meta_action_battle.uid], set([LogicStorage.get_action_uid(proxy_action_1),
                                                                                            LogicStorage.get_action_uid(proxy_action_2)]))

        self.storage.save_changed_data()

        self.assertIs(self.hero_1.actions.current_action.meta_action, self.hero_2.actions.current_action.meta_action)
        self.assertIs(self.hero_1.actions.current_action.saved_meta_action, self.hero_2.actions.current_action.saved_meta_action)

        storage = LogicStorage()
        storage.load_account_data(AccountPrototype.get_by_id(self.account_1.id))
        storage.load_account_data(AccountPrototype.get_by_id(self.account_2.id))

        self.assertEqual(len(storage.meta_actions), 1)
        self.assertEqual(len(storage.meta_actions_to_actions), 1)
        self.assertEqual(storage.meta_actions_to_actions[meta_action_battle.uid], set([LogicStorage.get_action_uid(proxy_action_1),
                                                                                       LogicStorage.get_action_uid(proxy_action_2)]))

        self.assertEqual(storage.bundles_to_accounts, {self.hero_1.actions.current_action.bundle_id: set([self.account_1.id, self.account_2.id])})

        hero_1 = storage.accounts_to_heroes[self.account_1.id]
        hero_2 = storage.accounts_to_heroes[self.account_2.id]

        self.assertIs(hero_1.actions.current_action.meta_action, hero_2.actions.current_action.meta_action)
        self.assertIsNot(hero_1.actions.current_action.saved_meta_action, hero_2.actions.current_action.saved_meta_action)
        self.assertEqual(hero_1.actions.current_action.saved_meta_action.serialize(), hero_2.actions.current_action.saved_meta_action.serialize())


    def test_add_duplicate_hero(self):
        self.assertRaises(exceptions.HeroAlreadyRegisteredError, self.storage._add_hero, self.hero_1)


    def test_action_release_account_data(self):

        actions_prototypes.ActionRegenerateEnergyPrototype.create(hero=self.hero_1)

        self.storage.skipped_heroes.add(self.hero_1.id)

        self.storage.release_account_data(self.account_1.id)

        self.assertEqual(len(self.storage.heroes), 1)
        self.assertEqual(len(self.storage.accounts_to_heroes), 1)
        self.assertEqual(self.storage.bundles_to_accounts, {self.hero_2.actions.current_action.bundle_id: set([self.account_2.id])})
        self.assertEqual(self.storage.heroes.values()[0].id, self.hero_2.id)
        self.assertFalse(self.storage.skipped_heroes)

    def test_save_hero_data(self):

        self.hero_1.health = 1
        self.hero_2.health = 1

        self.hero_1.actions.updated = True

        self.storage._save_hero_data(self.hero_1.id)

        self.assertEqual(self.hero_1.health, HeroPrototype.get_by_id(self.hero_1.id).health)
        self.assertNotEqual(self.hero_2.health, HeroPrototype.get_by_id(self.hero_2.id).health)

        self.assertFalse(self.hero_1.actions.updated)

    def test_save_all(self):

        self.hero_1.health = 1
        self.hero_2.health = 1

        self.hero_1.actions.updated = True

        self.storage.save_all()

        self.assertEqual(self.hero_1.health, HeroPrototype.get_by_id(self.hero_1.id).health)
        self.assertEqual(self.hero_2.health, HeroPrototype.get_by_id(self.hero_2.id).health)

        self.assertFalse(self.hero_1.actions.updated)

    def test_save_hero_data_with_meta_action(self):
        bundle_id = 666

        meta_action_battle = meta_actions.ArenaPvP1x1.create(self.storage, self.hero_1, self.hero_2)

        actions_prototypes.ActionMetaProxyPrototype.create(hero=self.hero_1, _bundle_id=bundle_id, meta_action=meta_action_battle)
        actions_prototypes.ActionMetaProxyPrototype.create(hero=self.hero_2, _bundle_id=bundle_id, meta_action=meta_action_battle)

        self.storage._save_hero_data(self.hero_1.id)
        self.storage._save_hero_data(self.hero_2.id)

        self.hero_1.reload()
        self.hero_2.reload()

        self.assertEqual(meta_action_battle.serialize(), self.hero_1.actions.current_action.saved_meta_action.serialize())
        self.assertEqual(meta_action_battle.serialize(), self.hero_2.actions.current_action.saved_meta_action.serialize())

    def test_switch_caches(self):
        self.assertEqual(self.storage.previous_cache, {})
        self.assertEqual(self.storage.current_cache, {})

        self.storage.previous_cache[1] = 2
        self.storage.current_cache[3] = 4

        self.storage.switch_caches()

        self.assertEqual(self.storage.previous_cache, {3: 4})
        self.assertEqual(self.storage.current_cache, {})

        self.storage.current_cache[5] = 6

        self.storage.switch_caches()

        self.assertEqual(self.storage.previous_cache, {5: 6})
        self.assertEqual(self.storage.current_cache, {})

    def test_process_cache_queue__with_update(self):
        self.assertEqual(self.storage.cache_queue, set())

        self.storage.cache_queue.add(self.hero_2.id)
        self.storage.cache_queue.add(self.hero_1.id)

        self.storage.process_cache_queue(update_cache=True)

        self.assertItemsEqual(self.storage.current_cache.keys(), (self.hero_1.cached_ui_info_key, self.hero_2.cached_ui_info_key))
        self.assertEqual(self.storage.cache_queue, set())

    def test_process_cache_queue__without_update(self):
        self.assertEqual(self.storage.cache_queue, set())

        self.storage.cache_queue.add(self.hero_2.id)
        self.storage.cache_queue.add(self.hero_1.id)

        self.storage.process_cache_queue(update_cache=False)

        self.assertItemsEqual(self.storage.current_cache.keys(), ())
        self.assertEqual(self.storage.cache_queue, set())

    def test_process_cache_queue__update_cache__with_update(self):
        self.assertEqual(self.storage.cache_queue, set())

        self.storage.current_cache[self.hero_1.cached_ui_info_key] = 1
        self.storage.current_cache[self.hero_2.cached_ui_info_key] = 2

        self.storage.cache_queue.add(self.hero_2.id)

        self.storage.process_cache_queue(update_cache=True)

        self.assertEqual(self.storage.current_cache[self.hero_1.cached_ui_info_key], 1)
        self.assertNotEqual(self.storage.current_cache[self.hero_2.cached_ui_info_key], 2)

    def test_process_cache_queue__update_cache__without_update(self):
        self.assertEqual(self.storage.cache_queue, set())

        self.storage.current_cache[self.hero_1.cached_ui_info_key] = 1
        self.storage.current_cache[self.hero_2.cached_ui_info_key] = 2

        self.storage.cache_queue.add(self.hero_2.id)

        self.storage.process_cache_queue(update_cache=False)

        self.assertEqual(self.storage.current_cache[self.hero_1.cached_ui_info_key], 1)
        self.assertEqual(self.storage.current_cache[self.hero_2.cached_ui_info_key], 2)

    @mock.patch('the_tale.game.heroes.conf.heroes_settings.DUMP_CACHED_HEROES', True)
    def test_process_turn(self):
        self.assertEqual(self.storage.skipped_heroes, set())
        self.storage.process_turn()
        self.assertEqual(self.storage.skipped_heroes, set())

        with mock.patch('the_tale.game.logic_storage.LogicStorage._save_hero_data') as save_hero_data:
            self.storage.save_changed_data()

        self.assertEqual(save_hero_data.call_count, 2)

    @mock.patch('the_tale.game.heroes.conf.heroes_settings.DUMP_CACHED_HEROES', True)
    def test_process_turn__switch_caches(self):
        self.assertEqual(self.storage.previous_cache, {})
        self.assertEqual(self.storage.current_cache, {})

        self.storage.process_turn()
        self.storage.save_changed_data()

        self.assertEqual(self.storage.previous_cache, {})
        self.assertNotEqual(self.storage.current_cache, {})

        old_cache = self.storage.current_cache

        self.storage.process_turn()
        self.storage.save_changed_data()

        self.assertEqual(self.storage.previous_cache, old_cache)
        self.assertNotEqual(self.storage.current_cache, old_cache)

    def test_process_turn_single_hero__runned_outside_storage(self):
        action_1 = actions_prototypes.ActionRegenerateEnergyPrototype.create(hero=self.hero_1)
        action_1.state = action_1.STATE.PROCESSED

        action_2 = actions_prototypes.ActionMoveToPrototype.create(hero=self.hero_1, destination=self.p1)
        action_2.state = action_2.STATE.PROCESSED

        action_3 = actions_prototypes.ActionInPlacePrototype.create(hero=self.hero_1)
        action_3.state = action_3.STATE.PROCESSED

        self.assertEqual(self.hero_1.actions.number, 4)

        self.storage.process_turn__single_hero(hero=self.hero_1,
                                               logger=None,
                                               continue_steps_if_needed=True)

        self.assertEqual(self.hero_1.actions.number, 2)
        self.assertEqual(self.hero_1.actions.current_action.TYPE, actions_prototypes.ActionQuestPrototype.TYPE)

        self.storage.process_turn() # just nothing was broken


    def test_process_turn__process_action_chain(self):
        action_1 = actions_prototypes.ActionRegenerateEnergyPrototype.create(hero=self.hero_1)
        action_1.state = action_1.STATE.PROCESSED

        action_2 = actions_prototypes.ActionMoveToPrototype.create(hero=self.hero_1, destination=self.p1)
        action_2.state = action_2.STATE.PROCESSED

        action_3 = actions_prototypes.ActionInPlacePrototype.create(hero=self.hero_1)
        action_3.state = action_3.STATE.PROCESSED

        self.assertEqual(self.hero_1.actions.number, 4)

        self.storage.process_turn()

        self.assertEqual(self.hero_1.actions.number, 2)
        self.assertEqual(self.hero_1.actions.current_action.TYPE, actions_prototypes.ActionQuestPrototype.TYPE)


    @mock.patch('the_tale.game.heroes.conf.heroes_settings.DUMP_CACHED_HEROES', False)
    def test_process_turn__without_dump(self):
        self.assertEqual(self.storage.skipped_heroes, set())
        self.storage.process_turn()
        self.assertEqual(self.storage.skipped_heroes, set())

        with mock.patch('the_tale.game.logic_storage.LogicStorage._save_hero_data') as save_hero_data:
            self.storage.save_changed_data()

        self.assertEqual(save_hero_data.call_count, 1) # save only game_settings.SAVED_UNCACHED_HEROES_FRACTION bundles number


    def test_process_turn__process_created_action(self):
        from the_tale.game.actions.prototypes import ActionMoveToPrototype

        place = self.p1

        def process_action(self):
            ActionMoveToPrototype.create(hero=self.hero, destination=place)

        with mock.patch('the_tale.game.actions.prototypes.ActionIdlenessPrototype.process', process_action):
            with mock.patch('the_tale.game.actions.prototypes.ActionMoveToPrototype.process') as move_to_process:
                self.storage.process_turn()

        self.assertEqual(move_to_process.call_count, 2)

    @mock.patch('the_tale.game.heroes.conf.heroes_settings.DUMP_CACHED_HEROES', True)
    def test_process_turn_with_skipped_hero(self):
        # skipped heroes saved, but not processed
        self.storage.skipped_heroes.add(self.hero_1.id)

        with mock.patch('the_tale.game.actions.prototypes.ActionBase.process_turn') as action_process_turn:
            self.storage.process_turn()

        self.assertEqual(action_process_turn.call_count, 1)

        with mock.patch('the_tale.game.logic_storage.LogicStorage._save_hero_data') as save_hero_data:
            self.storage.save_changed_data()

        self.assertEqual(save_hero_data.call_count, 2)

    @mock.patch('the_tale.game.heroes.conf.heroes_settings.DUMP_CACHED_HEROES', False)
    def test_process_turn_with_skipped_hero__without_cache_dump(self):
        # skipped heroes saved, but not processed
        self.storage.skipped_heroes.add(self.hero_1.id)

        with mock.patch('the_tale.game.actions.prototypes.ActionBase.process_turn') as action_process_turn:
            self.storage.process_turn()

        self.assertEqual(action_process_turn.call_count, 1)

        with mock.patch('the_tale.game.logic_storage.LogicStorage._save_hero_data') as save_hero_data:
            self.storage.save_changed_data()

        self.assertEqual(save_hero_data.call_count, 1)

    @mock.patch('the_tale.game.heroes.prototypes.HeroPrototype.can_process_turn', lambda self, turn: True)
    def test_process_turn__can_process_turn(self):
        with mock.patch('the_tale.game.actions.prototypes.ActionBase.process_turn') as action_process_turn:
            self.storage.process_turn(continue_steps_if_needed=False)

        self.assertEqual(action_process_turn.call_count, 2)

    @mock.patch('the_tale.game.heroes.prototypes.HeroPrototype.can_process_turn', lambda self, turn: False)
    def test_process_turn__can_not_process_turn(self):
        with mock.patch('the_tale.game.actions.prototypes.ActionBase.process_turn') as action_process_turn:
            self.storage.process_turn(continue_steps_if_needed=False)

        self.assertEqual(action_process_turn.call_count, 0)

    def test_process_turn___exception_raises(self):
        def process_turn_raise_exception(action):
            if action.hero.id == self.hero_2.id:
                raise Exception('error')

        with mock.patch('the_tale.game.actions.prototypes.ActionBase.process_turn', process_turn_raise_exception):
            with mock.patch('the_tale.game.logic_storage.LogicStorage._save_on_exception') as _save_on_exception:
                with mock.patch('django.conf.settings.TESTS_RUNNING', False):
                    self.storage.process_turn()

        self.assertIn(self.hero_2.actions.current_action.bundle_id, self.storage.ignored_bundles)

        self.assertEqual(_save_on_exception.call_count, 1)
        self.assertEqual(_save_on_exception.call_args, mock.call())

    @mock.patch('the_tale.game.conf.game_settings.SAVE_ON_EXCEPTION_TIMEOUT', 0)
    def test_save_on_exception(self):
        # hero 1 not saved due to one bundle with hero 3
        # hero 2 saved
        # hero 3 not saved
        # hero 4 saved

        result, account_3_id, bundle_3_id = register_user('test_user_3', '*****@*****.**', '111111')
        self.storage.load_account_data(AccountPrototype.get_by_id(account_3_id))
        hero_3 = self.storage.accounts_to_heroes[account_3_id]

        result, account_4_id, bundle_4_id = register_user('test_user_4', '*****@*****.**', '111111')
        self.storage.load_account_data(AccountPrototype.get_by_id(account_4_id))
        hero_4 = self.storage.accounts_to_heroes[account_4_id]

        self.hero_1.actions.current_action.bundle_id = hero_3.actions.current_action.bundle_id

        saved_heroes = set()

        def save_hero_data(storage, hero_id, **kwargs):
            saved_heroes.add(hero_id)

        self.storage.ignored_bundles.add(hero_3.actions.current_action.bundle_id)

        with mock.patch('the_tale.game.logic_storage.LogicStorage._save_hero_data', save_hero_data):
            self.storage._save_on_exception()

        self.assertEqual(saved_heroes, set([self.hero_2.id, hero_4.id]))

    def test_save_on_exception__time_border(self):
        # hero 1 not saved due to one bundle with hero 3
        # hero 2 saved
        # hero 3 not saved
        # hero 4 saved

        result, account_3_id, bundle_3_id = register_user('test_user_3', '*****@*****.**', '111111')
        self.storage.load_account_data(AccountPrototype.get_by_id(account_3_id))
        hero_3 = self.storage.accounts_to_heroes[account_3_id]

        result, account_4_id, bundle_4_id = register_user('test_user_4', '*****@*****.**', '111111')
        self.storage.load_account_data(AccountPrototype.get_by_id(account_4_id))
        hero_4 = self.storage.accounts_to_heroes[account_4_id]

        self.hero_1.actions.current_action.bundle_id = hero_3.actions.current_action.bundle_id

        saved_heroes = set()

        self.hero_2._model.saved_at = datetime.datetime.now() - datetime.timedelta(seconds=conf.game_settings.SAVE_ON_EXCEPTION_TIMEOUT+1)

        def save_hero_data(storage, hero_id, **kwargs):
            saved_heroes.add(hero_id)

        self.storage.ignored_bundles.add(hero_3.actions.current_action.bundle_id)

        with mock.patch('the_tale.game.logic_storage.LogicStorage._save_hero_data', save_hero_data):
            self.storage._save_on_exception()

        self.assertEqual(saved_heroes, set([self.hero_2.id]))


    def test_save_changed_data(self):
        self.storage.process_turn()

        with mock.patch('dext.common.utils.cache.set_many') as set_many:
            with mock.patch('the_tale.game.heroes.prototypes.HeroPrototype.ui_info') as ui_info:
                self.storage.save_changed_data()

        self.assertEqual(set_many.call_count, 1)
        self.assertEqual(ui_info.call_count, 2)
        self.assertEqual(ui_info.call_args_list, [mock.call(actual_guaranteed=True, old_info=None), mock.call(actual_guaranteed=True, old_info=None)])


    def test_old_info(self):
        self.storage.process_turn()

        calls = []

        def ui_info(hero, **kwargs):
            calls.append(kwargs)
            return {'hero': hero.id}

        with mock.patch('the_tale.game.heroes.prototypes.HeroPrototype.ui_info', ui_info):
            self.storage.save_changed_data()
            self.storage.process_turn()
            self.storage.save_changed_data()

        self.assertEqual(calls, [{'actual_guaranteed': True, 'old_info': None},
                                 {'actual_guaranteed': True, 'old_info': None},
                                 {'actual_guaranteed': True, 'old_info': {'hero': self.hero_1.id}},
                                 {'actual_guaranteed': True, 'old_info': {'hero': self.hero_2.id}}])


    def test_save_changed_data__old_info(self):
        self.storage.process_turn()
        self.storage.save_changed_data()

        self.storage.process_turn()

        with mock.patch('dext.common.utils.cache.set_many') as set_many:
            with mock.patch('the_tale.game.heroes.prototypes.HeroPrototype.ui_info') as ui_info:
                self.storage.save_changed_data()

        self.assertEqual(set_many.call_count, 1)
        self.assertEqual(ui_info.call_count, 2)
        self.assertNotEqual(ui_info.call_args_list[0][1]['old_info'], None)
        self.assertNotEqual(ui_info.call_args_list[1][1]['old_info'], None)


    @mock.patch('the_tale.game.heroes.conf.heroes_settings.DUMP_CACHED_HEROES', True)
    def test_save_changed_data__with_unsaved_bundles(self):
        self.storage.process_turn()

        self.assertEqual(len(self.storage.heroes), 2)

        with mock.patch('the_tale.game.logic_storage.LogicStorage._get_bundles_to_save', lambda x: [self.bundle_2_id]):
            with mock.patch('the_tale.game.logic_storage.LogicStorage._save_hero_data') as save_hero_data:
                with mock.patch('the_tale.game.heroes.prototypes.HeroPrototype.ui_info') as ui_info:
                    self.storage.save_changed_data()

        self.assertEqual(ui_info.call_count, 2) # cache all heroes, since they are new
        self.assertEqual(ui_info.call_args_list, [mock.call(actual_guaranteed=True, old_info=None), mock.call(actual_guaranteed=True, old_info=None)])
        self.assertEqual(save_hero_data.call_args, mock.call(self.hero_2.id))

    def test_save_changed_data__with_unsaved_bundles__without_dump(self):
        self.storage.process_turn()

        self.assertEqual(len(self.storage.heroes), 2)

        self.hero_2.ui_caching_started_at = datetime.datetime.fromtimestamp(0)

        with mock.patch('the_tale.game.logic_storage.LogicStorage._get_bundles_to_save', lambda x: [self.bundle_2_id]):
            with mock.patch('the_tale.game.logic_storage.LogicStorage._save_hero_data') as save_hero_data:
                with mock.patch('the_tale.game.heroes.prototypes.HeroPrototype.ui_info') as ui_info:
                    self.storage.save_changed_data()

        self.assertEqual(ui_info.call_count, 1) # cache only first hero
        self.assertEqual(ui_info.call_args, mock.call(actual_guaranteed=True, old_info=None))
        self.assertEqual(save_hero_data.call_args, mock.call(self.hero_2.id))

    def test_remove_action__from_middle(self):
        actions_prototypes.ActionRegenerateEnergyPrototype.create(hero=self.hero_1)
        self.assertRaises(exceptions.RemoveActionFromMiddleError, self.storage.remove_action, self.action_idl_1)

    def test_remove_action__metaaction(self):
        bundle_id = 666

        meta_action_battle = meta_actions.ArenaPvP1x1.create(self.storage, self.hero_1, self.hero_2)

        proxy_action_1 = actions_prototypes.ActionMetaProxyPrototype.create(hero=self.hero_1, _bundle_id=bundle_id, meta_action=meta_action_battle)
        proxy_action_2 = actions_prototypes.ActionMetaProxyPrototype.create(hero=self.hero_2, _bundle_id=bundle_id, meta_action=meta_action_battle)

        self.assertEqual(len(self.storage.meta_actions), 1)
        self.assertEqual(len(self.storage.meta_actions_to_actions), 1)
        self.assertEqual(self.storage.meta_actions_to_actions[meta_action_battle.uid], set([LogicStorage.get_action_uid(proxy_action_1),
                                                                                            LogicStorage.get_action_uid(proxy_action_2)]))

        self.storage.remove_action(proxy_action_2)

        self.assertEqual(len(self.storage.meta_actions), 1)
        self.assertEqual(len(self.storage.meta_actions_to_actions), 1)
        self.assertEqual(self.storage.meta_actions_to_actions[meta_action_battle.uid], set([LogicStorage.get_action_uid(proxy_action_1)]))

        self.storage.remove_action(proxy_action_1)

        self.assertEqual(len(self.storage.meta_actions), 0)
        self.assertEqual(len(self.storage.meta_actions_to_actions), 0)

    @mock.patch('the_tale.game.heroes.conf.heroes_settings.DUMP_CACHED_HEROES', True)
    @mock.patch('the_tale.game.conf.game_settings.SAVED_UNCACHED_HEROES_FRACTION', 0)
    def test_get_bundles_to_save(self):
        # hero 1 not saved
        # hero 2 saved by quota
        # hero 3 saved by caching
        # hero 4 not saved

        result, account_3_id, bundle_3_id = register_user('test_user_3', '*****@*****.**', '111111')
        result, account_4_id, bundle_4_id = register_user('test_user_4', '*****@*****.**', '111111')
        result, account_5_id, bundle_5_id = register_user('test_user_5', '*****@*****.**', '111111')

        self.storage.load_account_data(AccountPrototype.get_by_id(account_3_id))
        self.storage.load_account_data(AccountPrototype.get_by_id(account_4_id))
        self.storage.load_account_data(AccountPrototype.get_by_id(account_5_id))

        hero_3 = self.storage.accounts_to_heroes[account_3_id]
        hero_4 = self.storage.accounts_to_heroes[account_4_id]

        self.hero_1._model.saved_at = datetime.datetime.now()
        self.hero_1.ui_caching_started_at = datetime.datetime.fromtimestamp(0)
        self.hero_2.ui_caching_started_at = datetime.datetime.fromtimestamp(0)
        hero_4.ui_caching_started_at = datetime.datetime.fromtimestamp(0)

        self.assertTrue(self.hero_1.saved_at > self.hero_2.saved_at)

        self.assertFalse(self.hero_1.is_ui_caching_required)
        self.assertFalse(self.hero_2.is_ui_caching_required)
        self.assertTrue(hero_3.is_ui_caching_required)
        self.assertFalse(hero_4.is_ui_caching_required)

        self.assertEqual(self.storage._get_bundles_to_save(), set([self.bundle_2_id, bundle_3_id, bundle_5_id]))


    @mock.patch('the_tale.game.heroes.conf.heroes_settings.DUMP_CACHED_HEROES', True)
    @mock.patch('the_tale.game.conf.game_settings.SAVED_UNCACHED_HEROES_FRACTION', 0)
    def test_get_bundles_to_save__force_save_required(self):
        # hero 1 not saved
        # hero 2 saved by quota
        # hero 3 saved by caching
        # hero 4 saved by force

        result, account_3_id, bundle_3_id = register_user('test_user_3', '*****@*****.**', '111111')
        result, account_4_id, bundle_4_id = register_user('test_user_4', '*****@*****.**', '111111')
        result, account_5_id, bundle_5_id = register_user('test_user_5', '*****@*****.**', '111111')

        self.storage.load_account_data(AccountPrototype.get_by_id(account_3_id))
        self.storage.load_account_data(AccountPrototype.get_by_id(account_4_id))
        self.storage.load_account_data(AccountPrototype.get_by_id(account_5_id))

        hero_3 = self.storage.accounts_to_heroes[account_3_id]
        hero_4 = self.storage.accounts_to_heroes[account_4_id]

        hero_4.force_save_required = True

        self.hero_1._model.saved_at = datetime.datetime.now()
        self.hero_1.ui_caching_started_at = datetime.datetime.fromtimestamp(0)
        self.hero_2.ui_caching_started_at = datetime.datetime.fromtimestamp(0)
        hero_4.ui_caching_started_at = datetime.datetime.fromtimestamp(0)

        self.assertTrue(self.hero_1.saved_at > self.hero_2.saved_at)

        self.assertFalse(self.hero_1.is_ui_caching_required)
        self.assertFalse(self.hero_2.is_ui_caching_required)
        self.assertTrue(hero_3.is_ui_caching_required)
        self.assertFalse(hero_4.is_ui_caching_required)

        self.assertEqual(self.storage._get_bundles_to_save(), set([self.bundle_2_id, bundle_3_id, bundle_4_id, bundle_5_id]))

        self.assertFalse(hero_4.force_save_required)


    @mock.patch('the_tale.game.heroes.conf.heroes_settings.DUMP_CACHED_HEROES', False)
    @mock.patch('the_tale.game.conf.game_settings.SAVED_UNCACHED_HEROES_FRACTION', 0)
    def test_get_bundles_to_save__without_cache_dump(self):
        # hero 1 not saved
        # hero 2 saved by quota
        # hero 3 does not saved by caching
        # hero 4 not saved

        result, account_3_id, bundle_3_id = register_user('test_user_3', '*****@*****.**', '111111')
        result, account_4_id, bundle_4_id = register_user('test_user_4', '*****@*****.**', '111111')

        self.storage.load_account_data(AccountPrototype.get_by_id(account_3_id))
        self.storage.load_account_data(AccountPrototype.get_by_id(account_4_id))

        hero_3 = self.storage.accounts_to_heroes[account_3_id]
        hero_4 = self.storage.accounts_to_heroes[account_4_id]

        self.hero_1._model.saved_at = datetime.datetime.now()
        self.hero_1.ui_caching_started_at = datetime.datetime.fromtimestamp(0)
        self.hero_2.ui_caching_started_at = datetime.datetime.fromtimestamp(0)
        hero_4.ui_caching_started_at = datetime.datetime.fromtimestamp(0)

        self.assertTrue(self.hero_1.saved_at > self.hero_2.saved_at)

        self.assertFalse(self.hero_1.is_ui_caching_required)
        self.assertFalse(self.hero_2.is_ui_caching_required)
        self.assertTrue(hero_3.is_ui_caching_required)
        self.assertFalse(hero_4.is_ui_caching_required)

        self.assertEqual(self.storage._get_bundles_to_save(), set([self.bundle_2_id]))

    @mock.patch('the_tale.game.heroes.conf.heroes_settings.DUMP_CACHED_HEROES', True)
    @mock.patch('the_tale.game.conf.game_settings.SAVED_UNCACHED_HEROES_FRACTION', 0)
    def test_save_changed_data__with_multiple_heroes_to_bundle(self):
        # hero 1 saved by bundle from hero 3
        # hero 2 saved by quota
        # hero 3 saved by caching

        result, account_3_id, bundle_3_id = register_user('test_user_3', '*****@*****.**', '111111')

        self.storage.load_account_data(AccountPrototype.get_by_id(account_3_id))

        hero_3 = self.storage.accounts_to_heroes[account_3_id]

        self.hero_1._model.saved_at = datetime.datetime.now()
        self.hero_1.ui_caching_started_at = datetime.datetime.fromtimestamp(0)
        self.hero_1.actions.current_action.bundle_id = hero_3.actions.current_action.bundle_id
        self.hero_2.ui_caching_started_at = datetime.datetime.fromtimestamp(0)

        self.assertTrue(self.hero_1.saved_at > self.hero_2.saved_at)
        self.assertFalse(self.hero_1.is_ui_caching_required)
        self.assertFalse(self.hero_2.is_ui_caching_required)
        self.assertTrue(hero_3.is_ui_caching_required)

        self.assertEqual(self.storage._get_bundles_to_save(), set([self.bundle_2_id, bundle_3_id]))

        self.storage.process_turn()

        with mock.patch('dext.common.utils.cache.set_many') as set_many:
            with mock.patch('the_tale.game.logic_storage.LogicStorage._save_hero_data') as save_hero_data:
                with mock.patch('the_tale.game.heroes.prototypes.HeroPrototype.ui_info') as ui_info:
                    self.storage.save_changed_data()

        self.assertEqual(set_many.call_count, 1)
        self.assertEqual(save_hero_data.call_count, 3)
        self.assertEqual(ui_info.call_count, 2)
        self.assertEqual(ui_info.call_args_list, [mock.call(actual_guaranteed=True, old_info=None), mock.call(actual_guaranteed=True, old_info=None)])


    @mock.patch('the_tale.game.heroes.conf.heroes_settings.DUMP_CACHED_HEROES', False)
    @mock.patch('the_tale.game.conf.game_settings.SAVED_UNCACHED_HEROES_FRACTION', 0)
    def test_save_changed_data__with_multiple_heroes_to_bundle__without_cache_dump(self):
        # hero 1 saved by bundle from hero 2
        # hero 2 saved by quota
        # hero 3 does not saved by caching

        result, account_3_id, bundle_3_id = register_user('test_user_3', '*****@*****.**', '111111')

        self.storage.load_account_data(AccountPrototype.get_by_id(account_3_id))

        hero_3 = self.storage.accounts_to_heroes[account_3_id]

        self.hero_1._model.saved_at = datetime.datetime.now()
        self.hero_1.ui_caching_started_at = datetime.datetime.fromtimestamp(0)
        self.hero_1.actions.current_action.bundle_id = self.hero_2.actions.current_action.bundle_id
        self.hero_2.ui_caching_started_at = datetime.datetime.fromtimestamp(0)

        self.assertTrue(self.hero_1.saved_at > self.hero_2.saved_at)
        self.assertFalse(self.hero_1.is_ui_caching_required)
        self.assertFalse(self.hero_2.is_ui_caching_required)
        self.assertTrue(hero_3.is_ui_caching_required)

        self.assertEqual(self.storage._get_bundles_to_save(), set([self.bundle_2_id]))

        self.storage.process_turn()

        with mock.patch('dext.common.utils.cache.set_many') as set_many:
            with mock.patch('the_tale.game.logic_storage.LogicStorage._save_hero_data') as save_hero_data:
                with mock.patch('the_tale.game.heroes.prototypes.HeroPrototype.ui_info') as ui_info:
                    self.storage.save_changed_data()

        self.assertEqual(set_many.call_count, 1)
        self.assertEqual(save_hero_data.call_count, 2)
        self.assertEqual(ui_info.call_count, 1)
        self.assertEqual(ui_info.call_args, mock.call(actual_guaranteed=True, old_info=None))


    def test_merge_bundles(self):

        storage = LogicStorage()

        storage.bundles_to_accounts[555] = set([1, 2])
        storage.bundles_to_accounts[666] = set([3])

        storage.merge_bundles([555, 666], 777)

        self.assertEqual(storage.bundles_to_accounts, {777: set([1, 2, 3])})


    def test_merge_bundles__in_existed_bundle(self):

        storage = LogicStorage()

        storage.bundles_to_accounts[555] = set([1, 2])
        storage.bundles_to_accounts[666] = set([3])
        storage.bundles_to_accounts[777] = set([4, 5])

        storage.merge_bundles([555, 666], 777)

        self.assertEqual(storage.bundles_to_accounts, {777: set([1, 2, 3, 4, 5])})

    def test_unmerge_bundles__in_existed_bundle(self):
        storage = LogicStorage()

        storage.bundles_to_accounts[555] = set([1, 2])
        storage.bundles_to_accounts[666] = set([3])
        storage.bundles_to_accounts[777] = set([4, 5])

        storage.unmerge_bundles(4, 777, 666)

        self.assertEqual(storage.bundles_to_accounts, {555: set([1, 2]),
                                                       666: set([3, 4]),
                                                       777: set([5])})

    def test_unmerge_bundles__last_account_in_bundle(self):
        storage = LogicStorage()

        storage.bundles_to_accounts[555] = set([1, 2])
        storage.bundles_to_accounts[666] = set([3])
        storage.bundles_to_accounts[777] = set([4, 5])

        storage.unmerge_bundles(3, 666, 555)

        self.assertEqual(storage.bundles_to_accounts, {555: set([1, 2, 3]),
                                                       777: set([4, 5])})

    def test_unmerge_bundles(self):
        storage = LogicStorage()

        storage.bundles_to_accounts[555] = set([1, 2])
        storage.bundles_to_accounts[666] = set([3])
        storage.bundles_to_accounts[777] = set([4, 5])

        storage.unmerge_bundles(4, 777, 888)

        self.assertEqual(storage.bundles_to_accounts, {555: set([1, 2]),
                                                       666: set([3]),
                                                       777: set([5]),
                                                       888: set([4])})


    def test_save_bundle_data(self):

        storage = LogicStorage()

        storage.bundles_to_accounts[555] = set([1, 2])
        storage.bundles_to_accounts[666] = set([3, 7, 9])
        storage.bundles_to_accounts[777] = set([4, 5])

        storage.accounts_to_heroes = {1: mock.Mock(id=1),
                                      2: mock.Mock(id=2),
                                      3: mock.Mock(id=3),
                                      4: mock.Mock(id=4),
                                      5: mock.Mock(id=5),
                                      7: mock.Mock(id=7),
                                      9: mock.Mock(id=9)}

        with mock.patch('the_tale.game.logic_storage.LogicStorage._save_hero_data') as _save_hero_data:
            with mock.patch('the_tale.game.logic_storage.LogicStorage.process_cache_queue') as process_cache_queue:
                storage.save_bundle_data(666)

        self.assertEqual(_save_hero_data.call_count, 3)
        self.assertEqual(storage.cache_queue, set([3, 7, 9]))
        self.assertEqual(process_cache_queue.call_count, 1)

        self.assertEqual(set(call[0][0] for call in _save_hero_data.call_args_list), set([3, 7, 9]))
Example #4
0
class MakeChoiceTaskTest(testcase.TestCase, QuestTestsMixin):
    def setUp(self):
        super(MakeChoiceTaskTest, self).setUp()
        create_test_map()

        account = self.accounts_factory.create_account()

        self.account_id = account.id
        self.storage = LogicStorage()
        self.storage.load_account_data(account)
        self.hero = self.storage.accounts_to_heroes[account.id]

        self.choice_1_uid = '[ns-0]choice_1'
        self.choice_2_uid = '[ns-0]choice_2'
        self.option_1_1_uid = '#option([ns-0]choice_1, [ns-0]choice_2, opt_2)'
        self.option_1_2_uid = '#option([ns-0]choice_1, [ns-0]finish_2, opt_1)'
        self.option_2_1_uid = '#option([ns-0]choice_2, [ns-0]finish_1_1, opt_2_1)'
        self.option_2_2_uid = '#option([ns-0]choice_2, [ns-0]finish_1_2, opt_2_2)'

    def create_task(self, option_uid, account_id=None):

        if account_id is None:
            account_id = self.account_id

        quest = self.turn_to_quest(self.storage, self.hero.id)

        self.assertTrue(self.choice_1_uid in quest.knowledge_base)
        self.assertTrue(self.choice_2_uid in quest.knowledge_base)
        self.assertTrue(self.option_1_1_uid in quest.knowledge_base)
        self.assertTrue(self.option_1_2_uid in quest.knowledge_base)
        self.assertTrue(self.option_2_1_uid in quest.knowledge_base)
        self.assertTrue(self.option_2_2_uid in quest.knowledge_base)

        task = MakeChoiceTask(account_id=account_id, option_uid=option_uid)
        return task

    @mock.patch('questgen.quests.quests_base.QuestsBase._available_quests',
                lambda *argv, **kwargs: [QuestWith2ChoicePoints])
    def test_create(self):
        task = self.create_task(option_uid=self.option_1_1_uid)
        self.assertTrue(task.state.is_UNPROCESSED)

    @mock.patch('questgen.quests.quests_base.QuestsBase._available_quests',
                lambda *argv, **kwargs: [QuestWith2ChoicePoints])
    def test_serialization(self):
        task = self.create_task(option_uid=self.option_1_1_uid)
        self.assertEqual(
            task.serialize(),
            MakeChoiceTask.deserialize(task.serialize()).serialize())

    @mock.patch('questgen.quests.quests_base.QuestsBase._available_quests',
                lambda *argv, **kwargs: [QuestWith2ChoicePoints])
    def test_unknown_choice(self):
        task = self.create_task(option_uid='unknown_choice')
        self.assertEqual(
            task.process(FakePostpondTaskPrototype(), self.storage),
            POSTPONED_TASK_LOGIC_RESULT.ERROR)
        self.assertTrue(task.state.is_WRONG_POINT)

    @mock.patch('questgen.quests.quests_base.QuestsBase._available_quests',
                lambda *argv, **kwargs: [QuestWith2ChoicePoints])
    def test_no_quests(self):
        self.turn_to_quest(self.storage, self.hero.id)

        account = self.accounts_factory.create_account()

        self.storage.load_account_data(account)

        task = self.create_task(option_uid=self.option_1_1_uid,
                                account_id=account.id)

        self.assertEqual(
            task.process(FakePostpondTaskPrototype(), self.storage),
            POSTPONED_TASK_LOGIC_RESULT.ERROR)
        self.assertTrue(task.state.is_QUEST_NOT_FOUND)

    @mock.patch('questgen.quests.quests_base.QuestsBase._available_quests',
                lambda *argv, **kwargs: [QuestWith2ChoicePoints])
    def test_wrong_point(self):
        task = self.create_task(option_uid=self.option_2_1_uid)
        self.assertEqual(
            task.process(FakePostpondTaskPrototype(), self.storage),
            POSTPONED_TASK_LOGIC_RESULT.ERROR)
        self.assertTrue(task.state.is_WRONG_POINT)

    @mock.patch('questgen.quests.quests_base.QuestsBase._available_quests',
                lambda *argv, **kwargs: [QuestWith2ChoicePoints])
    def test_already_chosen(self):
        task = self.create_task(option_uid=self.option_1_1_uid)
        self.assertEqual(
            task.process(FakePostpondTaskPrototype(), self.storage),
            POSTPONED_TASK_LOGIC_RESULT.SUCCESS)
        self.assertTrue(task.state.is_PROCESSED)

        task = self.create_task(option_uid=self.option_1_2_uid)
        self.assertEqual(
            task.process(FakePostpondTaskPrototype(), self.storage),
            POSTPONED_TASK_LOGIC_RESULT.ERROR)
        self.assertTrue(task.state.is_ALREADY_CHOSEN)

    @mock.patch('questgen.quests.quests_base.QuestsBase._available_quests',
                lambda *argv, **kwargs: [QuestWith2ChoicePoints])
    def test_success(self):
        self.hero.quests.updated = False

        self.assertTrue(
            all(not action.replane_required
                for action in self.hero.actions.actions_list))
        task = self.create_task(option_uid=self.option_1_1_uid)
        self.assertEqual(
            task.process(FakePostpondTaskPrototype(), self.storage),
            POSTPONED_TASK_LOGIC_RESULT.SUCCESS)
        self.assertTrue(task.state.is_PROCESSED)
        self.assertTrue(
            all(action.replane_required
                for action in self.hero.actions.actions_list))

        self.assertTrue(self.hero.quests.updated)

    @mock.patch('questgen.quests.quests_base.QuestsBase._available_quests',
                lambda *argv, **kwargs: [QuestWith2ChoicePoints])
    def test_choose_second_choice_before_first_completed(self):
        task = self.create_task(option_uid=self.option_1_2_uid)
        self.assertEqual(
            task.process(FakePostpondTaskPrototype(), self.storage),
            POSTPONED_TASK_LOGIC_RESULT.SUCCESS)
        self.assertTrue(task.state.is_PROCESSED)

        task = self.create_task(option_uid=self.option_2_1_uid)
        self.assertEqual(
            task.process(FakePostpondTaskPrototype(), self.storage),
            POSTPONED_TASK_LOGIC_RESULT.ERROR)
        self.assertTrue(task.state.is_WRONG_POINT)

    @mock.patch('questgen.quests.quests_base.QuestsBase._available_quests',
                lambda *argv, **kwargs: [QuestWith2ChoicePoints])
    def test_choose_second_choice_after_first_completed(self):
        task = self.create_task(option_uid=self.option_1_2_uid)
        self.assertEqual(
            task.process(FakePostpondTaskPrototype(), self.storage),
            POSTPONED_TASK_LOGIC_RESULT.SUCCESS)
        self.assertTrue(task.state.is_PROCESSED)

        current_time = TimePrototype.get_current_time()

        while True:
            self.assertNotEqual(self.hero.actions.current_action.TYPE,
                                ActionIdlenessPrototype.TYPE)

            task = self.create_task(option_uid=self.option_2_1_uid)

            if task.process(FakePostpondTaskPrototype(),
                            self.storage) == POSTPONED_TASK_LOGIC_RESULT.ERROR:
                break

            self.storage.process_turn()
            self.storage.save_changed_data()
            current_time.increment_turn()

    @mock.patch('questgen.quests.quests_base.QuestsBase._available_quests',
                lambda *argv, **kwargs: [QuestWith2ChoicePoints])
    def test_no_choices(self):
        task = self.create_task(option_uid=self.option_1_1_uid)

        knowledge_base = self.hero.quests.current_quest.knowledge_base
        finish_state = next(knowledge_base.filter(facts.Finish))
        self.hero.quests.current_quest.machine.pointer.change_in_knowlege_base(
            knowledge_base, state=finish_state.uid, jump=None)

        self.assertEqual(
            task.process(FakePostpondTaskPrototype(), self.storage),
            POSTPONED_TASK_LOGIC_RESULT.ERROR)
        self.assertTrue(task.state.is_NO_CHOICES_IN_QUEST)
class MakeChoiceTaskTest(testcase.TestCase, QuestTestsMixin):
    def setUp(self):
        super(MakeChoiceTaskTest, self).setUp()
        create_test_map()

        result, account_id, bundle_id = register_user("test_user", "*****@*****.**", "111111")

        self.account_id = account_id
        self.storage = LogicStorage()
        self.storage.load_account_data(AccountPrototype.get_by_id(account_id))
        self.hero = self.storage.accounts_to_heroes[account_id]

        self.choice_1_uid = "[ns-0]choice_1"
        self.choice_2_uid = "[ns-0]choice_2"
        self.option_1_1_uid = "#option([ns-0]choice_1, [ns-0]choice_2, opt_2)"
        self.option_1_2_uid = "#option([ns-0]choice_1, [ns-0]finish_2, opt_1)"
        self.option_2_1_uid = "#option([ns-0]choice_2, [ns-0]finish_1_1, opt_2_1)"
        self.option_2_2_uid = "#option([ns-0]choice_2, [ns-0]finish_1_2, opt_2_2)"

    def create_task(self, option_uid, account_id=None):

        if account_id is None:
            account_id = self.account_id

        quest = self.turn_to_quest(self.storage, self.hero.id)

        self.assertTrue(self.choice_1_uid in quest.knowledge_base)
        self.assertTrue(self.choice_2_uid in quest.knowledge_base)
        self.assertTrue(self.option_1_1_uid in quest.knowledge_base)
        self.assertTrue(self.option_1_2_uid in quest.knowledge_base)
        self.assertTrue(self.option_2_1_uid in quest.knowledge_base)
        self.assertTrue(self.option_2_2_uid in quest.knowledge_base)

        task = MakeChoiceTask(account_id=account_id, option_uid=option_uid)
        return task

    @mock.patch(
        "questgen.quests.quests_base.QuestsBase._available_quests", lambda *argv, **kwargs: [QuestWith2ChoicePoints]
    )
    def test_create(self):
        task = self.create_task(option_uid=self.option_1_1_uid)
        self.assertTrue(task.state.is_UNPROCESSED)

    @mock.patch(
        "questgen.quests.quests_base.QuestsBase._available_quests", lambda *argv, **kwargs: [QuestWith2ChoicePoints]
    )
    def test_serialization(self):
        task = self.create_task(option_uid=self.option_1_1_uid)
        self.assertEqual(task.serialize(), MakeChoiceTask.deserialize(task.serialize()).serialize())

    @mock.patch(
        "questgen.quests.quests_base.QuestsBase._available_quests", lambda *argv, **kwargs: [QuestWith2ChoicePoints]
    )
    def test_unknown_choice(self):
        task = self.create_task(option_uid="unknown_choice")
        self.assertEqual(task.process(FakePostpondTaskPrototype(), self.storage), POSTPONED_TASK_LOGIC_RESULT.ERROR)
        self.assertTrue(task.state.is_WRONG_POINT)

    @mock.patch(
        "questgen.quests.quests_base.QuestsBase._available_quests", lambda *argv, **kwargs: [QuestWith2ChoicePoints]
    )
    def test_no_quests(self):
        self.turn_to_quest(self.storage, self.hero.id)

        result, account_id, bundle_id = register_user("test_user_2", "*****@*****.**", "111111")
        self.storage.load_account_data(AccountPrototype.get_by_id(account_id))

        task = self.create_task(option_uid=self.option_1_1_uid, account_id=account_id)

        self.assertEqual(task.process(FakePostpondTaskPrototype(), self.storage), POSTPONED_TASK_LOGIC_RESULT.ERROR)
        self.assertTrue(task.state.is_QUEST_NOT_FOUND)

    @mock.patch(
        "questgen.quests.quests_base.QuestsBase._available_quests", lambda *argv, **kwargs: [QuestWith2ChoicePoints]
    )
    def test_wrong_point(self):
        task = self.create_task(option_uid=self.option_2_1_uid)
        self.assertEqual(task.process(FakePostpondTaskPrototype(), self.storage), POSTPONED_TASK_LOGIC_RESULT.ERROR)
        self.assertTrue(task.state.is_WRONG_POINT)

    @mock.patch(
        "questgen.quests.quests_base.QuestsBase._available_quests", lambda *argv, **kwargs: [QuestWith2ChoicePoints]
    )
    def test_already_chosen(self):
        task = self.create_task(option_uid=self.option_1_1_uid)
        self.assertEqual(task.process(FakePostpondTaskPrototype(), self.storage), POSTPONED_TASK_LOGIC_RESULT.SUCCESS)
        self.assertTrue(task.state.is_PROCESSED)

        task = self.create_task(option_uid=self.option_1_2_uid)
        self.assertEqual(task.process(FakePostpondTaskPrototype(), self.storage), POSTPONED_TASK_LOGIC_RESULT.ERROR)
        self.assertTrue(task.state.is_ALREADY_CHOSEN)

    @mock.patch(
        "questgen.quests.quests_base.QuestsBase._available_quests", lambda *argv, **kwargs: [QuestWith2ChoicePoints]
    )
    def test_success(self):
        self.hero.quests.updated = False

        self.assertTrue(all(not action.replane_required for action in self.hero.actions.actions_list))
        task = self.create_task(option_uid=self.option_1_1_uid)
        self.assertEqual(task.process(FakePostpondTaskPrototype(), self.storage), POSTPONED_TASK_LOGIC_RESULT.SUCCESS)
        self.assertTrue(task.state.is_PROCESSED)
        self.assertTrue(all(action.replane_required for action in self.hero.actions.actions_list))

        self.assertTrue(self.hero.quests.updated)

    @mock.patch(
        "questgen.quests.quests_base.QuestsBase._available_quests", lambda *argv, **kwargs: [QuestWith2ChoicePoints]
    )
    def test_choose_second_choice_before_first_completed(self):
        task = self.create_task(option_uid=self.option_1_2_uid)
        self.assertEqual(task.process(FakePostpondTaskPrototype(), self.storage), POSTPONED_TASK_LOGIC_RESULT.SUCCESS)
        self.assertTrue(task.state.is_PROCESSED)

        task = self.create_task(option_uid=self.option_2_1_uid)
        self.assertEqual(task.process(FakePostpondTaskPrototype(), self.storage), POSTPONED_TASK_LOGIC_RESULT.ERROR)
        self.assertTrue(task.state.is_WRONG_POINT)

    @mock.patch(
        "questgen.quests.quests_base.QuestsBase._available_quests", lambda *argv, **kwargs: [QuestWith2ChoicePoints]
    )
    def test_choose_second_choice_after_first_completed(self):
        task = self.create_task(option_uid=self.option_1_2_uid)
        self.assertEqual(task.process(FakePostpondTaskPrototype(), self.storage), POSTPONED_TASK_LOGIC_RESULT.SUCCESS)
        self.assertTrue(task.state.is_PROCESSED)

        current_time = TimePrototype.get_current_time()

        while True:
            self.assertNotEqual(self.hero.actions.current_action, ActionIdlenessPrototype.TYPE)

            task = self.create_task(option_uid=self.option_2_1_uid)

            if task.process(FakePostpondTaskPrototype(), self.storage) == POSTPONED_TASK_LOGIC_RESULT.ERROR:
                break

            self.storage.process_turn()
            self.storage.save_changed_data()
            current_time.increment_turn()

    @mock.patch(
        "questgen.quests.quests_base.QuestsBase._available_quests", lambda *argv, **kwargs: [QuestWith2ChoicePoints]
    )
    def test_no_choices(self):
        task = self.create_task(option_uid=self.option_1_1_uid)

        knowledge_base = self.hero.quests.current_quest.knowledge_base
        finish_state = knowledge_base.filter(facts.Finish).next()
        self.hero.quests.current_quest.machine.pointer.change_in_knowlege_base(
            knowledge_base, state=finish_state.uid, jump=None
        )

        self.assertEqual(task.process(FakePostpondTaskPrototype(), self.storage), POSTPONED_TASK_LOGIC_RESULT.ERROR)
        self.assertTrue(task.state.is_NO_CHOICES_IN_QUEST)
Example #6
0
class Worker(workers.BaseWorker):
    STOP_SIGNAL_REQUIRED = False

    def initialize(self):
        # worker initialized by supervisor
        pass

    def cmd_initialize(self, turn_number, worker_id):
        self.send_cmd('initialize', {'turn_number': turn_number, 'worker_id': worker_id})

    def process_initialize(self, turn_number, worker_id):

        if self.initialized:
            self.logger.warn('WARNING: game already initialized, do reinitialization')

        self.storage = LogicStorage()

        self.initialized = True
        self.turn_number = turn_number
        self.queue = []
        self.worker_id = worker_id

        self.logger.info('GAME INITIALIZED')

        environment.workers.supervisor.cmd_answer('initialize', self.worker_id)

    def cmd_next_turn(self, turn_number):
        return self.send_cmd('next_turn', data={'turn_number': turn_number})

    # @profile.profile_decorator('/home/tie/repos/mine/the-tale/profile.info')
    def process_next_turn(self, turn_number):

        self.turn_number += 1

        if turn_number != self.turn_number:
            raise LogicException('dessinchonization: workers turn number (%d) not equal to command turn number (%d)' % (self.turn_number, turn_number))


        if TimePrototype.get_current_turn_number() != self.turn_number:
            raise LogicException('dessinchonization: workers turn number (%d) not equal to saved turn number (%d)' % (self.turn_number,
                                                                                                                      TimePrototype.get_current_turn_number()))

        self.storage.process_turn(logger=self.logger)
        self.storage.save_changed_data(logger=self.logger)

        for hero_id in self.storage.skipped_heroes:
            hero = self.storage.heroes[hero_id]
            if hero.actions.current_action.bundle_id in self.storage.ignored_bundles:
                continue
            environment.workers.supervisor.cmd_account_release_required(hero.account_id)

        environment.workers.supervisor.cmd_answer('next_turn', self.worker_id)

        if game_settings.COLLECT_GARBAGE and self.turn_number % game_settings.COLLECT_GARBAGE_PERIOD == 0:
            self.logger.info('GC: start')
            gc.collect()
            self.logger.info('GC: end')

    def release_account(self, account_id):
        if account_id not in self.storage.accounts_to_heroes:
            environment.workers.supervisor.cmd_account_released(account_id)
            return

        hero = self.storage.accounts_to_heroes[account_id]
        bundle_id = hero.actions.current_action.bundle_id

        if bundle_id in self.storage.ignored_bundles:
            return

        with self.storage.on_exception(self.logger,
                                       message='LogicWorker.process_release_account catch exception, while processing hero %d, try to save all bundles except %d',
                                       data=(hero.id, bundle_id),
                                       excluded_bundle_id=bundle_id):
            self.storage.release_account_data(account_id)
            environment.workers.supervisor.cmd_account_released(account_id)

    def cmd_stop(self):
        return self.send_cmd('stop')

    def process_stop(self):
        # no need to save data, since they automaticaly saved on every turn
        self.initialized = False
        self.storage.save_all(logger=self.logger)
        environment.workers.supervisor.cmd_answer('stop', self.worker_id)
        self.stop_required = True
        self.logger.info('LOGIC STOPPED')

    def cmd_register_account(self, account_id):
        return self.send_cmd('register_account', {'account_id': account_id})

    def process_register_account(self, account_id):
        from the_tale.accounts.prototypes import AccountPrototype
        account = AccountPrototype.get_by_id(account_id)
        if account is None:
            raise LogicException('can not get account with id "%d"' % (account_id,))
        self.storage.load_account_data(account)

    def cmd_release_account(self, account_id):
        return self.send_cmd('release_account', {'account_id': account_id})

    def process_release_account(self, account_id):
        self.release_account(account_id)

    def cmd_logic_task(self, account_id, task_id):
        return self.send_cmd('logic_task', {'task_id': task_id,
                                            'account_id': account_id})

    def process_logic_task(self, account_id, task_id): # pylint: disable=W0613
        hero = self.storage.accounts_to_heroes[account_id]
        bundle_id = hero.actions.current_action.bundle_id

        if bundle_id in self.storage.ignored_bundles:
            return

        with self.storage.on_exception(self.logger,
                                       message='LogicWorker.process_logic_task catch exception, while processing hero %d, try to save all bundles except %d',
                                       data=(hero.id, bundle_id),
                                       excluded_bundle_id=bundle_id):
            task = postponed_tasks.PostponedTaskPrototype.get_by_id(task_id)
            task.process(self.logger, storage=self.storage)
            task.do_postsave_actions()

            self.storage.recache_bundle(bundle_id)


    def cmd_force_save(self, account_id):
        return self.send_cmd('force_save', {'account_id': account_id})

    def process_force_save(self, account_id): # pylint: disable=W0613
        hero = self.storage.accounts_to_heroes[account_id]
        bundle_id = hero.actions.current_action.bundle_id
        if bundle_id in self.storage.ignored_bundles:
            return
        self.storage.save_bundle_data(bundle_id=bundle_id)

    def cmd_start_hero_caching(self, account_id):
        self.send_cmd('start_hero_caching', {'account_id': account_id})

    def process_start_hero_caching(self, account_id):
        hero = self.storage.accounts_to_heroes[account_id]

        if hero.actions.current_action.bundle_id in self.storage.ignored_bundles:
            return

        hero.ui_caching_started_at = datetime.datetime.now()
        self.storage.recache_bundle(hero.actions.current_action.bundle_id)

    def cmd_update_hero_with_account_data(self, account_id, is_fast, premium_end_at, active_end_at, ban_end_at, might, actual_bills):
        self.send_cmd('update_hero_with_account_data', {'account_id': account_id,
                                                        'is_fast': is_fast,
                                                        'premium_end_at': premium_end_at,
                                                        'active_end_at': active_end_at,
                                                        'ban_end_at': ban_end_at,
                                                        'might': might,
                                                        'actual_bills': actual_bills})

    def process_update_hero_with_account_data(self, account_id, is_fast, premium_end_at, active_end_at, ban_end_at, might, actual_bills):
        hero = self.storage.accounts_to_heroes[account_id]

        if hero.actions.current_action.bundle_id in self.storage.ignored_bundles:
            return

        hero.update_with_account_data(is_fast=is_fast,
                                      premium_end_at=datetime.datetime.fromtimestamp(premium_end_at),
                                      active_end_at=datetime.datetime.fromtimestamp(active_end_at),
                                      ban_end_at=datetime.datetime.fromtimestamp(ban_end_at),
                                      might=might,
                                      actual_bills=actual_bills)
        self.storage.save_bundle_data(hero.actions.current_action.bundle_id)

    def cmd_highlevel_data_updated(self):
        self.send_cmd('highlevel_data_updated')

    def process_highlevel_data_updated(self):
        self.storage.on_highlevel_data_updated()

    def cmd_setup_quest(self, account_id, knowledge_base):
        return self.send_cmd('setup_quest', {'account_id': account_id,
                                             'knowledge_base': knowledge_base})

    def process_setup_quest(self, account_id, knowledge_base):
        hero = self.storage.accounts_to_heroes[account_id]
        bundle_id = hero.actions.current_action.bundle_id

        if bundle_id in self.storage.ignored_bundles:
            return

        with self.storage.on_exception(self.logger,
                                       message='LogicWorker.process_logic_task catch exception, while processing hero %d, try to save all bundles except %d',
                                       data=(hero.id, bundle_id),
                                       excluded_bundle_id=bundle_id):
            quests_logic.setup_quest_for_hero(hero, knowledge_base)
            self.storage.recache_bundle(bundle_id)