import bpy, mathutils
from bpy.types import Context
import typing, math
from . import UTIL_functions, UTIL_virtools_types

# Raw Data

class RawVirtoolsLight():
    # Class member

    mType: UTIL_virtools_types.VXLIGHT_TYPE
    mColor: UTIL_virtools_types.VxColor

    mConstantAttenuation: float
    mLinearAttenuation: float
    mQuadraticAttenuation: float

    mRange: float

    mHotSpot: float
    mFalloff: float
    mFalloffShape: float

    # Class member default value

    cDefaultType: typing.ClassVar[UTIL_virtools_types.VXLIGHT_TYPE] = UTIL_virtools_types.VXLIGHT_TYPE.VX_LIGHTPOINT
    cDefaultColor: typing.ClassVar[UTIL_virtools_types.VxColor] = UTIL_virtools_types.VxColor(1.0, 1.0, 1.0, 1.0)

    cDefaultConstantAttenuation: typing.ClassVar[float] = 1.0
    cDefaultLinearAttenuation: typing.ClassVar[float] = 0.0
    cDefaultQuadraticAttenuation: typing.ClassVar[float] = 0.0

    cDefaultRange: typing.ClassVar[float] = 100.0

    cDefaultHotSpot: typing.ClassVar[float] = math.radians(40)
    cDefaultFalloff: typing.ClassVar[float] = math.radians(45)
    cDefaultFalloffShape: typing.ClassVar[float] = 1.0

    def __init__(self, **kwargs):
        # assign default value for each component
        self.mType = kwargs.get('mType', RawVirtoolsLight.cDefaultType)
        self.mColor = kwargs.get('mColor', RawVirtoolsLight.cDefaultColor).clone()

        self.mConstantAttenuation = kwargs.get('mConstantAttenuation', RawVirtoolsLight.cDefaultConstantAttenuation)
        self.mLinearAttenuation = kwargs.get('mLinearAttenuation', RawVirtoolsLight.cDefaultLinearAttenuation)
        self.mQuadraticAttenuation = kwargs.get('mQuadraticAttenuation', RawVirtoolsLight.cDefaultQuadraticAttenuation)
        
        self.mRange = kwargs.get('mRange', RawVirtoolsLight.cDefaultRange)

        self.mHotSpot = kwargs.get('mHotSpot', RawVirtoolsLight.cDefaultHotSpot)
        self.mFalloff = kwargs.get('mFalloff', RawVirtoolsLight.cDefaultFalloff)
        self.mFalloffShape = kwargs.get('mFalloffShape', RawVirtoolsLight.cDefaultFalloffShape)

    def regulate(self) -> None:
        # regulate color and reset its alpha value
        self.mColor.regulate()
        self.mColor.a = 1.0
        # regulate range
        self.mRange = UTIL_functions.clamp_float(self.mRange, 0.0, 200.0)

        # regulate attenuation
        self.mConstantAttenuation = UTIL_functions.clamp_float(self.mConstantAttenuation, 0.0, 10.0)
        self.mLinearAttenuation = UTIL_functions.clamp_float(self.mLinearAttenuation, 0.0, 10.0)
        self.mQuadraticAttenuation = UTIL_functions.clamp_float(self.mQuadraticAttenuation, 0.0, 10.0)

        # regulate spot cone
        self.mHotSpot = UTIL_functions.clamp_float(self.mHotSpot, 0.0, math.radians(180))
        self.mFalloff = UTIL_functions.clamp_float(self.mFalloff, 0.0, math.radians(180))
        self.mFalloffShape = UTIL_functions.clamp_float(self.mFalloffShape, 0.0, 10.0)
        # regulate spot cone size order
        if self.mFalloff < self.mHotSpot:
            self.mFalloff = self.mHotSpot

# Blender Property Group

_g_Helper_VXLIGHT_TYPE: UTIL_virtools_types.EnumPropHelper = UTIL_virtools_types.EnumPropHelper(UTIL_virtools_types.VXLIGHT_TYPE)

class BBP_PG_virtools_light(bpy.types.PropertyGroup):
    light_type: bpy.props.EnumProperty(
        name = "Type",
        description = "The type of this light",
        items = _g_Helper_VXLIGHT_TYPE.generate_items(),
        default = _g_Helper_VXLIGHT_TYPE.to_selection(RawVirtoolsLight.cDefaultType)
    ) # type: ignore

    light_color: bpy.props.FloatVectorProperty(
        name = "Color",
        description = "Defines the red, green and blue components of the light.",
        subtype = 'COLOR',
        min = 0.0,
        max = 1.0,
        size = 3,
        default = RawVirtoolsLight.cDefaultColor.to_const_rgb()
    ) # type: ignore

    constant_attenuation: bpy.props.FloatProperty(
        name = "Constant Attenuation",
        description = "Defines the constant attenuation factor.",
        min = 0.0,
        max = 10.0,
        step = 10,
        default = RawVirtoolsLight.cDefaultConstantAttenuation
    ) # type: ignore

    linear_attenuation: bpy.props.FloatProperty(
        name = "Linear Attenuation",
        description = "Defines the linear attenuation factor.",
        min = 0.0,
        max = 10.0,
        step = 10,
        default = RawVirtoolsLight.cDefaultLinearAttenuation
    ) # type: ignore

    quadratic_attenuation: bpy.props.FloatProperty(
        name = "Quadratic Attenuation",
        description = "Defines the quadratic attenuation factor.",
        min = 0.0,
        max = 10.0,
        step = 10,
        default = RawVirtoolsLight.cDefaultQuadraticAttenuation
    ) # type: ignore

    light_range: bpy.props.FloatProperty(
        name = "Range",
        description = "Defines the radius of the lighting area.",
        min = 0.0,
        max = 200.0,
        step = 100,
        default = RawVirtoolsLight.cDefaultRange
    ) # type: ignore

    hot_spot: bpy.props.FloatProperty(
        name = "Hot Spot",
        description = "Sets the value of the hot spot of the light.",
        min = 0.0,
        max = math.radians(180),
        subtype = 'ANGLE',
        default = RawVirtoolsLight.cDefaultHotSpot
    ) # type: ignore

    falloff: bpy.props.FloatProperty(
        name = "Fall Off",
        description = "Sets the light fall off rate.",
        min = 0.0,
        max = math.radians(180),
        subtype = 'ANGLE',
        default = RawVirtoolsLight.cDefaultFalloff
    ) # type: ignore

    falloff_shape: bpy.props.FloatProperty(
        name = "Fall Off Shape",
        description = "Sets the value of the light fall off shape.",
        min = 0.0,
        max = 10.0,
        step = 10,
        default = RawVirtoolsLight.cDefaultFalloffShape
    ) # type: ignore

# Getter Setter and Applyer

def get_virtools_light(lit: bpy.types.Light) -> BBP_PG_virtools_light:
    return lit.virtools_light

def get_raw_virtools_light(lit: bpy.types.Light) -> RawVirtoolsLight:
    props: BBP_PG_virtools_light = get_virtools_light(lit)
    rawdata: RawVirtoolsLight = RawVirtoolsLight()

    rawdata.mType = _g_Helper_VXLIGHT_TYPE.get_selection(props.light_type)
    rawdata.mColor.from_const_rgb(props.light_color)

    rawdata.mConstantAttenuation = props.constant_attenuation
    rawdata.mLinearAttenuation = props.linear_attenuation
    rawdata.mQuadraticAttenuation = props.quadratic_attenuation

    rawdata.mRange = props.light_range

    rawdata.mHotSpot = props.hot_spot
    rawdata.mFalloff = props.falloff
    rawdata.mFalloffShape = props.falloff_shape

    rawdata.regulate()
    return rawdata

def set_raw_virtools_light(lit: bpy.types.Light, rawdata: RawVirtoolsLight) -> None:
    props: BBP_PG_virtools_light = get_virtools_light(lit)

    props.light_type = _g_Helper_VXLIGHT_TYPE.to_selection(rawdata.mType)
    props.light_color = rawdata.mColor.to_const_rgb()

    props.constant_attenuation = rawdata.mConstantAttenuation
    props.linear_attenuation = rawdata.mLinearAttenuation
    props.quadratic_attenuation = rawdata.mQuadraticAttenuation

    props.light_range = rawdata.mRange

    props.hot_spot = rawdata.mHotSpot
    props.falloff = rawdata.mFalloff
    props.falloff_shape = rawdata.mFalloffShape

def apply_to_blender_light(lit: bpy.types.Light) -> None:
    # get raw data first
    rawdata: RawVirtoolsLight = get_raw_virtools_light(lit)

    # set light type and color
    match(rawdata.mType):
        case UTIL_virtools_types.VXLIGHT_TYPE.VX_LIGHTPOINT:
            lit.type = 'POINT'
        case UTIL_virtools_types.VXLIGHT_TYPE.VX_LIGHTSPOT:
            lit.type = 'SPOT'
        case UTIL_virtools_types.VXLIGHT_TYPE.VX_LIGHTDIREC:
            lit.type = 'SUN'
    lit.color = rawdata.mColor.to_const_rgb()

    # MARK:
    # After set light type, we must re-fetch light object,
    # because it seems that the object hold by this variable 
    # is not the object after light type changes.
    # 
    # If I do not do this, function will throw exception 
    # like `'PointLight' object has no attribute 'spot_size'`.
    lit = bpy.data.lights[lit.name]
    match(rawdata.mType):
        case UTIL_virtools_types.VXLIGHT_TYPE.VX_LIGHTPOINT:
            point_lit: bpy.types.PointLight = typing.cast(bpy.types.PointLight, lit)
            point_lit.shadow_soft_size = rawdata.mRange
        case UTIL_virtools_types.VXLIGHT_TYPE.VX_LIGHTSPOT:
            spot_lit: bpy.types.SpotLight = typing.cast(bpy.types.SpotLight, lit)
            spot_lit.shadow_soft_size = rawdata.mRange
            spot_lit.spot_size = rawdata.mFalloff
            if rawdata.mFalloff == 0: spot_lit.spot_blend = 0.0
            else: spot_lit.spot_blend = 1.0 - rawdata.mHotSpot / rawdata.mFalloff
        case UTIL_virtools_types.VXLIGHT_TYPE.VX_LIGHTDIREC:
            pass

# Operators

class BBP_OT_apply_virtools_light(bpy.types.Operator):
    """Apply Virtools Light to Blender Light."""
    bl_idname = "bbp.apply_virtools_light"
    bl_label = "Apply to Blender Light"
    bl_options = {'UNDO'}

    @classmethod
    def poll(cls, context):
        return context.light is not None

    def execute(self, context):
        lit: bpy.types.Light = context.light
        apply_to_blender_light(lit)
        return {'FINISHED'}

# Display Panel

class BBP_PT_virtools_light(bpy.types.Panel):
    """Show Virtools Light Properties"""
    bl_label = "Virtools Light"
    bl_idname = "BBP_PT_virtools_light"
    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'
    bl_context = "data" # idk why blender use `data` as the light tab same as mesh.

    @classmethod
    def poll(cls, context):
        return context.light is not None

    def draw(self, context):
        # get layout and target
        layout = self.layout
        layout.use_property_split = True
        lit: bpy.types.Light = context.light
        props: BBP_PG_virtools_light = get_virtools_light(lit)
        rawdata: RawVirtoolsLight = get_raw_virtools_light(lit)

        # draw operator
        layout.operator(BBP_OT_apply_virtools_light.bl_idname, text = 'Apply', icon = 'NODETREE')

        # draw data
        layout.separator()
        layout.label(text = 'Basics')
        # all lights has type and color property
        sublayout = layout.row()
        sublayout.use_property_split = False
        sublayout.prop(props, 'light_type', expand = True)
        layout.prop(props, 'light_color')
        # all light has range property exception directional light
        if rawdata.mType != UTIL_virtools_types.VXLIGHT_TYPE.VX_LIGHTDIREC:
            layout.prop(props, 'light_range')

        # all light has attenuation exception directional light
        if rawdata.mType != UTIL_virtools_types.VXLIGHT_TYPE.VX_LIGHTDIREC:
            layout.separator()
            layout.label(text = 'Attenuation')
            layout.prop(props, 'constant_attenuation', text = 'Constant')
            layout.prop(props, 'linear_attenuation', text = 'Linear')
            layout.prop(props, 'quadratic_attenuation', text = 'Quadratic')

        # only spot light has spot cone properties.
        if rawdata.mType == UTIL_virtools_types.VXLIGHT_TYPE.VX_LIGHTSPOT:
            layout.separator()
            layout.label(text = 'Spot Cone')
            layout.prop(props, 'hot_spot')
            layout.prop(props, 'falloff')
            layout.prop(props, 'falloff_shape')

# Register

def register() -> None:
    bpy.utils.register_class(BBP_PG_virtools_light)
    bpy.utils.register_class(BBP_OT_apply_virtools_light)
    bpy.utils.register_class(BBP_PT_virtools_light)

    # add into light metadata
    bpy.types.Light.virtools_light = bpy.props.PointerProperty(type = BBP_PG_virtools_light)

def unregister() -> None:
    # remove from metadata
    del bpy.types.Light.virtools_light

    bpy.utils.unregister_class(BBP_PT_virtools_light)
    bpy.utils.unregister_class(BBP_OT_apply_virtools_light)
    bpy.utils.unregister_class(BBP_PG_virtools_light)