/*!
    \file
    \copyright  (c) 2015 Jascha Wetzel. All rights reserved.
 */

#ifndef SHADER_API_H
#define SHADER_API_H

#include "api/base.h"
#include "api/client.h"
#include "api/container.h"
#include "api/string.h"

namespace tfd {

enum Interpolation {
    fast,
    smooth,
    sharp
};

namespace api {

struct SubGridDetail
{
    SubGridDetail() :
        velocity_displacement(0.),
        noise_intensity(0.),
        noise_smallest_size(0.01),
        noise_largest_size(0.01),
        noise_small_power(0.56),
        noise_period(1.)
    {}

    double velocity_displacement;
    double noise_intensity;
    double noise_smallest_size;
    double noise_largest_size;
    double noise_small_power;
    double noise_period;
};

/*!
    Combined fire and smoke shading parameters for use with tfd::Shader and tfd::TFDFluidContainer::get_shader_parameters.
    This class is supposed to be used like a value type.
    But it needs to handle the cross-API string buffers.
    Therefore, use the tfd::ShaderParameters wrapper to instantiate an object of this class.
 */
class ShaderParameters
{
public:
    bool use_empty_space_skipping;  //!< if true, precomputation will be done to enable Shader::skip_empty_space
    double sub_frame_time;  //!< fraction in [0,1). Used for motion-blur.
    double scene_time;      //!< seconds since start of render sequence. Used for noise continuity.
    double frame_per_second;

    double camera_step_size;
    double shadow_step_size;
    Interpolation interpolation;

    String* smoke_color_channel_name;
    float smoke_color_channel_smoothing;
    String* smoke_color_mapping;

    String* smoke_opacity_channel_name;
    float smoke_opacity_channel_smoothing;
    String* smoke_opacity_mapping;

    float smoke_thickness;
    String* smoke_color_gradient;

    SubGridDetail smoke_subgrid_detail;

    String* fire_color_channel_name;
    float fire_color_channel_smoothing;
    float clear_smoke_if_above;
    String* fire_color_mapping;

    String* fire_opacity_channel_name;
    float fire_opacity_channel_smoothing;
    String* fire_opacity_mapping;

    float fire_opacity;
    String* fire_color_gradient;

    SubGridDetail fire_subgrid_detail;

    double object_to_world_scale;   //!< Uniform scale of object space transform. Used for scale dependent shading parameters.

protected:
    ShaderParameters() :
        use_empty_space_skipping(false),
        sub_frame_time(0.),
        scene_time(0.),
        frame_per_second(24.),

        camera_step_size(0.5),
        shadow_step_size(0.5),
        interpolation(fast),

        smoke_color_channel_name(nullptr),
        smoke_color_channel_smoothing(0.f),
        smoke_color_mapping(nullptr),

        smoke_opacity_channel_name(nullptr),
        smoke_opacity_channel_smoothing(0.f),
        smoke_opacity_mapping(nullptr),

        smoke_thickness(1.f),
        smoke_color_gradient(nullptr),

        fire_color_channel_name(nullptr),
        fire_color_channel_smoothing(0.f),
        clear_smoke_if_above(1.f),
        fire_color_mapping(nullptr),

        fire_opacity_channel_name(nullptr),
        fire_opacity_channel_smoothing(0.f),
        fire_opacity_mapping(nullptr),

        fire_opacity(1.f),
        fire_color_gradient(nullptr),

        object_to_world_scale(1.)
    {}

private:
    ShaderParameters& operator=(const ShaderParameters&);
};

/*!
    Use tfd::Shader wrapper class instead of tfd::api::Shader interface.
 */
struct Shader : Base
{
    virtual void shade_combined(float x, float y, float z, float* rgbe) const = 0;

    virtual void skip_empty_space(
        double org_x,
        double org_y,
        double org_z,
        double dir_x,
        double dir_y,
        double dir_z,
        double& t_near,
        double& t_far
    ) const = 0;

    virtual void shade_fire(float x, float y, float z, float* rgbe) const = 0;
    virtual void shade_smoke(float x, float y, float z, float* rgbe) const = 0;
};

/*!
    Used by tfd::Shader wrapper class.
    Don't use directly.
 */
struct ShaderFactory : Base
{
    virtual Shader*
    create(ChannelContainer*, const ShaderParameters*) = 0;
};

} // namespace api

/*!

 */
class Shader
{
public:
    /*!

     */
    Shader(
        tfd::ChannelContainer& container,
        const api::ShaderParameters& params
    )
    {
        auto factory = api::create_shared_instance<api::ShaderFactory>();
        iface_ = api::wrap_raw_iface_pointer(
            factory->create(container, &params)
        );
    }

    bool valid() const {
        return bool(iface_);
    }

    operator api::Shader*() {
        return iface_.get();
    }

    /*!
        Combined fire and smoke shading.
        [x,y,z] are voxel-space coordinates.
        rgbe must point to a float[4] buffer that receives the radiance (rgb) and extinction (e) values.
        This function is thread-safe.
     */
    void shade_combined(float x, float y, float z, float* rgbe) const {
        iface_->shade_combined(x,y,z,rgbe);
    }

    void shade_fire(float x, float y, float z, float* rgbe) const {
        iface_->shade_fire(x,y,z,rgbe);
    }

    void shade_smoke(float x, float y, float z, float* rgbe) const {
        iface_->shade_smoke(x,y,z,rgbe);
    }

    /*!
        Trim ray parameters to minimize empty space along the ray.
        Shader must have been created with ShaderParameters::use_empty_space_skipping = true.
        Call may return with t_near >= t_far if no non-empty space is intersected by the ray.
        This function is thread-safe.
     */
    void skip_empty_space(
        double org_x,
        double org_y,
        double org_z,
        double dir_x,
        double dir_y,
        double dir_z,
        double& t_near,
        double& t_far
    ) const
    {
        iface_->skip_empty_space(
            org_x,
            org_y,
            org_z,
            dir_x,
            dir_y,
            dir_z,
            t_near,
            t_far
        );
    }

private:
    std::shared_ptr<api::Shader>
        iface_;
};

/*!

 */
class ShaderParameters : public api::ShaderParameters
{
public:
    ShaderParameters() {
        smoke_color_channel_name = &smoke_color_channel_name_buffer;
        smoke_color_mapping = &smoke_color_mapping_buffer;
        smoke_opacity_channel_name = &smoke_opacity_channel_name_buffer;
        smoke_opacity_mapping = &smoke_opacity_mapping_buffer;
        smoke_color_gradient = &smoke_color_gradient_buffer;

        fire_color_channel_name = &fire_color_channel_name_buffer;
        fire_color_mapping = &fire_color_mapping_buffer;
        fire_opacity_channel_name = &fire_opacity_channel_name_buffer;
        fire_opacity_mapping = &fire_opacity_mapping_buffer;
        fire_color_gradient = &fire_color_gradient_buffer;
    }

private:
    String smoke_color_channel_name_buffer;
    String smoke_color_mapping_buffer;
    String smoke_opacity_channel_name_buffer;
    String smoke_opacity_mapping_buffer;
    String smoke_color_gradient_buffer;

    String fire_color_channel_name_buffer;
    String fire_color_mapping_buffer;
    String fire_opacity_channel_name_buffer;
    String fire_opacity_mapping_buffer;
    String fire_color_gradient_buffer;
};

} // namespace tfd

#endif // SHADER_API_H
