#    This file is part of Korman.
#
#    Korman is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    Korman is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with Korman.  If not, see <http://www.gnu.org/licenses/>.

import bpy
from bpy.props import *
import math
from PyHSPlasma import *

from .base import PlasmaModifierProperties
from ...exporter import ExportError, ExportAssertionError
from ... import idprops

class PlasmaSwimRegion(idprops.IDPropObjectMixin, PlasmaModifierProperties, bpy.types.PropertyGroup):
    pl_id = "swimregion"

    bl_category = "Water"
    bl_label = "Swimming Surface"
    bl_description = "Surface that the avatar can swim on"
    bl_icon = "MOD_WAVE"

    _CURRENTS = {
        "NONE": plSwimRegionInterface,
        "CIRCULAR": plSwimCircularCurrentRegion,
        "STRAIGHT": plSwimStraightCurrentRegion,
    }

    region = PointerProperty(name="Region",
                             description="Swimming detector region",
                             type=bpy.types.Object,
                             poll=idprops.poll_mesh_objects)

    down_buoyancy = FloatProperty(name="Downward Buoyancy",
                                  description="Distance the avatar sinks into the water",
                                  min=0.0, max=100.0, default=3.0,
                                  options=set())
    up_buoyancy = FloatProperty(name="Up Buoyancy",
                                description="Distance the avatar rises up after sinking",
                                min=0.0, max=100.0, default=0.05,
                                options=set())
    up_velocity = FloatProperty(name="Up Velcocity",
                                description="Rate at which the avatar rises",
                                min=0.0, max=100.0, default=3.0,
                                options=set())

    current_type = EnumProperty(name="Water Current",
                                description="",
                                items=[("NONE", "None", "No current"),
                                       ("CIRCULAR", "Circular", "Circular current"),
                                       ("STRAIGHT", "Straight", "Straight current")],
                                options=set())
    rotation = FloatProperty(name="Rotation",
                             description="Rate of rotation about the current object",
                             min=-100.0, max=100.0, default=1.0,
                             options=set())
    near_distance = FloatProperty(name="Near Distance",
                                  description="Maximum distance at which the current is at the Near Velocity rate",
                                  min=0.0, max=10000.0, default=1.0,
                                  options=set())
    far_distance = FloatProperty(name="Far Distance",
                                 description="Distance at which the current is at the Far Velocity rate",
                                 min=0.0, max=10000.0, default=1.0,
                                 options=set())
    near_velocity = FloatProperty(name="Near Velocity",
                                  description="Current velocity near the region center",
                                  min=-100.0, max=100.0, default=0.0,
                                  options=set())
    far_velocity = FloatProperty(name="Far Velocity",
                                 description="Current velocity far from the region center",
                                 min=-100.0, max=100.0, default=0.0,
                                 options=set())
    current = PointerProperty(name="Current Object",
                              description="Object whose Y-axis defines the direction of the current",
                              type=bpy.types.Object,
                              poll=idprops.poll_empty_objects)

    def export(self, exporter, bo, so):
        swimIface = self.get_key(exporter, so).object
        swimIface.downBuoyancy = self.down_buoyancy
        swimIface.upBuoyancy = self.up_buoyancy
        swimIface.maxUpwardVel = self.up_velocity
        if isinstance(swimIface, plSwimCircularCurrentRegion):
            swimIface.rotation = self.rotation
            swimIface.pullNearDistSq = pow(self.near_distance, 2)
            swimIface.pullFarDistSq = pow(self.far_distance, 2)
            swimIface.pullNearVel = self.near_velocity
            swimIface.pullFarVel = self.far_velocity
        elif isinstance(swimIface, plSwimStraightCurrentRegion):
            swimIface.nearDist = self.near_distance
            swimIface.farDist = self.far_distance
            swimIface.nearVel = self.near_velocity
            swimIface.farVel = self.far_velocity
        if isinstance(swimIface, (plSwimCircularCurrentRegion, plSwimStraightCurrentRegion)):
            if self.current is None:
                raise ExportError("Swimming Surface '{}' does not specify a current object".format(bo.name))
            swimIface.currentObj = exporter.mgr.find_create_key(plSceneObject, bl=self.current)

        # The surface needs bounds for LOS -- this is generally a flat plane, or I would think...
        # NOTE: If the artist has this on a WaveSet, they probably intend for the avatar to swim on
        #       the surface of the water BUT wave sets are supposed to conform to the bottom of the
        #       pool. Therefore, we need to flatten out a temporary mesh in that case.
        # Ohey! CWE doesn't let you swim at all if the surface isn't flat...
        losdbs = ["kLOSDBSwimRegion"]
        member_group = "kGroupLOSOnly" if exporter.mgr.getVer() != pvMoul else "kGroupStatic"
        if bo.plasma_modifiers.water_basic.enabled:
            exporter.physics.generate_flat_proxy(bo, so, z_coord=bo.location[2],
                                                 member_group=member_group,
                                                 losdbs=losdbs)
        else:
            exporter.physics.generate_physical(bo, so, bounds="trimesh",
                                               member_group=member_group,
                                               losdbs=losdbs)

        # Detector region bounds
        if self.region is not None:
            region_so = exporter.mgr.find_create_object(plSceneObject, bl=self.region)
    
            # Good news: if this phys has already been exported, this is basically a noop
            member_group = "kGroupDetector" if exporter.mgr.getVer() == "pvMoul" else "kGroupLOSOnly"
            exporter.physics.generate_physical(self.region, region_so,
                                               member_group=member_group,
                                               report_groups=["kGroupAvatar"])

            # I am a little concerned if we already have a plSwimDetector... I am not certain how
            # well Plasma would tolerate having a plSwimMsg with multiple regions referenced.
            # If you're brave, maybe you will test this...
            # What? Me test it?
            # I are chicken.
            # Mmmmm chicken ***drool***
            if exporter.mgr.find_key(plSwimDetector, so=region_so) is None:
                enter_msg, exit_msg = plSwimMsg(), plSwimMsg()
                for i in (enter_msg, exit_msg):
                    i.BCastFlags = plMessage.kLocalPropagate | plMessage.kPropagateToModifiers
                    i.sender = region_so.key
                    i.swimRegion = swimIface.key
                enter_msg.isEntering = True
                exit_msg.isEntering = False

                detector = exporter.mgr.add_object(plSwimDetector, so=region_so)
                detector.enterMsg = enter_msg
                detector.exitMsg = exit_msg
        else:
            # OK, I lied. It is perfectly legal to NOT have a detector... Think about Ahnonay,
            # if you want to have currents inside of a large body of water, only your main
            # swimming surface should have a detector. m'kay? But still, we might want to make note
            # of this sitation. Just in case someone is like "WTF! Why am I not swimming?!?!1111111"
            # Because you need to have a detector, dummy.
            exporter.report.warn("Swimming Surface '{}' does not specify a detector region".format(bo.name), indent=2)

    def get_key(self, exporter, so=None):
        pClass = self._CURRENTS[self.current_type]
        return exporter.mgr.find_create_key(pClass, bl=self.id_data, so=so)

    def harvest_actors(self):
        if self.current_type != "NONE" and self.current:
            return set((self.current.name,))
        return set()

    @classmethod
    def _idprop_mapping(cls):
        return {"current": "current_object",
                "region": "region_name"}


class PlasmaWaterModifier(idprops.IDPropMixin, PlasmaModifierProperties, bpy.types.PropertyGroup):
    pl_id = "water_basic"

    bl_category = "Water"
    bl_label = "Basic Water"
    bl_description = "Basic water properties"

    wind_object = PointerProperty(name="Wind Object",
                                  description="Object whose Y axis represents the wind direction",
                                  type=bpy.types.Object,
                                  poll=idprops.poll_empty_objects)
    wind_speed = FloatProperty(name="Wind Speed",
                               description="Magnitude of the wind",
                               default=1.0)
    envmap = PointerProperty(name="EnvMap",
                             description="Texture defining an environment map for this water object",
                             type=bpy.types.Texture,
                             poll=idprops.poll_envmap_textures)
    envmap_radius = FloatProperty(name="Environment Sphere Radius",
                                  description="How far away the first object you want to see is",
                                  min=5.0, max=10000.0,
                                  default=500.0)

    specular_tint = FloatVectorProperty(name="Specular Tint",
                                        subtype="COLOR",
                                        min=0.0, max=1.0,
                                        default=(1.0, 1.0, 1.0))
    specular_alpha = FloatProperty(name="Specular Alpha",
                                   min=0.0, max=1.0,
                                   default=0.3)
    noise = IntProperty(name="Noise",
                        subtype="PERCENTAGE",
                        min=0, max=300,
                        default=50)
    specular_start = FloatProperty(name="Specular Start",
                                   min=0.0, max=1000.0,
                                   default=50.0)
    specular_end = FloatProperty(name="Specular End",
                                 min=0.0, max=10000.0,
                                 default=1000.0)
    ripple_scale = FloatProperty(name="Ripple Scale",
                                 min=5.0, max=1000.0,
                                 default=25.0)

    depth_opacity = FloatProperty(name="Opacity End",
                                  min=0.5, max=20.0,
                                  default=3.0)
    depth_reflection = FloatProperty(name="Reflection End",
                                     min=0.5, max=20.0,
                                     default=3.0)
    depth_wave = FloatProperty(name="Wave End",
                               min=0.5, max=20.0,
                               default=4.0)
    zero_opacity = FloatProperty(name="Opacity Start",
                                 min=-10.0, max=10.0,
                                 default=-1.0)
    zero_reflection = FloatProperty(name="Reflection Start",
                                    min=-10.0, max=10.0,
                                    default=0.0)
    zero_wave = FloatProperty(name="Wave Start",
                              min=-10.0, max=10.0,
                              default=0.0)

    @property
    def copy_material(self):
        return True

    def export(self, exporter, bo, so):
        waveset = exporter.mgr.find_create_object(plWaveSet7, name=bo.name, so=so)
        if self.wind_object:
            if self.wind_object.plasma_object.enabled and self.wind_object.plasma_modifiers.animation.enabled:
                waveset.refObj = exporter.mgr.find_create_key(plSceneObject, bl=self.wind_object)
                waveset.setFlag(plWaveSet7.kHasRefObject, True)

            # This is much like what happened in PyPRP
            speed = self.wind_speed
            matrix = self.wind_object.matrix_world
            wind_dir = hsVector3(matrix[1][0] * speed, matrix[1][1] * speed, matrix[1][2] * speed)
        else:
            # Stolen shamelessly from PyPRP
            wind_dir = hsVector3(0.0871562, 0.996195, 0.0)

        # Stuff we expose
        state = waveset.state
        state.rippleScale = self.ripple_scale
        state.waterHeight = bo.location[2]
        state.windDir = wind_dir
        state.specVector = hsVector3(self.noise / 100.0, self.specular_start, self.specular_end)
        state.specularTint = hsColorRGBA(*self.specular_tint, alpha=self.specular_alpha)
        state.waterOffset = hsVector3(self.zero_opacity * -1.0, self.zero_reflection * -1.0, self.zero_wave * -1.0)
        state.depthFalloff = hsVector3(self.depth_opacity, self.depth_reflection, self.depth_wave)

        # Environment Map
        if self.envmap:
            # maybe, just maybe, we're absuing our privledges?
            dem = exporter.mesh.material.export_dynamic_env(bo, None, self.envmap, plDynamicEnvMap)
            waveset.envMap = dem.key
            state.envCenter = dem.position
            state.envRefresh = dem.refreshRate
        else:
            state.envCenter = hsVector3(*bo.location)
            state.envRefresh = 0.0
        state.envRadius = self.envmap_radius

        # These are either unused, set from somewhere else at runtime, or hardcoded
        state.waterTint = hsColorRGBA(1.0, 1.0, 1.0, 1.0)
        state.maxColor = hsColorRGBA(1.0, 1.0, 1.0, 1.0)
        state.shoreTint = hsColorRGBA(1.0, 1.0, 1.0, 1.0)
        state.maxAtten = hsVector3(1.0, 1.0, 1.0)
        state.minAtten = hsVector3(0.0, 0.0, 0.0)

        # Now, we have some related modifiers that may or may not be enabled... If not, we should
        # make them export their defaults.
        mods = bo.plasma_modifiers
        if not mods.water_geostate.enabled:
            mods.water_geostate.convert_default_wavestate(state.geoState)
        if not mods.water_texstate.enabled:
            mods.water_texstate.convert_default_wavestate(state.texState)
        if not mods.water_shore.enabled:
            mods.water_shore.convert_default(state)

    @classmethod
    def _idprop_mapping(cls):
        return {"wind_object": "wind_object_name",
                "envmap": "envmap_name"}

    def _idprop_sources(self):
        return {"wind_object_name": bpy.data.objects,
                "envmap_name": bpy.data.textures}

    @property
    def key_name(self):
        return "{}_WaveSet7".format(self.id_data.name)


class PlasmaShoreObject(idprops.IDPropObjectMixin, bpy.types.PropertyGroup):
    display_name = StringProperty(name="Display Name")
    shore_object = PointerProperty(name="Shore Object",
                                   description="Object that waves crash upon",
                                   type=bpy.types.Object,
                                   poll=idprops.poll_mesh_objects)

    @classmethod
    def _idprop_mapping(cls):
        return {"shore_object": "object_name"}


class PlasmaWaterShoreModifier(PlasmaModifierProperties):
    pl_depends = {"water_basic"}
    pl_id = "water_shore"

    bl_category = "Water"
    bl_label = "Water Shore"
    bl_description = ""

    # The basic modifier may want to export a default copy of us
    _shore_tint_default = (0.2, 0.4, 0.4)
    _shore_opacity_default = 40
    _wispiness_default = 50
    _period_default = 100.0
    _finger_default = 100.0
    _edge_opacity_default = 100
    _edge_radius_default = 100.0

    shores = CollectionProperty(type=PlasmaShoreObject)
    active_shore_index = IntProperty(options={"HIDDEN"})

    shore_tint = FloatVectorProperty(name="Shore Tint",
                                     subtype="COLOR",
                                     min=0.0, max=1.0,
                                     default=_shore_tint_default)
    shore_opacity = IntProperty(name="Shore Opacity",
                                subtype="PERCENTAGE",
                                min=0, max=100,
                                default=_shore_opacity_default)
    wispiness = IntProperty(name="Wispiness",
                            subtype="PERCENTAGE",
                            min=0, max=200,
                            default=_wispiness_default)

    period = FloatProperty(name="Period",
                           min=0.0, max=200.0,
                           default=_period_default)
    finger = FloatProperty(name="Finger",
                           min=50.0, max=300.0,
                           default=_finger_default)
    edge_opacity = IntProperty(name="Edge Opacity",
                               subtype="PERCENTAGE",
                               min=0, max=100,
                               default=_edge_opacity_default)
    edge_radius = FloatProperty(name="Edge Radius",
                                subtype="PERCENTAGE",
                                min=50, max=300,
                                default=_edge_radius_default)

    def convert_default(self, wavestate):
        wavestate.wispiness = self._wispiness_default / 100.0
        wavestate.minColor = hsColorRGBA(*self._shore_tint_default, alpha=(self._shore_opacity_default / 100.0))
        wavestate.edgeOpacity = self._edge_opacity_default / 100.0
        wavestate.edgeRadius = self._edge_radius_default / 100.0
        wavestate.period = self._period_default / 100.0
        wavestate.fingerLength = self._finger_default / 100.0

    def export(self, exporter, bo, so):
        waveset = exporter.mgr.find_create_object(plWaveSet7, name=bo.name, so=so)
        wavestate = waveset.state

        for i in self.shores:
            if i.shore_object is None:
                raise ExportError("'{}': Shore Object for '{}' is invalid".format(self.key_name, i.display_name))
            waveset.addShore(exporter.mgr.find_create_key(plSceneObject, bl=i.shore_object))

        wavestate.wispiness = self.wispiness / 100.0
        wavestate.minColor = hsColorRGBA(*self.shore_tint, alpha=(self.shore_opacity / 100.0))
        wavestate.edgeOpacity = self.edge_opacity / 100.0
        wavestate.edgeRadius = self.edge_radius / 100.0
        wavestate.period = self.period / 100.0
        wavestate.fingerLength = self.finger / 100.0


class PlasmaWaveState:
    pl_depends = {"water_basic"}

    def convert_wavestate(self, state):
        state.minLength = self.min_length
        state.maxLength = self.max_length
        state.ampOverLen = self.amplitude / 100.0
        state.chop = self.chop / 100.0
        state.angleDev = self.angle_dev

    def convert_default_wavestate(self, state):
        cls = self.__class__
        state.minLength = cls._min_length_default
        state.maxLength = cls._max_length_default
        state.ampOverLen = cls._amplitude_default / 100.0
        state.chop = cls._chop_default / 100.0
        state.angleDev = cls._angle_dev_default

    @classmethod
    def register(cls):
        cls.min_length = FloatProperty(name="Min Length",
                                       description="Smallest wave length",
                                       min=0.1, max=50.0,
                                       default=cls._min_length_default)
        cls.max_length = FloatProperty(name="Max Length",
                                       description="Largest wave length",
                                       min=0.1, max=50.0,
                                       default=cls._max_length_default)
        cls.amplitude = IntProperty(name="Amplitude",
                                    description="Multiplier for wave height",
                                    subtype="PERCENTAGE",
                                    min=0, max=100,
                                    default=cls._amplitude_default)
        cls.chop = IntProperty(name="Choppiness",
                               description="Sharpness of wave crests",
                               subtype="PERCENTAGE",
                               min=0, max=500,
                               default=cls._chop_default)
        cls.angle_dev = FloatProperty(name="Wave Spread",
                                      subtype="ANGLE",
                                      min=math.radians(0.0), max=math.radians(180.0),
                                      default=cls._angle_dev_default)


class PlasmaWaveGeoState(PlasmaWaveState, PlasmaModifierProperties):
    pl_id = "water_geostate"

    bl_category = "Water"
    bl_label = "Geometry Waves"
    bl_description = "Mesh wave settings"

    _min_length_default = 4.0
    _max_length_default = 8.0
    _amplitude_default = 10
    _chop_default = 50
    _angle_dev_default = math.radians(20.0)

    def export(self, exporter, bo, so):
        waveset = exporter.mgr.find_create_object(plWaveSet7, name=bo.name, so=so)
        self.convert_wavestate(waveset.state.geoState)


class PlasmaWaveTexState(PlasmaWaveState, PlasmaModifierProperties):
    pl_id = "water_texstate"

    bl_category = "Water"
    bl_label = "Texture Waves"
    bl_description = "Texture wave settings"

    _min_length_default = 0.1
    _max_length_default = 4.0
    _amplitude_default = 10
    _chop_default = 50
    _angle_dev_default = math.radians(20.0)

    def export(self, exporter, bo, so):
        waveset = exporter.mgr.find_create_object(plWaveSet7, name=bo.name, so=so)
        self.convert_wavestate(waveset.state.texState)
