#include "shaders.hpp"
#include "stats.hpp"


DepthFog::DepthFog(const Img::rgb_t& c, coord_t s, unsigned d)
    : col(c), start(s), sqstart(s*s), density(1.0/(double)d) {
}


void DepthFog::get(const Scene*, ShaderInfo::shader_info_t& info, Img::rgb_t& c) const {
    // compute the two intersection points with this box and take its len
    // TODO: per default acc. to DOV?
    // http://blog.demofox.org/2014/06/22/analytic-fog-density/
    // http://computergraphics.stackexchange.com/questions/227/how-are-volumetric-effects-handled-in-raytracing
    // https://renderman.pixar.com/view/fog-and-atmospheric-effects
    // http://www.cs.rpi.edu/~cutler/classes/advancedgraphics/S07/final_projects/fischc/fog_simulation.html
    #if 1
        coord_t len = info.ray.direction.sqlen();
        if (len <= sqstart) {
            return;
        }
        len = GETORSET(info.depth, sqrt(len)) - start;
    #else
        coord_t len = ray.direction.max() - start;
        if (len <= 0.0) {
            return;
        }
    #endif
    float alpha = len*density;
    alpha = MIN(1.0, alpha); // 1: only fog
    // + some random value for cloudiness?

    // TODO: config.img_lin_avg.d
    c.r += (col.r - c.r) * alpha;
    c.g += (col.g - c.g) * alpha;
    c.b += (col.b - c.b) * alpha;
}


VolumetricLight::VolumetricLight(const vertex_t p, unsigned brightness, unsigned len)
    : pos(p), intensity(MIN((double)brightness,100.0)/100.0), maxlen(len), maxsqlen(len*len), samples_max((2.0*maxlen)/stepsize) {
}


const coord_t VolumetricLight::stepsize = 50.0;


void VolumetricLight::get(const Scene* scene, ShaderInfo::shader_info_t& info, Img::rgb_t& c) const {
    // TODO: blur/skip/interpolate/noise?
    // volumetric effects, i.e. light rays? participating media?
    // precomputed lightmap for voxels?
    // config.img_lin_avg.d
    // http://www.alexandre-pestana.com/volumetric-lights/

    const coord_t raylen = GETORSET(info.depth, info.ray.direction.len()); // TODO: can move first sample pos to length boundary (as camera should be far away)
    const int samples = raylen / stepsize; // TODO: round down? we start with 1 below, too
    const vertex_t dir = info.ray.direction / raylen; // unit vector to travel along

    ray_t lray(pos, vertex_t(0, 0, 0)); // cast from light to current sample
    float l = 0.0;
    for (int i=1; i<=samples; ++i) {
        coord_t sample_len = ((coord_t)i) * stepsize;
        sample_len -= (((coord_t)rand())/((coord_t)RAND_MAX)) * 0.5 * stepsize; // not too far beyond clipping

        lray.direction = info.ray.origin + (dir * sample_len);
        if (lray.direction.min() < 0.0) {
            continue; // lightrays must not clip scene
        }
        lray.direction -= lray.origin;

        coord_t rlen = lray.direction.sqlen();
        if (rlen >= maxsqlen) {
            continue;
        }
        if (scene->intersect(lray)) {
            continue;
        }

        l += (maxsqlen-rlen)/maxsqlen; // TODO: account for angle? use non-squared len?
    }
    l /= (float)samples_max; // average light throughout all samples on the ray
    l *= intensity;

    c.r += (255 - c.r) * l;
    c.g += (255 - c.g) * l;
    c.b += (255 - c.b) * l;
}


DepthBlur::DepthBlur(unsigned mmin, unsigned mmax)
    : mind(mmin), maxd(mmax), diffd(mmax-mmin) {
}


void DepthBlur::get(const Scene*, ShaderInfo& shader_info, Img& out) const {
    for (unsigned y=0; y<out.h-1; ++y) {
        for (unsigned x=0; x<out.w-1; ++x) {
            Img::rgb_t& p = out.at(x, y);
            ShaderInfo::shader_info_t& i = shader_info.at(x, y);
            float blur = 1.0 - ((GETORSET(i.depth, i.ray.direction.len()) <= mind)? 0.0: ((i.depth >= maxd)? 1.0: ((i.depth - mind) / diffd))); // or hitpoint len?

            // depth-map, actually
            // FIXME: blur according to average surroundings, if its the same object or depth. Otherwise this could be simply a pixel shader or light source.
            p.r *= blur;
            p.g *= blur;
            p.b *= blur;
        }
    }
}