/*
 * feSpecularLighting renderer
 *
 * Authors:
 *   Niko Kiirala <niko@kiirala.com>
 *   Jean-Rene Reinhard <jr@komite.net>
 *
 * Copyright (C) 2007 authors
 *
 * Released under GNU GPL, read the file 'COPYING' for more information
 */

#include <glib.h>
#include <cmath>

#include "display/nr-3dutils.h"
#include "display/nr-arena-item.h"
#include "display/nr-filter-specularlighting.h"
#include "display/nr-filter-getalpha.h"
#include "display/nr-filter-slot.h"
#include "display/nr-filter-units.h"
#include "display/nr-filter-utils.h"
#include "display/nr-light.h"
#include "libnr/nr-blit.h"
#include "libnr/nr-pixblock.h"
#include "libnr/nr-rect-l.h"
#include "color.h"

namespace Inkscape {
namespace Filters {

FilterSpecularLighting::FilterSpecularLighting()
{
    light_type = NO_LIGHT;
    specularConstant = 1;
    specularExponent = 1;
    surfaceScale = 1;
    lighting_color = 0xffffffff;
}

FilterPrimitive * FilterSpecularLighting::create() {
    return new FilterSpecularLighting();
}

FilterSpecularLighting::~FilterSpecularLighting()
{}

//Investigating Phong Lighting model we should not take N.H but
//R.E which equals to 2*N.H^2 - 1
//replace the second line by
//gdouble scal = scalar_product((N), (H)); scal = 2 * scal * scal - 1;
//to get the expected formula
#define COMPUTE_INTER(inter, H, N, ks, speculaExponent) \
do {\
    gdouble scal = NR::scalar_product((N), (H)); \
    if (scal <= 0)\
        (inter) = 0;\
    else\
        (inter) = (ks) * std::pow(scal, (specularExponent));\
}while(0)

int FilterSpecularLighting::render(FilterSlot &slot, FilterUnits const &units) {
    NRPixBlock *in = slot.get(_input);
    if (!in) {
        g_warning("Missing source image for feSpecularLighting (in=%d)", _input);
        return 1;
    }

    NRPixBlock *out = new NRPixBlock;

    //Fvector *L = NULL; //vector to the light

    int w = in->area.x1 - in->area.x0;
    int h = in->area.y1 - in->area.y0;
    int x0 = in->area.x0;
    int y0 = in->area.y0;
    int i, j;
    //As long as FilterRes and kernel unit is not supported we hardcode the
    //default value
    int dx = 1; //TODO setup
    int dy = 1; //TODO setup
    //surface scale
    Geom::Matrix trans = units.get_matrix_primitiveunits2pb();
    gdouble ss = surfaceScale * trans[0];
    gdouble ks = specularConstant; //diffuse lighting constant
    NR::Fvector L, N, LC, H;
    gdouble inter;

    nr_pixblock_setup_fast(out, NR_PIXBLOCK_MODE_R8G8B8A8N,
            in->area.x0, in->area.y0, in->area.x1, in->area.y1,
            true);
    unsigned char *data_i = NR_PIXBLOCK_PX (in);
    unsigned char *data_o = NR_PIXBLOCK_PX (out);
    //No light, nothing to do
    switch (light_type) {
        case DISTANT_LIGHT:
            //the light vector is constant
            {
            DistantLight *dl = new DistantLight(light.distant, lighting_color);
            dl->light_vector(L);
            dl->light_components(LC);
            NR::normalized_sum(H, L, NR::EYE_VECTOR);
            //finish the work
            for (i = 0, j = 0; i < w*h; i++) {
                NR::compute_surface_normal(N, ss, in, i / w, i % w, dx, dy);
                COMPUTE_INTER(inter, N, H, ks, specularExponent);

                data_o[j++] = CLAMP_D_TO_U8(inter * LC[LIGHT_RED]); // CLAMP includes rounding!
                data_o[j++] = CLAMP_D_TO_U8(inter * LC[LIGHT_GREEN]);
                data_o[j++] = CLAMP_D_TO_U8(inter * LC[LIGHT_BLUE]);
                data_o[j] = MAX(MAX(data_o[j-3], data_o[j-2]), data_o[j-1]);
                ++j;
            }
            out->empty = FALSE;
            delete dl;
            }
            break;
        case POINT_LIGHT:
            {
            PointLight *pl = new PointLight(light.point, lighting_color, trans);
            pl->light_components(LC);
        //TODO we need a reference to the filter to determine primitiveUnits
        //if objectBoundingBox is used, use a different matrix for light_vector
        // UPDATE: trans is now correct matrix from primitiveUnits to
        // pixblock coordinates
            //finish the work
            for (i = 0, j = 0; i < w*h; i++) {
                NR::compute_surface_normal(N, ss, in, i / w, i % w, dx, dy);
                pl->light_vector(L,
                        i % w + x0,
                        i / w + y0,
                        ss * (double) data_i[4*i+3]/ 255);
                NR::normalized_sum(H, L, NR::EYE_VECTOR);
                COMPUTE_INTER(inter, N, H, ks, specularExponent);

                data_o[j++] = CLAMP_D_TO_U8(inter * LC[LIGHT_RED]);
                data_o[j++] = CLAMP_D_TO_U8(inter * LC[LIGHT_GREEN]);
                data_o[j++] = CLAMP_D_TO_U8(inter * LC[LIGHT_BLUE]);
                data_o[j] = MAX(MAX(data_o[j-3], data_o[j-2]), data_o[j-1]);
                ++j;
            }
            out->empty = FALSE;
            delete pl;
            }
            break;
        case SPOT_LIGHT:
            {
            SpotLight *sl = new SpotLight(light.spot, lighting_color, trans);
        //TODO we need a reference to the filter to determine primitiveUnits
        //if objectBoundingBox is used, use a different matrix for light_vector
        // UPDATE: trans is now correct matrix from primitiveUnits to
        // pixblock coordinates
            //finish the work
            for (i = 0, j = 0; i < w*h; i++) {
                NR::compute_surface_normal(N, ss, in, i / w, i % w, dx, dy);
                sl->light_vector(L,
                    i % w + x0,
                    i / w + y0,
                    ss * (double) data_i[4*i+3]/ 255);
                sl->light_components(LC, L);
                NR::normalized_sum(H, L, NR::EYE_VECTOR);
                COMPUTE_INTER(inter, N, H, ks, specularExponent);

                data_o[j++] = CLAMP_D_TO_U8(inter * LC[LIGHT_RED]);
                data_o[j++] = CLAMP_D_TO_U8(inter * LC[LIGHT_GREEN]);
                data_o[j++] = CLAMP_D_TO_U8(inter * LC[LIGHT_BLUE]);
                data_o[j] = MAX(MAX(data_o[j-3], data_o[j-2]), data_o[j-1]);
                ++j;
            }
            out->empty = FALSE;
            delete sl;
            }
            break;
        //else unknown light source, doing nothing
        case NO_LIGHT:
        default:
            {
            if (light_type != NO_LIGHT)
                g_warning("unknown light source %d", light_type);
            out->empty = false;
            }
    }

    //finishing
    slot.set(_output, out);
    //nr_pixblock_release(in);
    //delete in;
    return 0;
}

void FilterSpecularLighting::area_enlarge(NRRectL &area, Geom::Matrix const &trans)
{
    // TODO: support kernelUnitLength
    double scalex = std::fabs(trans[0]) + std::fabs(trans[1]);
    double scaley = std::fabs(trans[2]) + std::fabs(trans[3]);

    //FIXME: no +2 should be there!... (noticable only for big scales at big zoom factor)
    area.x0 -= (int)(scalex) + 2;
    area.x1 += (int)(scalex) + 2;
    area.y0 -= (int)(scaley) + 2;
    area.y1 += (int)(scaley) + 2;
}

FilterTraits FilterSpecularLighting::get_input_traits() {
    return TRAIT_PARALLER;
}

} /* namespace Filters */
} /* namespace Inkscape */

/*
  Local Variables:
  mode:c++
  c-file-style:"stroustrup"
  c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
  indent-tabs-mode:nil
  fill-column:99
  End:
*/
// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
