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

#include "light.h"
#include <sampling.hpp>
#include <math/linearspace3.h>
#include "helper.hpp"

namespace embree {
    struct SpotLight {
        Light super; //!< inherited light fields

        Vec3fa position; //!< Position of the SpotLight
        LinearSpace3fa frame; //!< coordinate frame, with vz == direction that the SpotLight is emitting
        Vec3fa power; //!< RGB color and intensity of the SpotLight
        float cosAngleMax; //!< Angular limit of the spot in an easier to use form: cosine of the half angle in radians
        float cosAngleScale; //!< 1/(cos(border of the penumbra area) - cosAngleMax); positive
        float radius; //!< defines the size of the (extended) SpotLight
        float diskPdf; //!< pdf of disk with radius
    };


    // Implementation
    //////////////////////////////////////////////////////////////////////////////

    Light_SampleRes SpotLight_sample(const Light* super,
                                     const Sample& dg,
                                     const Vec2f& s) {
        const SpotLight* self = (SpotLight *) super;
        Light_SampleRes res;

        // extant light vector from the hit point
        res.dir = self->position - dg.P;

        if (self->radius > 0.0f)
            res.dir = self->frame * uniformSampleDisk(self->radius, s) + res.dir;

        const float dist2 = dot(res.dir, res.dir);
        const float invdist = rsqrt(dist2);

        // normalized light vector
        res.dir = res.dir * invdist;
        res.dist = dist2 * invdist;

        // cosine of the negated light direction and light vector.
        const float cosAngle = -dot(self->frame.vz, res.dir);
        const float angularAttenuation = clamp((cosAngle - self->cosAngleMax) * self->cosAngleScale);

        if (self->radius > 0.0f)
            res.pdf = self->diskPdf * dist2 * abs(cosAngle);
        else
            res.pdf = inf; // we always take this res

        // convert from power to radiance by attenuating by distance^2; attenuate by angle
        res.weight = self->power * (sqr(invdist) * angularAttenuation);

        return res;
    }

    Light_EvalRes SpotLight_eval(const Light* super,
                                 const Sample& dg,
                                 const Vec3fa& dir) {
        const SpotLight* self = (SpotLight *) super;
        Light_EvalRes res;
        res.value = Vec3fa(0.0f);
        res.dist = inf;
        res.pdf = 0.0f;

        if (self->radius > 0.0f) {
            // intersect disk
            const float cosAngle = -dot(dir, self->frame.vz);
            if (cosAngle > self->cosAngleMax) {
                // inside illuminated cone?
                const Vec3fa vp = dg.P - self->position;
                const float dp = dot(vp, self->frame.vz);
                if (dp > 0.0f) {
                    // in front of light?
                    const float t = dp * rcp(cosAngle);
                    const Vec3fa vd = vp + t * dir;
                    if (dot(vd, vd) < sqr(self->radius)) {
                        // inside disk?
                        const float angularAttenuation = min((cosAngle - self->cosAngleMax) * self->cosAngleScale, 1.f);
                        const float pdf = self->diskPdf * cosAngle;
                        res.value = self->power * (angularAttenuation * pdf); // *sqr(t)/sqr(t) cancels
                        res.dist = t;
                        res.pdf = pdf * sqr(t);
                    }
                }
            }
        }

        return res;
    }


    // Exports (called from C++)
    //////////////////////////////////////////////////////////////////////////////

    //! Set the parameters of an ispc-side SpotLight object
    extern "C" void SpotLight_set(void* super,
                                  const Vec3fa& position,
                                  const Vec3fa& direction,
                                  const Vec3fa& power,
                                  float cosAngleMax,
                                  float cosAngleScale,
                                  float radius) {
        SpotLight* self = (SpotLight *) super;
        self->position = position;
        self->frame = frame(direction);
        self->power = power;
        self->cosAngleMax = cosAngleMax;
        self->cosAngleScale = cosAngleScale;
        self->radius = radius;
        self->diskPdf = uniformSampleDiskPDF(radius);
    }

    //! Create an ispc-side SpotLight object
    extern "C" void* SpotLight_create() {
        SpotLight* self = (SpotLight *) alignedUSMMalloc(sizeof(SpotLight), 16);

        Light_Constructor(&self->super);
        //self->super.sample = GET_FUNCTION_POINTER(SpotLight_sample);
        //self->super.eval = GET_FUNCTION_POINTER(SpotLight_eval);
        self->super.type = LIGHT_SPOT;

        SpotLight_set(self,
                      Vec3fa(0.f),
                      Vec3fa(0.f, 0.f, 1.f),
                      Vec3fa(1.f),
                      0.f,
                      100.f,
                      0.f);

        return self;
    }
} // namespace embree
