from ursina import Entity, Vec2, invoke, scene, color
from ursina.prefabs.sky import Sky
from panda3d.core import DirectionalLight as PandaDirectionalLight
from panda3d.core import PointLight as PandaPointLight
from panda3d.core import AmbientLight as PandaAmbientLight
from panda3d.core import Spotlight as PandaSpotLight


class Light(Entity):
    def __init__(self, **kwargs):
        super().__init__(rotation_x=90, **kwargs)


    @property
    def color(self):
        return getattr(self, '_color', color.white)

    @color.setter
    def color(self, value):
        self._color = value
        self._light.setColor(value)


class DirectionalLight(Light):
    def __init__(self, shadows=True, **kwargs):
        super().__init__()
        self._light = PandaDirectionalLight('directional_light')
        node_path = self.attachNewNode(self._light)
        node_path.node().setCameraMask(0b0001)
        render.setLight(node_path)
        self.shadow_map_resolution = Vec2(1024, 1024)

        for key, value in kwargs.items():
            setattr(self, key ,value)

        invoke(setattr, self, 'shadows', shadows, delay=1/60)


    @property
    def shadows(self):
        return self._shadows

    @shadows.setter
    def shadows(self, value):
        self._shadows = value
        if value:
            self._light.set_shadow_caster(True, int(self.shadow_map_resolution[0]), int(self.shadow_map_resolution[1]))
            self.update_bounds()
        else:
            self._light.set_shadow_caster(False)


    def update_bounds(self, entity=scene):  # update the shadow area to fit the bounds of target entity, defaulted to scene.
        # don't include skydome when calculating shadow bounds
        [e.disable() for e in Sky.instances]
        bounds = entity.get_tight_bounds(self)
        if bounds is not None:
            bmin, bmax = bounds
            lens = self._light.get_lens()
            lens.set_near_far(bmin.z*2, bmax.z*2)
            lens.set_film_offset((bmin.xy + bmax.xy) * .5)
            lens.set_film_size(bmax.xy - bmin.xy)

        [e.enable() for e in Sky.instances]


class PointLight(Light):
    def __init__(self, **kwargs):
        super().__init__()
        self._light = PandaPointLight('point_light')
        render.setLight(self.attachNewNode(self._light))

        for key, value in kwargs.items():
            setattr(self, key ,value)



class AmbientLight(Light):
    def __init__(self, **kwargs):
        super().__init__()
        self._light = PandaAmbientLight('ambient_light')
        render.setLight(self.attachNewNode(self._light))

        for key, value in kwargs.items():
            setattr(self, key ,value)



class SpotLight(Light):
    def __init__(self, **kwargs):
        super().__init__()
        self._light = PandaSpotLight('spot_light')
        render.setLight(self.attachNewNode(self._light))

        for key, value in kwargs.items():
            setattr(self, key ,value)




if __name__ == '__main__':
    from ursina import Ursina, EditorCamera, color, Vec3

    app = Ursina()
    from ursina.shaders import lit_with_shadows_shader # you have to apply this shader to enties for them to receive shadows.
    EditorCamera()
    Entity(model='plane', scale=10, color=color.gray, shader=lit_with_shadows_shader)
    Entity(model='cube', y=1, shader=lit_with_shadows_shader, color=color.light_gray)
    light = DirectionalLight(shadows=True)
    light.look_at(Vec3(1,-1,1))

    dont_cast_shadow = Entity(model='cube', y=1, shader=lit_with_shadows_shader, x=2, color=color.light_gray)
    dont_cast_shadow.hide(0b0001)

    unlit_entity = Entity(model='cube', y=1,x=-2, unlit=True, color=color.light_gray)

    bar = Entity(model='cube', position=(0,3,-2), shader=lit_with_shadows_shader, scale=(10,.2,.2), color=color.light_gray)
    # dont_cast_shadow.hide(0b0001)

    app.run()
