/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef TNT_FILAMENT_LIGHTMANAGER_H
#define TNT_FILAMENT_LIGHTMANAGER_H

#include <filament/FilamentAPI.h>
#include <filament/Color.h>

#include <utils/compiler.h>
#include <utils/Entity.h>
#include <utils/EntityInstance.h>

#include <math/mathfwd.h>

namespace utils {
    class Entity;
} // namespace utils

namespace filament {

class Engine;
class FEngine;
class FLightManager;

/**
 * LightManager allows to create a light source in the scene, such as a sun or street lights.
 *
 * At least one light must be added to a scene in order to see anything
 * (unless the Material.Shading.UNLIT is used).
 *
 *
 * Creation and destruction
 * ========================
 *
 * A Light component is created using the LightManager::Builder and destroyed by calling
 * LightManager::destroy(utils::Entity).
 *
 * ~~~~~~~~~~~{.cpp}
 *  filament::Engine* engine = filament::Engine::create();
 *  utils::Entity sun = utils::EntityManager.get().create();
 *
 *  filament::LightManager::Builder(Type::SUN)
 *              .castShadows(true)
 *              .build(*engine, sun);
 *
 *  engine->getLightManager().destroy(sun);
 * ~~~~~~~~~~~
 *
 *
 * Light types
 * ===========
 *
 * Lights come in three flavors:
 * - directional lights
 * - point lights
 * - spot lights
 *
 *
 * Directional lights
 * ------------------
 *
 * Directional lights have a direction, but don't have a position. All light rays are
 * parallel and come from infinitely far away and from everywhere. Typically a directional light
 * is used to simulate the sun.
 *
 * Directional lights and spot lights are able to cast shadows.
 *
 * To create a directional light use Type.DIRECTIONAL or Type.SUN, both are similar, but the later
 * also draws a sun's disk in the sky and its reflection on glossy objects.
 *
 * @warning Currently, only a single directional light is supported. If several directional lights
 * are added to the scene, the dominant one will be used.
 *
 * @see Builder.direction(), Builder.sunAngularRadius()
 *
 * Point lights
 * ------------
 *
 * Unlike directional lights, point lights have a position but emit light in all directions.
 * The intensity of the light diminishes with the inverse square of the distance to the light.
 * Builder.falloff() controls distance beyond which the light has no more influence.
 *
 * A scene can have multiple point lights.
 *
 * @see Builder.position(), Builder.falloff()
 *
 * Spot lights
 * -----------
 *
 * Spot lights are similar to point lights but the light it emits is limited to a cone defined by
 * Builder.spotLightCone() and the light's direction.
 *
 * A spot light is therefore defined by a position, a direction and inner and outer cones. The
 * spot light's influence is limited to inside the outer cone. The inner cone defines the light's
 * falloff attenuation.
 *
 * A physically correct spot light is a little difficult to use because changing the outer angle
 * of the cone changes the illumination levels, as the same amount of light is spread over a
 * changing volume. The coupling of illumination and the outer cone means that an artist cannot
 * tweak the influence cone of a spot light without also changing the perceived illumination.
 * It therefore makes sense to provide artists with a parameter to disable this coupling. This
 * is the difference between Type.FOCUSED_SPOT and Type.SPOT.
 *
 * @see Builder.position(), Builder.direction(), Builder.falloff(), Builder.spotLightCone()
 *
 * Performance considerations
 * ==========================
 *
 * Generally, adding lights to the scene hurts performance, however filament is designed to be
 * able to handle hundreds of lights in a scene under certain conditions. Here are some tips
 * to keep performances high.
 *
 * 1. Prefer spot lights to point lights and use the smallest outer cone angle possible.
 *
 * 2. Use the smallest possible falloff distance for point and spot lights.
 *    Performance is very sensitive to overlapping lights. The falloff distance essentially
 *    defines a sphere of influence for the light, so try to position point and spot lights
 *    such that they don't overlap too much.
 *
 *    On the other hand, a scene can contain hundreds of non overlapping lights without
 *    incurring a significant overhead.
 *
 */
class UTILS_PUBLIC LightManager : public FilamentAPI {
    struct BuilderDetails;

public:
    using Instance = utils::EntityInstance<LightManager>;

    /**
     * Returns the number of component in the LightManager, not that component are not
     * guaranteed to be active. Use the EntityManager::isAlive() before use if needed.
     *
     * @return number of component in the LightManager
     */
    size_t getComponentCount() const noexcept;

    /**
     * Returns the list of Entity for all components. Use getComponentCount() to know the size
     * of the list.
     * @return a pointer to Entity
     */
    utils::Entity const* getEntities() const noexcept;

    /**
     * Returns whether a particular Entity is associated with a component of this LightManager
     * @param e An Entity.
     * @return true if this Entity has a component associated with this manager.
     */
    bool hasComponent(utils::Entity e) const noexcept;

    /**
     * Gets an Instance representing the Light component associated with the given Entity.
     * @param e An Entity.
     * @return An Instance object, which represents the Light component associated with the Entity e.
     * @note Use Instance::isValid() to make sure the component exists.
     * @see hasComponent()
     */
    Instance getInstance(utils::Entity e) const noexcept;

    // destroys this component from the given entity
    void destroy(utils::Entity e) noexcept;


    //! Denotes the type of the light being created.
    enum class Type : uint8_t {
        SUN,            //!< Directional light that also draws a sun's disk in the sky.
        DIRECTIONAL,    //!< Directional light, emits light in a given direction.
        POINT,          //!< Point light, emits light from a position, in all directions.
        FOCUSED_SPOT,   //!< Physically correct spot light.
        SPOT,           //!< Spot light with coupling of outer cone and illumination disabled.
    };

    /**
     * Control the quality / performance of the shadow map associated to this light
     */
    struct ShadowOptions {
        /** Size of the shadow map in texels. Must be a power-of-two. */
        uint32_t mapSize = 1024;

        /**
         * Number of shadow cascades to use for this light. Must be between 1 and 4 (inclusive).
         * A value greater than 1 turns on cascaded shadow mapping (CSM).
         * Only applicable to Type.SUN or Type.DIRECTIONAL lights.
         *
         * When using shadow cascades, cascadeSplitPositions must also be set.
         *
         * @see ShadowOptions::cascadeSplitPositions
         */
        uint8_t shadowCascades = 1;

        /**
         * The split positions for shadow cascades.
         *
         * Cascaded shadow mapping (CSM) partitions the camera frustum into cascades. These values
         * determine the planes along the camera's Z axis to split the frustum. The camera near
         * plane is represented by 0.0f and the far plane represented by 1.0f.
         *
         * For example, if using 4 cascades, these values would set a uniform split scheme:
         * { 0.25f, 0.50f, 0.75f }
         *
         * For N cascades, N - 1 split positions will be read from this array.
         *
         * Filament provides utility methods inside LightManager::ShadowCascades to help set these
         * values. For example, to use a uniform split scheme:
         *
         * ~~~~~~~~~~~{.cpp}
         *   LightManager::ShadowCascades::computeUniformSplits(options.splitPositions, 4);
         * ~~~~~~~~~~~
         *
         * @see ShadowCascades::computeUniformSplits
         * @see ShadowCascades::computeLogSplits
         * @see ShadowCascades::computePracticalSplits
         */
        float cascadeSplitPositions[3] = { 0.25f, 0.50f, 0.75f };

        /** Constant bias in world units (e.g. meters) by which shadows are moved away from the
         * light. 1mm by default.
         */
        float constantBias = 0.001f;

        /** Amount by which the maximum sampling error is scaled. The resulting value is used
         * to move the shadow away from the fragment normal. Should be 1.0.
         */
        float normalBias = 1.0f;

        /** Distance from the camera after which shadows are clipped. this is used to clip
         * shadows that are too far and wouldn't contribute to the scene much, improving
         * performance and quality. This value is always positive.
         * Use 0.0f to use the camera far distance.
         */
        float shadowFar = 0.0f;

        /** Optimize the quality of shadows from this distance from the camera. Shadows will
         * be rendered in front of this distance, but the quality may not be optimal.
         * This value is always positive. Use 0.0f to use the camera near distance.
         * The default of 1m works well with many scenes. The quality of shadows may drop
         * rapidly when this value decreases.
         */
        float shadowNearHint = 1.0f;

        /** Optimize the quality of shadows in front of this distance from the camera. Shadows
         * will be rendered behind this distance, but the quality may not be optimal.
         * This value is always positive. Use std::numerical_limits<float>::infinity() to
         * use the camera far distance.
         */
        float shadowFarHint = 100.0f;

        /**
         * Controls whether the shadow map should be optimized for resolution or stability.
         * When set to true, all resolution enhancing features that can affect stability are
         * disabling, resulting in significantly lower resolution shadows, albeit stable ones.
         */
        bool stable = false;

        /**
         * Constant bias in depth-resolution units by which shadows are moved away from the
         * light. The default value of 0.5 is used to round depth values up.
         * Generally this value shouldn't be changed or at least be small and positive.
         */
        float polygonOffsetConstant = 0.5f;

        /**
         * Bias based on the change in depth in depth-resolution units by which shadows are moved
         * away from the light. The default value of 2.0 works well with SHADOW_SAMPLING_PCF_LOW.
         * Generally this value is between 0.5 and the size in texel of the PCF filter.
         * Setting this value correctly is essential for LISPSM shadow-maps.
         */
        float polygonOffsetSlope = 2.0f;

        /**
         * Whether screen-space contact shadows are used. This applies regardless of whether a
         * Renderable is a shadow caster.
         * Screen-space contact shadows are typically useful in large scenes.
         * (off by default)
         */
        bool screenSpaceContactShadows = false;

        /**
         * Number of ray-marching steps for screen-space contact shadows (8 by default).
         *
         * CAUTION: this parameter is ignored for all lights except the directional/sun light,
         *          all other lights use the same value set for the directional/sun light.
         *
         */
        uint8_t stepCount = 8;

        /**
         * Maximum shadow-occluder distance for screen-space contact shadows (world units).
         * (30 cm by default)
         *
         * CAUTION: this parameter is ignored for all lights except the directional/sun light,
         *          all other lights use the same value set for the directional/sun light.
         *
         */
        float maxShadowDistance = 0.3;

        /**
         * Options available when the View's ShadowType is set to VSM.
         *
         * @warning This API is still experimental and subject to change.
         * @see View::setShadowType
         */
        struct Vsm {
            /**
             * The number of MSAA samples to use when rendering VSM shadow maps.
             * Must be a power-of-two and greater than or equal to 1. A value of 1 effectively turns
             * off MSAA.
             * Higher values may not be available depending on the underlying hardware.
             */
            uint8_t msaaSamples = 1;
        } vsm;
    };

    struct ShadowCascades {
        /**
         * Utility method to compute ShadowOptions::cascadeSplitPositions according to a uniform
         * split scheme.
         *
         * @param splitPositions    a float array of at least size (cascades - 1) to write the split
         *                          positions into
         * @param cascades          the number of shadow cascades, at most 4
         */
        static void computeUniformSplits(float* splitPositions, uint8_t cascades);

        /**
         * Utility method to compute ShadowOptions::cascadeSplitPositions according to a logarithmic
         * split scheme.
         *
         * @param splitPositions    a float array of at least size (cascades - 1) to write the split
         *                          positions into
         * @param cascades          the number of shadow cascades, at most 4
         * @param near              the camera near plane
         * @param far               the camera far plane
         */
        static void computeLogSplits(float* splitPositions, uint8_t cascades,
                float near, float far);

        /**
         * Utility method to compute ShadowOptions::cascadeSplitPositions according to a practical
         * split scheme.
         *
         * The practical split scheme uses uses a lambda value to interpolate between the logarithmic
         * and uniform split schemes. Start with a lambda value of 0.5f and adjust for your scene.
         *
         * See: Zhang et al 2006, "Parallel-split shadow maps for large-scale virtual environments"
         *
         * @param splitPositions    a float array of at least size (cascades - 1) to write the split
         *                          positions into
         * @param cascades          the number of shadow cascades, at most 4
         * @param near              the camera near plane
         * @param far               the camera far plane
         * @param lambda            a float in the range [0, 1] that interpolates between log and
         *                          uniform split schemes
         */
        static void computePracticalSplits(float* splitPositions, uint8_t cascades,
                float near, float far, float lambda);
    };

    //! Use Builder to construct a Light object instance
    class Builder : public BuilderBase<BuilderDetails> {
        friend struct BuilderDetails;
    public:
        /**
         * Creates a light builder and set the light's #Type.
         *
         * @param type #Type of Light object to create.
         */
        explicit Builder(Type type) noexcept;
        Builder(Builder const& rhs) noexcept;
        Builder(Builder&& rhs) noexcept;
        ~Builder() noexcept;
        Builder& operator=(Builder const& rhs) noexcept;
        Builder& operator=(Builder&& rhs) noexcept;

        /**
         * Whether this Light casts shadows (disabled by default)
         *
         * @param enable Enables or disables casting shadows from this Light.
         *
         * @return This Builder, for chaining calls.
         *
         * @warning
         * - Only a Type.DIRECTIONAL, Type.SUN, Type.SPOT, or Type.FOCUSED_SPOT light can cast shadows
         */
        Builder& castShadows(bool enable) noexcept;

        /**
         * Sets the shadow-map options for this light.
         *
         * @return This Builder, for chaining calls.
         */
        Builder& shadowOptions(const ShadowOptions& options) noexcept;

        /**
         * Whether this light casts light (enabled by default)
         *
         * @param enable Enables or disables lighting from this Light.
         *
         * @return This Builder, for chaining calls.
         *
         * @note
         * In some situations it can be useful to have a light in the scene that doesn't
         * actually emit light, but does cast shadows.
         */
        Builder& castLight(bool enable) noexcept;

        /**
         * Sets the initial position of the light in world space.
         *
         * @param position Light's position in world space. The default is at the origin.
         *
         * @return This Builder, for chaining calls.
         *
         * @note
         * The Light's position is ignored for directional lights (Type.DIRECTIONAL or Type.SUN)
         */
        Builder& position(const math::float3& position) noexcept;

        /**
         * Sets the initial direction of a light in world space.
         *
         * @param direction Light's direction in world space. Should be a unit vector.
         *                  The default is {0,-1,0}.
         *
         * @return This Builder, for chaining calls.
         *
         * @note
         * The Light's direction is ignored for Type.POINT lights.
         */
        Builder& direction(const math::float3& direction) noexcept;

        /**
         * Sets the initial color of a light.
         *
         * @param color Color of the light specified in the linear sRGB color-space.
         *              The default is white {1,1,1}.
         *
         * @return This Builder, for chaining calls.
         */
        Builder& color(const LinearColor& color) noexcept;

        /**
         * Sets the initial intensity of a light.
         * @param intensity This parameter depends on the Light.Type:
         *                  - For directional lights, it specifies the illuminance in *lux*
         *                  (or *lumen/m^2*).
         *                  - For point lights and spot lights, it specifies the luminous power
         *                  in *lumen*.
         *
         * @return This Builder, for chaining calls.
         *
         * For example, the sun's illuminance is about 100,000 lux.
         *
         * This method overrides any prior calls to intensity or intensityCandela.
         *
         */
        Builder& intensity(float intensity) noexcept;

        /**
         * Sets the initial intensity of a spot or point light in candela.
         *
         * @param intensity Luminous intensity in *candela*.
         *
         * @return This Builder, for chaining calls.
         *
         * @note
         * This method is equivalent to calling intensity(float intensity) for directional lights
         * (Type.DIRECTIONAL or Type.SUN).
         *
         * This method overrides any prior calls to intensity or intensityCandela.
         */
        Builder& intensityCandela(float intensity) noexcept;

        /**
         * Sets the initial intensity of a light in watts.
         *
         * @param watts         Energy consumed by a lightbulb. It is related to the energy produced
         *                      and ultimately the brightness by the \p efficiency parameter.
         *                      This value is often available on the packaging of commercial
         *                      lightbulbs.
         *
         * @param efficiency    Efficiency in percent. This depends on the type of lightbulb used.
         *
         *  Lightbulb type  | Efficiency
         * ----------------:|-----------:
         *     Incandescent |  2.2%
         *         Halogen  |  7.0%
         *             LED  |  8.7%
         *     Fluorescent  | 10.7%
         *
         * @return This Builder, for chaining calls.
         *
         *
         * @note
         * This call is equivalent to `Builder::intensity(efficiency * 683 * watts);`
         *
         * This method overrides any prior calls to intensity or intensityCandela.
         */
        Builder& intensity(float watts, float efficiency) noexcept;

        /**
         * Set the falloff distance for point lights and spot lights.
         *
         * At the falloff distance, the light has no more effect on objects.
         *
         * The falloff distance essentially defines a *sphere of influence* around the light, and
         * therefore has an impact on performance. Larger falloffs might reduce performance
         * significantly, especially when many lights are used.
         *
         * Try to avoid having a large number of light's spheres of influence overlap.
         *
         * @param radius Falloff distance in world units. Default is 1 meter.
         *
         * @return This Builder, for chaining calls.
         *
         * @note
         * The Light's falloff is ignored for directional lights (Type.DIRECTIONAL or Type.SUN)
         */
        Builder& falloff(float radius) noexcept;

        /**
         * Defines a spot light'st angular falloff attenuation.
         *
         * A spot light is defined by a position, a direction and two cones, \p inner and \p outer.
         * These two cones are used to define the angular falloff attenuation of the spot light
         * and are defined by the angle from the center axis to where the falloff begins (i.e.
         * cones are defined by their half-angle).
         *
         * @param inner inner cone angle in *radians* between 0 and @f$ \pi/2 @f$
         *
         * @param outer outer cone angle in *radians* between \p inner and @f$ \pi/2 @f$
         *
         * @return This Builder, for chaining calls.
         *
         * @note
         * The spot light cone is ignored for directional and point lights.
         *
         * @see Type.SPOT, Type.FOCUSED_SPOT
         */
        Builder& spotLightCone(float inner, float outer) noexcept;

        /**
         * Defines the angular radius of the sun, in degrees, between 0.25° and 20.0°
         *
         * The Sun as seen from Earth has an angular size of 0.526° to 0.545°
         *
         * @param angularRadius sun's radius in degree. Default is 0.545°.
         *
         * @return This Builder, for chaining calls.
         */
        Builder& sunAngularRadius(float angularRadius) noexcept;

        /**
         * Defines the halo radius of the sun. The radius of the halo is defined as a
         * multiplier of the sun angular radius.
         *
         * @param haloSize radius multiplier. Default is 10.0.
         *
         * @return This Builder, for chaining calls.
         */
        Builder& sunHaloSize(float haloSize) noexcept;

        /**
         * Defines the halo falloff of the sun. The falloff is a dimensionless number
         * used as an exponent.
         *
         * @param haloFalloff halo falloff. Default is 80.0.
         *
         * @return This Builder, for chaining calls.
         */
        Builder& sunHaloFalloff(float haloFalloff) noexcept;

        enum Result { Error = -1, Success = 0  };

        /**
         * Adds the Light component to an entity.
         *
         * @param engine Reference to the filament::Engine to associate this light with.
         * @param entity Entity to add the light component to.
         * @return Success if the component was created successfully, Error otherwise.
         *
         * If exceptions are disabled and an error occurs, this function is a no-op.
         *        Success can be checked by looking at the return value.
         *
         * If this component already exists on the given entity, it is first destroyed as if
         * destroy(utils::Entity e) was called.
         *
         * @warning
         * Currently, only 2048 lights can be created on a given Engine.
         *
         * @exception utils::PostConditionPanic if a runtime error occurred, such as running out of
         *            memory or other resources.
         * @exception utils::PreConditionPanic if a parameter to a builder function was invalid.
         */
        Result build(Engine& engine, utils::Entity entity);

    private:
        friend class FEngine;
        friend class FLightManager;
    };

    static constexpr float EFFICIENCY_INCANDESCENT = 0.0220f;   //!< Typical efficiency of an incandescent light bulb (2.2%)
    static constexpr float EFFICIENCY_HALOGEN      = 0.0707f;   //!< Typical efficiency of an halogen light bulb (7.0%)
    static constexpr float EFFICIENCY_FLUORESCENT  = 0.0878f;   //!< Typical efficiency of a fluorescent light bulb (8.7%)
    static constexpr float EFFICIENCY_LED          = 0.1171f;   //!< Typical efficiency of a LED light bulb (11.7%)

    Type getType(Instance i) const noexcept;

    /**
     * Helper function that returns if a light is a directional light
     *
     * @param i     Instance of the component obtained from getInstance().
     * @return      true is this light is a type of directional light
     */
    inline bool isDirectional(Instance i) const noexcept {
        Type type = getType(i);
        return type == Type::DIRECTIONAL || type == Type::SUN;
    }

    /**
     * Helper function that returns if a light is a point light
     *
     * @param i     Instance of the component obtained from getInstance().
     * @return      true is this light is a type of point light
     */
    inline bool isPointLight(Instance i) const noexcept {
        return getType(i) == Type::POINT;
    }

    /**
     * Helper function that returns if a light is a spot light
     *
     * @param i     Instance of the component obtained from getInstance().
     * @return      true is this light is a type of spot light
     */
    inline bool isSpotLight(Instance i) const noexcept {
        Type type = getType(i);
        return type == Type::SPOT || type == Type::FOCUSED_SPOT;
    }

    /**
     * Dynamically updates the light's position.
     *
     * @param i        Instance of the component obtained from getInstance().
     * @param position Light's position in world space. The default is at the origin.
     *
     * @see Builder.position()
     */
    void setPosition(Instance i, const math::float3& position) noexcept;

    //! returns the light's position in world space
    const math::float3& getPosition(Instance i) const noexcept;

    /**
     * Dynamically updates the light's direction
     *
     * @param i         Instance of the component obtained from getInstance().
     * @param direction Light's direction in world space. Should be a unit vector.
     *                  The default is {0,-1,0}.
     *
     * @see Builder.direction()
     */
    void setDirection(Instance i, const math::float3& direction) noexcept;

    //! returns the light's direction in world space
    const math::float3& getDirection(Instance i) const noexcept;

    /**
     * Dynamically updates the light's hue as linear sRGB
     *
     * @param i     Instance of the component obtained from getInstance().
     * @param color Color of the light specified in the linear sRGB color-space.
     *              The default is white {1,1,1}.
     *
     * @see Builder.color(), getInstance()
     */
    void setColor(Instance i, const LinearColor& color) noexcept;

    /**
     * @param i     Instance of the component obtained from getInstance().
     * @return the light's color in linear sRGB
     */
    const math::float3& getColor(Instance i) const noexcept;

    /**
     * Dynamically updates the light's intensity. The intensity can be negative.
     *
     * @param i         Instance of the component obtained from getInstance().
     * @param intensity This parameter depends on the Light.Type:
     *                  - For directional lights, it specifies the illuminance in *lux*
     *                  (or *lumen/m^2*).
     *                  - For point lights and spot lights, it specifies the luminous power
     *                  in *lumen*.
     *
     * @see Builder.intensity()
     */
    void setIntensity(Instance i, float intensity) noexcept;

    /**
     * Dynamically updates the light's intensity. The intensity can be negative.
     *
     * @param i             Instance of the component obtained from getInstance().
     * @param watts         Energy consumed by a lightbulb. It is related to the energy produced
     *                      and ultimately the brightness by the \p efficiency parameter.
     *                      This value is often available on the packaging of commercial
     *                      lightbulbs.
     * @param efficiency    Efficiency in percent. This depends on the type of lightbulb used.
     *
     *  Lightbulb type  | Efficiency
     * ----------------:|-----------:
     *     Incandescent |  2.2%
     *         Halogen  |  7.0%
     *             LED  |  8.7%
     *     Fluorescent  | 10.7%
     *
     * @see Builder.intensity(float watts, float efficiency)
     */
    void setIntensity(Instance i, float watts, float efficiency) noexcept {
        setIntensity(i, watts * 683.0f * efficiency);
    }

    /**
     * Dynamically updates the light's intensity in candela. The intensity can be negative.
     *
     * @param i         Instance of the component obtained from getInstance().
     * @param intensity Luminous intensity in *candela*.
     *
     * @note
     * This method is equivalent to calling setIntensity(float intensity) for directional lights
     * (Type.DIRECTIONAL or Type.SUN).
     *
     * @see Builder.intensityCandela(float intensity)
     */
    void setIntensityCandela(Instance i, float intensity) noexcept;

    /**
     * returns the light's luminous intensity in candela.
     *
     * @param i     Instance of the component obtained from getInstance().
     *
     * @note for Type.FOCUSED_SPOT lights, the returned value depends on the \p outer cone angle.
     *
     * @return luminous intensity in candela.
     */
    float getIntensity(Instance i) const noexcept;

    /**
     * Set the falloff distance for point lights and spot lights.
     *
     * @param i      Instance of the component obtained from getInstance().
     * @param radius falloff distance in world units. Default is 1 meter.
     *
     * @see Builder.falloff()
     */
    void setFalloff(Instance i, float radius) noexcept;

    /**
     * returns the falloff distance of this light.
     * @param i     Instance of the component obtained from getInstance().
     * @return the falloff distance of this light.
     */
    float getFalloff(Instance i) const noexcept;

    /**
     * Dynamically updates a spot light's cone as angles
     *
     * @param i     Instance of the component obtained from getInstance().
     * @param inner inner cone angle in *radians* between 0 and pi/2
     * @param outer outer cone angle in *radians* between inner and pi/2
     *
     * @see Builder.spotLightCone()
     */
    void setSpotLightCone(Instance i, float inner, float outer) noexcept;

    float getSpotLightOuterCone(Instance i) const noexcept;

    /**
     * Dynamically updates the angular radius of a Type.SUN light
     *
     * The Sun as seen from Earth has an angular size of 0.526° to 0.545°
     *
     * @param i     Instance of the component obtained from getInstance().
     * @param angularRadius sun's radius in degrees. Default is 0.545°.
     */
    void setSunAngularRadius(Instance i, float angularRadius) noexcept;

    /**
     * returns the angular radius if the sun in degrees.
     * @param i     Instance of the component obtained from getInstance().
     * @return the angular radius if the sun in degrees.
     */
    float getSunAngularRadius(Instance i) const noexcept;

    /**
     * Dynamically updates the halo radius of a Type.SUN light. The radius
     * of the halo is defined as a multiplier of the sun angular radius.
     *
     * @param i     Instance of the component obtained from getInstance().
     * @param haloSize radius multiplier. Default is 10.0.
     */
    void setSunHaloSize(Instance i, float haloSize) noexcept;

    /**
     * returns the halo size of a Type.SUN light as a multiplier of the
     * sun angular radius.
     * @param i     Instance of the component obtained from getInstance().
     * @return the halo size
     */
    float getSunHaloSize(Instance i) const noexcept;

    /**
     * Dynamically updates the halo falloff of a Type.SUN light. The falloff
     * is a dimensionless number used as an exponent.
     *
     * @param i     Instance of the component obtained from getInstance().
     * @param haloFalloff halo falloff. Default is 80.0.
     */
    void setSunHaloFalloff(Instance i, float haloFalloff) noexcept;

    /**
     * returns the halo falloff of a Type.SUN light as a dimensionless value.
     * @param i     Instance of the component obtained from getInstance().
     * @return the halo falloff
     */
    float getSunHaloFalloff(Instance i) const noexcept;

    /**
     * returns the shadow-map options for a given light
     * @param i     Instance of the component obtained from getInstance().
     * @return      A ShadowOption structure
     */
    ShadowOptions const& getShadowOptions(Instance i) const noexcept;

    /**
     * sets the shadow-map options for a given light
     * @param i     Instance of the component obtained from getInstance().
     * @param options  A ShadowOption structure
     */
    void setShadowOptions(Instance i, ShadowOptions const& options) noexcept;

    /**
     * Whether this Light casts shadows (disabled by default)
     *
     * @param i     Instance of the component obtained from getInstance().
     * @param shadowCaster Enables or disables casting shadows from this Light.
     *
     * @warning
     * - Only a Type.DIRECTIONAL, Type.SUN, Type.SPOT, or Type.FOCUSED_SPOT light can cast shadows
     */
    void setShadowCaster(Instance i, bool shadowCaster) noexcept;

    /**
     * returns whether this light casts shadows.
     * @param i     Instance of the component obtained from getInstance().
     */
    bool isShadowCaster(Instance i) const noexcept;

    /**
     * Helper to process all components with a given function
     * @tparam F    a void(Entity entity, Instance instance)
     * @param func  a function of type F
     */
    template<typename F>
    void forEachComponent(F func) noexcept {
        utils::Entity const* const pEntity = getEntities();
        for (size_t i = 0, c = getComponentCount(); i < c; i++) {
            // Instance 0 is the invalid instance
            func(pEntity[i], Instance(i + 1));
        }
    }
};

} // namespace filament

#endif // TNT_FILAMENT_LIGHTMANAGER_H
