// Copyright 2009-2021 Intel Corporation
// SPDX-License-Identifier: Apache-2.0

#pragma once

namespace embree {
    namespace SceneGraph {
        enum LightType {
            LIGHT_AMBIENT,
            LIGHT_POINT,
            LIGHT_DIRECTIONAL,
            LIGHT_SPOT,
            LIGHT_DISTANT,
            LIGHT_TRIANGLE,
            LIGHT_QUAD,
        };

        class Light {
            ALIGNED_CLASS_(16)

        public:
            Light(LightType type) : type(type) {
            }

            LightType getType() const { return type; }

        private:
            LightType type;
        };

        class AmbientLight : public Light {
        public:
            AmbientLight(const Vec3fa& L)
                : Light(LIGHT_AMBIENT), L(L) {
            }

            AmbientLight transform(const AffineSpace3fa& space) const {
                return AmbientLight(L);
            }

            static AmbientLight lerp(const AmbientLight& light0, const AmbientLight& light1, const float f) {
                return AmbientLight(embree::lerp(light0.L, light1.L, f));
            }

        public:
            Vec3fa L; //!< radiance of ambient light
        };

        class PointLight : public Light {
        public:
            PointLight(const Vec3fa& P, const Vec3fa& I)
                : Light(LIGHT_POINT), P(P), I(I) {
            }

            PointLight transform(const AffineSpace3fa& space) const {
                return PointLight(xfmPoint(space, P), I);
            }

            static PointLight lerp(const PointLight& light0, const PointLight& light1, const float f) {
                return PointLight(embree::lerp(light0.P, light1.P, f),
                                  embree::lerp(light0.I, light1.I, f));
            }

        public:
            Vec3fa P; //!< position of point light
            Vec3fa I; //!< radiant intensity of point light
        };

        class DirectionalLight : public Light {
        public:
            DirectionalLight(const Vec3fa& D, const Vec3fa& E)
                : Light(LIGHT_DIRECTIONAL), D(D), E(E) {
            }

            DirectionalLight transform(const AffineSpace3fa& space) const {
                return DirectionalLight(xfmVector(space, D), E);
            }

            static DirectionalLight lerp(const DirectionalLight& light0, const DirectionalLight& light1,
                                         const float f) {
                return DirectionalLight(embree::lerp(light0.D, light1.D, f),
                                        embree::lerp(light0.E, light1.E, f));
            }

        public:
            Vec3fa D; //!< Light direction
            Vec3fa E; //!< Irradiance (W/m^2)
        };

        class SpotLight : public Light {
        public:
            SpotLight(const Vec3fa& P, const Vec3fa& D, const Vec3fa& I, float angleMin, float angleMax)
                : Light(LIGHT_SPOT), P(P), D(D), I(I), angleMin(angleMin), angleMax(angleMax) {
            }

            SpotLight transform(const AffineSpace3fa& space) const {
                return SpotLight(xfmPoint(space, P), xfmVector(space, D), I, angleMin, angleMax);
            }

            static SpotLight lerp(const SpotLight& light0, const SpotLight& light1, const float f) {
                return SpotLight(embree::lerp(light0.P, light1.P, f),
                                 embree::lerp(light0.D, light1.D, f),
                                 embree::lerp(light0.I, light1.I, f),
                                 embree::lerp(light0.angleMin, light1.angleMin, f),
                                 embree::lerp(light0.angleMax, light1.angleMax, f));
            }

        public:
            Vec3fa P; //!< Position of the spot light
            Vec3fa D; //!< Light direction of the spot light
            Vec3fa I; //!< Radiant intensity (W/sr)
            float angleMin, angleMax; //!< Linear falloff region
        };

        class DistantLight : public Light {
        public:
            DistantLight(const Vec3fa& D, const Vec3fa& L, const float halfAngle)
                : Light(LIGHT_DISTANT), D(D), L(L), halfAngle(halfAngle), radHalfAngle(deg2rad(halfAngle)),
                  cosHalfAngle(cos(deg2rad(halfAngle))) {
            }

            DistantLight transform(const AffineSpace3fa& space) const {
                return DistantLight(xfmVector(space, D), L, halfAngle);
            }

            static DistantLight lerp(const DistantLight& light0, const DistantLight& light1, const float f) {
                return DistantLight(embree::lerp(light0.D, light1.D, f),
                                    embree::lerp(light0.L, light1.L, f),
                                    embree::lerp(light0.halfAngle, light1.halfAngle, f));
            }

        public:
            Vec3fa D; //!< Light direction
            Vec3fa L; //!< Radiance (W/(m^2*sr))
            float halfAngle; //!< Half illumination angle
            float radHalfAngle; //!< Half illumination angle in radians
            float cosHalfAngle; //!< Cosine of half illumination angle
        };

        class TriangleLight : public Light {
        public:
            TriangleLight(const Vec3fa& v0, const Vec3fa& v1, const Vec3fa& v2, const Vec3fa& L)
                : Light(LIGHT_TRIANGLE), v0(v0), v1(v1), v2(v2), L(L) {
            }

            TriangleLight transform(const AffineSpace3fa& space) const {
                return TriangleLight(xfmPoint(space, v0), xfmPoint(space, v1), xfmPoint(space, v2), L);
            }

            static TriangleLight lerp(const TriangleLight& light0, const TriangleLight& light1, const float f) {
                return TriangleLight(embree::lerp(light0.v0, light1.v0, f),
                                     embree::lerp(light0.v1, light1.v1, f),
                                     embree::lerp(light0.v2, light1.v2, f),
                                     embree::lerp(light0.L, light1.L, f));
            }

        public:
            Vec3fa v0;
            Vec3fa v1;
            Vec3fa v2;
            Vec3fa L;
        };

        class QuadLight : public Light {
        public:
            QuadLight(const Vec3fa& v0, const Vec3fa& v1, const Vec3fa& v2, const Vec3fa& v3, const Vec3fa& L)
                : Light(LIGHT_QUAD), v0(v0), v1(v1), v2(v2), v3(v3), L(L) {
            }

            QuadLight transform(const AffineSpace3fa& space) const {
                return QuadLight(xfmPoint(space, v0), xfmPoint(space, v1), xfmPoint(space, v2), xfmPoint(space, v3), L);
            }

            static QuadLight lerp(const QuadLight& light0, const QuadLight& light1, const float f) {
                return QuadLight(embree::lerp(light0.v0, light1.v0, f),
                                 embree::lerp(light0.v1, light1.v1, f),
                                 embree::lerp(light0.v2, light1.v2, f),
                                 embree::lerp(light0.v3, light1.v3, f),
                                 embree::lerp(light0.L, light1.L, f));
            }

        public:
            Vec3fa v0;
            Vec3fa v1;
            Vec3fa v2;
            Vec3fa v3;
            Vec3fa L;
        };
    }
}
