import shutil
import tempfile

from twisted.trial import unittest

import bravo.config
import bravo.factories.beta

class MockProtocol(object):

    username = None

    def __init__(self, player):
        self.player = player
        self.location = player.location if player else None

class TestBravoFactory(unittest.TestCase):

    def setUp(self):
        # Same setup as World, because Factory is very automagical.
        self.name = "unittest"

        bravo.config.configuration.add_section("world unittest")
        bravo.config.configuration.set("world unittest", "port", "0")

        self.f = bravo.factories.beta.BravoFactory(self.name)

    def tearDown(self):
        bravo.config.configuration.remove_section("world unittest")

    def test_trivial(self):
        pass

    def test_initial_attributes(self):
        """
        Make sure that the basic attributes of the factory are correct.

        You'd be surprised how often this test breaks.
        """

        self.assertEqual(self.f.name, "unittest")
        self.assertEqual(self.f.config_name, "world unittest")

        self.assertEqual(self.f.eid, 1)

    def test_update_season_empty(self):
        """
        If no seasons are enabled, things should proceed as normal.
        """

        bravo.config.configuration.set("world unittest", "seasons", "")
        self.f.register_plugins()

        self.f.day = 0
        self.f.update_season()
        self.assertTrue(self.f.world.season is None)

        self.f.day = 90
        self.f.update_season()
        self.assertTrue(self.f.world.season is None)

    def test_update_season_winter(self):
        """
        If winter is the only season available, then only winter should be
        selected, regardless of day.
        """

        bravo.config.configuration.set("world unittest", "seasons", "winter")
        self.f.register_plugins()

        self.f.day = 0
        self.f.update_season()
        self.assertEqual(self.f.world.season.name, "winter")

        self.f.day = 90
        self.f.update_season()
        self.assertEqual(self.f.world.season.name, "winter")

    def test_update_season_switch(self):
        """
        The season should change from spring to winter when both are enabled.
        """

        bravo.config.configuration.set("world unittest", "seasons",
            "winter, spring")
        self.f.register_plugins()

        self.f.day = 0
        self.f.update_season()
        self.assertEqual(self.f.world.season.name, "winter")

        self.f.day = 90
        self.f.update_season()
        self.assertEqual(self.f.world.season.name, "spring")

    def test_set_username(self):
        p = MockProtocol(None)
        p.username = "Hurp"
        self.f.protocols["Hurp"] = p

        self.assertTrue(self.f.set_username(p, "Derp"))

        self.assertTrue("Derp" in self.f.protocols)
        self.assertTrue("Hurp" not in self.f.protocols)
        self.assertEqual(p.username, "Derp")

    def test_set_username_taken(self):
        p = MockProtocol(None)
        p.username = "Hurp"
        self.f.protocols["Hurp"] = p
        self.f.protocols["Derp"] = None

        self.assertFalse(self.f.set_username(p, "Derp"))

        self.assertEqual(p.username, "Hurp")

    def test_set_username_noop(self):
        p = MockProtocol(None)
        p.username = "Hurp"
        self.f.protocols["Hurp"] = p

        self.assertFalse(self.f.set_username(p, "Hurp"))

class TestBravoFactoryStarted(unittest.TestCase):
    """
    Tests which require ``startFactory()`` to be called.
    """

    def setUp(self):
        # Same setup as World, because Factory is very automagical.
        self.d = tempfile.mkdtemp()
        self.name = "unittest"

        bravo.config.configuration.add_section("world unittest")
        d = {
            "authenticator" : "offline",
            "automatons"    : "",
            "generators"    : "",
            "port"          : "0",
            "seasons"       : "winter, spring",
            "serializer"    : "alpha",
            "url"           : "file://%s" % self.d,
        }
        for k, v in d.items():
            bravo.config.configuration.set("world unittest", k, v)

        self.f = bravo.factories.beta.BravoFactory(self.name)
        # And now start the factory.
        self.f.startFactory()

    def tearDown(self):
        bravo.config.configuration.remove_section("world unittest")

        self.f.stopFactory()

        shutil.rmtree(self.d)

    def test_trivial(self):
        pass

    def test_create_entity_pickup(self):
        entity = self.f.create_entity(0, 0, 0, "Item")
        self.assertEqual(entity.eid, 2)
        self.assertEqual(self.f.eid, 2)

    def test_create_entity_player(self):
        entity = self.f.create_entity(0, 0, 0, "Player", username="unittest")
        self.assertEqual(entity.eid, 2)
        self.assertEqual(entity.username, "unittest")
        self.assertEqual(self.f.eid, 2)

    def test_give(self):
        self.f.give((0, 0, 0), (2, 0), 1)

    def test_give_oversized(self):
        """
        Check that oversized inputs to ``give()`` merely cause lots of pickups
        to be spawned.
        """

        # Our check consists of counting the number of times broadcast is
        # called.
        count = [0]
        def broadcast(packet):
            count[0] += 1
        self.patch(self.f, "broadcast", broadcast)

        # 65 blocks should be split into two stacks.
        self.f.give((0, 0, 0), (2, 0), 65)
        self.assertEqual(count[0], 2)

    def test_players_near(self):
        # Register some protocols with a player on the factory first.
        players = [
            self.f.create_entity(0, 0, 0, "Player", username=""),   # eid 2
            self.f.create_entity(0, 2, 0, "Player", username=""),   # eid 3
            self.f.create_entity(1, 0, 3, "Player", username=""),   # eid 4
            self.f.create_entity(0, 4, 1, "Player", username="")    # eid 5
        ]

        for i, player in enumerate(players):
            self.f.protocols[i] = MockProtocol(player)

        # List of tests (player in the center, radius, expected eids).
        expected_results = [
            (players[0], 1, []),
            (players[0], 2, [3]),
            (players[0], 4, [3, 4]),
            (players[0], 5, [3, 4, 5]),
            (players[1], 3, [2, 5])
        ]

        for player, radius, result in expected_results:
            found = [p.eid for p in self.f.players_near(player, radius)]
            self.assertEqual(set(found), set(result))
