import sys
import unittest
from sdl2.ext.ebs import Entity, System, Applicator, World


class Position(object):
    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y

    def __eq__(self, other):
        return self.x == other.x and self.y == other.y


class Movement(object):
    def __init__(self, vx=0, vy=0):
        self.vx = vx
        self.vy = vy


class PositionEntity(Entity):
    def __init__(self, world, x=0, y=0):
        self.position = Position(x, y)


class MovingEntity(Entity):
    def __init__(self, world, x=0, y=0, vx=0, vy=0):
        self.position = Position(x, y)
        self.movement = Movement(vx, vy)


class PosEntity(Entity):
    def __init__(self, world, x=0, y=0):
        self.pos = Position(x, y)


class PositionSystem(System):
    def __init__(self):
        super(PositionSystem, self).__init__()
        self.componenttypes = (Position,)

    def process(self, world, components):
        for c in components:
            c.x += 1
            c.y += 1


class MovementApplicator(Applicator):
    def __init__(self):
        super(MovementApplicator, self).__init__()
        self.componenttypes = (Position, Movement)

    def process(self, world, componentsets):
        for p, m in componentsets:
            p.x += m.vx
            p.y += m.vy


class SDL2ExtEBSTest(unittest.TestCase):
    __tags__ = ["ebs", "sdl2ext"]

    def test_Entity(self):
        world = World()
        world.add_system(PositionSystem())

        e = Entity(world)
        e2 = Entity(world)
        self.assertIsInstance(e, Entity)
        self.assertIsInstance(e2, Entity)
        self.assertNotEqual(e, e2)

        p = PositionEntity(world)
        self.assertIsInstance(p, PositionEntity)
        self.assertIsInstance(p, Entity)

    def test_Entity_id(self):
        world = World()
        ent1 = Entity(world)
        ent2 = Entity(world)
        self.assertNotEqual(ent1.id, ent2.id)

    def test_Entity_world(self):
        world = World()
        world2 = World()
        ent1 = Entity(world)
        ent2 = Entity(world2)
        self.assertEqual(ent1.world, world)
        self.assertNotEqual(ent1.world, world2)
        self.assertEqual(ent2.world, world2)
        self.assertNotEqual(ent2.world, world)
        self.assertNotEqual(ent1.world, ent2.world)

    def test_Entity_delete(self):
        w = World()
        e1 = Entity(w)
        e2 = Entity(w)

        self.assertEqual(len(w.entities), 2)
        e1.delete()
        self.assertEqual(len(w.entities), 1)
        e2.delete()
        self.assertEqual(len(w.entities), 0)

        # The next two should have no effect
        e1.delete()
        e2.delete()

    def test_Entity__inheritance(self):
        world = World()

        pos1 = PositionEntity(world)
        pos2 = PositionEntity(world, 10, 10)
        for p in (pos1, pos2):
            self.assertIsInstance(p, PositionEntity)
            self.assertIsInstance(p, Entity)
            self.assertIsInstance(p.position, Position)

    def test_Entity__access(self):
        world = World()
        pos1 = PositionEntity(world)
        pos2 = PosEntity(world)

        pos1.position.x = 10

        # components are _always_ identified by a lower-case class name.
        def sx(p, v):
            p.pos.x = v
        self.assertRaises(AttributeError, sx, pos2, 10)

    def test_World(self):
        w = World()
        self.assertIsInstance(w, World)

    def test_World_add_remove_system(self):
        world = World()
        self.assertIsInstance(world, World)

        class SimpleSystem(object):
            def __init__(self):
                self.componenttypes = (Position,)

            def process(self, world, components):
                pass

        for method in (world.add_system, world.remove_system):
            for val in (None, "Test", Position, Entity(world)):
                self.assertRaises(ValueError, method, val)

        psystem = SimpleSystem()
        world.add_system(psystem)
        self.assertTrue(len(world.systems) != 0)
        self.assertTrue(psystem in world.systems)
        world.remove_system(psystem)
        self.assertTrue(len(world.systems) == 0)
        self.assertTrue(psystem not in world.systems)

        psystem = PositionSystem()
        world.add_system(psystem)
        self.assertTrue(len(world.systems) != 0)
        self.assertTrue(psystem in world.systems)

        entity = PositionEntity(world)
        self.assertIsInstance(entity.position, Position)

        world.remove_system(psystem)
        self.assertTrue(len(world.systems) == 0)
        self.assertTrue(psystem not in world.systems)

        # The data must stay intact in the world, even if the processing
        # system has been removed.
        self.assertIsInstance(entity.position, Position)

    def test_World_entities(self):
        w = World()
        self.assertEqual(len(w.entities), 0)

        for x in range(100):
            Entity(w)
        self.assertEqual(len(w.entities), 100)

    def test_World_delete(self):
        w = World()
        e1 = Entity(w)
        e2 = Entity(w)

        self.assertEqual(len(w.entities), 2)
        w.delete(e1)
        self.assertEqual(len(w.entities), 1)
        w.delete(e2)
        self.assertEqual(len(w.entities), 0)

        # The next two should have no effect
        w.delete(e1)
        w.delete(e2)

    def test_World_delete_entities(self):
        w = World()
        e1 = Entity(w)
        e2 = Entity(w)

        self.assertEqual(len(w.entities), 2)
        w.delete_entities((e1, e2))
        self.assertEqual(len(w.entities), 0)
        # The next should have no effect
        w.delete_entities((e1, e2))

    def test_World_get_entities(self):
        w = World()
        e1 = PositionEntity(w, 1, 1)
        e2 = PositionEntity(w, 1, 2)
        self.assertEqual(len(w.get_entities(e1.position)), 1)
        e2.position.y = 1
        self.assertEqual(len(w.get_entities(e1.position)), 2)

    def test_System(self):
        world = World()
        self.assertRaises(ValueError, world.add_system, None)
        self.assertRaises(ValueError, world.add_system, 1234)
        self.assertRaises(ValueError, world.add_system, "Test")

        class ErrornousSystem(System):
            def __init__(self):
                super(ErrornousSystem, self).__init__()

        esystem = ErrornousSystem()
        # No component types defined.
        self.assertRaises(ValueError, world.add_system, esystem)
        self.assertEqual(len(world.systems), 0)

        psystem = PositionSystem()
        world.add_system(psystem)
        self.assertTrue(psystem in world.systems)

    def test_System_process(self):
        world = World()

        class ErrornousSystem(System):
            def __init__(self):
                super(ErrornousSystem, self).__init__()
                self.componenttypes = (Position,)

        esystem = ErrornousSystem()
        world.add_system(esystem)
        for x in range(10):
            PositionEntity(world)
        self.assertTrue(esystem in world.systems)
        self.assertRaises(NotImplementedError, world.process)

        world2 = World()
        psystem = PositionSystem()
        world2.add_system(psystem)
        for x in range(10):
            PositionEntity(world2)
        self.assertTrue(psystem in world2.systems)
        world2.process()
        for c in world2.components[Position].values():
            self.assertEqual(c.x, 1)
            self.assertEqual(c.y, 1)
        world2.process()
        for c in world2.components[Position].values():
            self.assertEqual(c.x, 2)
            self.assertEqual(c.y, 2)

    def test_Applicator(self):
        world = World()

        class ErrornousApplicator(Applicator):
            def __init__(self):
                super(ErrornousApplicator, self).__init__()

        eapplicator = ErrornousApplicator()
        # No component types defined.
        self.assertRaises(ValueError, world.add_system, eapplicator)
        self.assertEqual(len(world.systems), 0)

        mapplicator = MovementApplicator()
        world.add_system(mapplicator)
        self.assertTrue(mapplicator in world.systems)

    def test_Applicator_process(self):
        world = World()

        class ErrornousApplicator(Applicator):
            def __init__(self):
                super(ErrornousApplicator, self).__init__()
                self.componenttypes = (Position, Movement)

        eapplicator = ErrornousApplicator()
        world.add_system(eapplicator)
        for x in range(10):
            MovingEntity(world)
        self.assertTrue(eapplicator in world.systems)
        self.assertRaises(NotImplementedError, world.process)

        world2 = World()
        mapplicator = MovementApplicator()
        world2.add_system(mapplicator)
        for x in range(10):
            MovingEntity(world2, vx=1, vy=1)
        self.assertTrue(mapplicator in world2.systems)
        world2.process()
        for c in world2.components[Position].values():
            self.assertEqual(c.x, 1)
            self.assertEqual(c.y, 1)
        world2.process()
        for c in world2.components[Position].values():
            self.assertEqual(c.x, 2)
            self.assertEqual(c.y, 2)


if __name__ == '__main__':
    sys.exit(unittest.main())
