/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim: set sw=4 sts=4 expandtab: */
/*
   rsvg-filter.c: Provides filters

   Copyright (C) 2004 Caleb Moore

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public License as
   published by the Free Software Foundation; either version 2 of the
   License, or (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public
   License along with this program; if not, write to the
   Free Software Foundation, Inc., 59 Temple Place - Suite 330,
   Boston, MA 02111-1307, USA.

   Author: Caleb Moore <c.moore@student.unsw.edu.au>
*/

#include "config.h"

#include "../rsvg-private.h"
#include "../rsvg-styles.h"
#include "../rsvg-css.h"
#include "common.h"
#include "light_source.h"

typedef struct _FactorAndMatrix FactorAndMatrix;

struct _FactorAndMatrix {
    gint matrix[9];
    gdouble factor;
};

gdouble
norm (vector3 A)
{
    return sqrt (A.x * A.x + A.y * A.y + A.z * A.z);
}

gdouble
dotproduct (vector3 A, vector3 B)
{
    return A.x * B.x + A.y * B.y + A.z * B.z;
}

vector3
normalise (vector3 A)
{
    double divisor;
    divisor = norm (A);

    A.x /= divisor;
    A.y /= divisor;
    A.z /= divisor;

    return A;
}

static FactorAndMatrix
get_light_normal_matrix_x (gint n)
{
    static const FactorAndMatrix matrix_list[] = {
        {
         {0, 0, 0,
          0, -2, 2,
          0, -1, 1},
         2.0 / 3.0},
        {
         {0, 0, 0,
          -2, 0, 2,
          -1, 0, 1},
         1.0 / 3.0},
        {
         {0, 0, 0,
          -2, 2, 0,
          -1, 1, 0},
         2.0 / 3.0},
        {
         {0, -1, 1,
          0, -2, 2,
          0, -1, 1},
         1.0 / 2.0},
        {
         {-1, 0, 1,
          -2, 0, 2,
          -1, 0, 1},
         1.0 / 4.0},
        {
         {-1, 1, 0,
          -2, 2, 0,
          -1, 1, 0},
         1.0 / 2.0},
        {
         {0, -1, 1,
          0, -2, 2,
          0, 0, 0},
         2.0 / 3.0},
        {
         {-1, 0, 1,
          -2, 0, 2,
          0, 0, 0},
         1.0 / 3.0},
        {
         {-1, 1, 0,
          -2, 2, 0,
          0, 0, 0},
         2.0 / 3.0}
    };

    return matrix_list[n];
}

static FactorAndMatrix
get_light_normal_matrix_y (gint n)
{
    static const FactorAndMatrix matrix_list[] = {
        {
         {0, 0, 0,
          0, -2, -1,
          0, 2, 1},
         2.0 / 3.0},
        {
         {0, 0, 0,
          -1, -2, -1,
          1, 2, 1},
         1.0 / 3.0},
        {
         {0, 0, 0,
          -1, -2, 0,
          1, 2, 0},
         2.0 / 3.0},
        {

         {0, -2, -1,
          0, 0, 0,
          0, 2, 1},
         1.0 / 2.0},
        {
         {-1, -2, -1,
          0, 0, 0,
          1, 2, 1},
         1.0 / 4.0},
        {
         {-1, -2, 0,
          0, 0, 0,
          1, 2, 0},
         1.0 / 2.0},
        {

         {0, -2, -1,
          0, 2, 1,
          0, 0, 0},
         2.0 / 3.0},
        {
         {0, -2, -1,
          1, 2, 1,
          0, 0, 0},
         1.0 / 3.0},
        {
         {-1, -2, 0,
          1, 2, 0,
          0, 0, 0},
         2.0 / 3.0}
    };

    return matrix_list[n];
}

vector3
get_surface_normal (guchar * I, RsvgIRect boundarys, gint x, gint y,
                    gdouble dx, gdouble dy, gdouble rawdx, gdouble rawdy, gdouble surfaceScale,
                    gint rowstride, int chan)
{
    gint mrow, mcol;
    FactorAndMatrix fnmx, fnmy;
    gint *Kx, *Ky;
    gdouble factorx, factory;
    gdouble Nx, Ny;
    vector3 output;

    if (x + dx >= boundarys.x1 - 1)
        mcol = 2;
    else if (x - dx < boundarys.x0 + 1)
        mcol = 0;
    else
        mcol = 1;

    if (y + dy >= boundarys.y1 - 1)
        mrow = 2;
    else if (y - dy < boundarys.y0 + 1)
        mrow = 0;
    else
        mrow = 1;

    fnmx = get_light_normal_matrix_x (mrow * 3 + mcol);
    factorx = fnmx.factor / rawdx;
    Kx = fnmx.matrix;

    fnmy = get_light_normal_matrix_y (mrow * 3 + mcol);
    factory = fnmy.factor / rawdy;
    Ky = fnmy.matrix;

    Nx = -surfaceScale * factorx * ((gdouble)
                                    (Kx[0] *
                                     get_interp_pixel (I, x - dx, y - dy, chan,
                                                                  boundarys,
                                                                  rowstride) +
                                     Kx[1] * get_interp_pixel (I, x, y - dy, chan,
                                                                          boundarys,
                                                                          rowstride) +
                                     Kx[2] * get_interp_pixel (I, x + dx, y - dy, chan,
                                                                          boundarys,
                                                                          rowstride) +
                                     Kx[3] * get_interp_pixel (I, x - dx, y, chan,
                                                                          boundarys,
                                                                          rowstride) +
                                     Kx[4] * get_interp_pixel (I, x, y, chan, boundarys,
                                                                          rowstride) +
                                     Kx[5] * get_interp_pixel (I, x + dx, y, chan,
                                                                          boundarys,
                                                                          rowstride) +
                                     Kx[6] * get_interp_pixel (I, x - dx, y + dy, chan,
                                                                          boundarys,
                                                                          rowstride) +
                                     Kx[7] * get_interp_pixel (I, x, y + dy, chan,
                                                                          boundarys,
                                                                          rowstride) +
                                     Kx[8] * get_interp_pixel (I, x + dx, y + dy, chan,
                                                                          boundarys,
                                                                          rowstride))) / 255.0;

    Ny = -surfaceScale * factory * ((gdouble)
                                    (Ky[0] *
                                     get_interp_pixel (I, x - dx, y - dy, chan,
                                                                  boundarys,
                                                                  rowstride) +
                                     Ky[1] * get_interp_pixel (I, x, y - dy, chan,
                                                                          boundarys,
                                                                          rowstride) +
                                     Ky[2] * get_interp_pixel (I, x + dx, y - dy, chan,
                                                                          boundarys,
                                                                          rowstride) +
                                     Ky[3] * get_interp_pixel (I, x - dx, y, chan,
                                                                          boundarys,
                                                                          rowstride) +
                                     Ky[4] * get_interp_pixel (I, x, y, chan, boundarys,
                                                                          rowstride) +
                                     Ky[5] * get_interp_pixel (I, x + dx, y, chan,
                                                                          boundarys,
                                                                          rowstride) +
                                     Ky[6] * get_interp_pixel (I, x - dx, y + dy, chan,
                                                                          boundarys,
                                                                          rowstride) +
                                     Ky[7] * get_interp_pixel (I, x, y + dy, chan,
                                                                          boundarys,
                                                                          rowstride) +
                                     Ky[8] * get_interp_pixel (I, x + dx, y + dy, chan,
                                                                          boundarys,
                                                                          rowstride))) / 255.0;

    output.x = Nx;
    output.y = Ny;

    output.z = 1;
    output = normalise (output);
    return output;
}

vector3
get_light_direction (RsvgComputedValues *values,
                     RsvgNodeLightSource * source, gdouble x1, gdouble y1, gdouble z,
                     cairo_matrix_t *affine, RsvgDrawingCtx * ctx)
{
    vector3 output;

    switch (source->type) {
    case DISTANTLIGHT:
        output.x = cos (source->azimuth) * cos (source->elevation);
        output.y = sin (source->azimuth) * cos (source->elevation);
        output.z = sin (source->elevation);
        break;
    default:
        {
            double x, y;
            x = affine->xx * x1 + affine->xy * y1 + affine->x0;
            y = affine->yx * x1 + affine->yy * y1 + affine->y0;
            output.x = rsvg_length_normalize (&source->x, values, ctx) - x;
            output.y = rsvg_length_normalize (&source->y, values, ctx) - y;
            output.z = rsvg_length_normalize (&source->z, values, ctx) - z;
            output = normalise (output);
        }
        break;
    }
    return output;
}

vector3
get_light_color (RsvgComputedValues *values,
                 RsvgNodeLightSource * source, vector3 color,
                 gdouble x1, gdouble y1, gdouble z, cairo_matrix_t *affine, RsvgDrawingCtx * ctx)
{
    double base, angle, x, y;
    vector3 s;
    vector3 L;
    vector3 output;
    double sx, sy, sz, spx, spy, spz;

    if (source->type != SPOTLIGHT)
        return color;

    sx = rsvg_length_normalize (&source->x, values, ctx);
    sy = rsvg_length_normalize (&source->y, values, ctx);
    sz = rsvg_length_normalize (&source->z, values, ctx);
    spx = rsvg_length_normalize (&source->pointsAtX, values, ctx);
    spy = rsvg_length_normalize (&source->pointsAtY, values, ctx);
    spz = rsvg_length_normalize (&source->pointsAtZ, values, ctx);

    x = affine->xx * x1 + affine->xy * y1 + affine->x0;
    y = affine->yx * x1 + affine->yy * y1 + affine->y0;

    L.x = sx - x;
    L.y = sy - y;
    L.z = sz - z;
    L = normalise (L);

    s.x = spx - sx;
    s.y = spy - sy;
    s.z = spz - sz;
    s = normalise (s);

    base = -dotproduct (L, s);

    angle = acos (base);

    if (base < 0 || angle > source->limitingconeAngle) {
        output.x = 0;
        output.y = 0;
        output.z = 0;
        return output;
    }

    output.x = color.x * pow (base, source->specularExponent);
    output.y = color.y * pow (base, source->specularExponent);
    output.z = color.z * pow (base, source->specularExponent);

    return output;
}

void
rsvg_node_light_source_set_atts (RsvgNode *node, gpointer impl, RsvgHandle *handle, RsvgPropertyBag atts)
{
    RsvgNodeLightSource *data = impl;
    RsvgPropertyBagIter *iter;
    const char *key;
    RsvgAttribute attr;
    const char *value;

    iter = rsvg_property_bag_iter_begin (atts);

    while (rsvg_property_bag_iter_next (iter, &key, &attr, &value)) {
        switch (attr) {
        case RSVG_ATTRIBUTE_AZIMUTH:
            data->azimuth = g_ascii_strtod (value, NULL) / 180.0 * M_PI;
            break;

        case RSVG_ATTRIBUTE_ELEVATION:
            data->elevation = g_ascii_strtod (value, NULL) / 180.0 * M_PI;
            break;

        case RSVG_ATTRIBUTE_LIMITING_CONE_ANGLE:
            data->limitingconeAngle = g_ascii_strtod (value, NULL) / 180.0 * M_PI;
            break;

        case RSVG_ATTRIBUTE_X:
            data->x = data->pointsAtX = rsvg_length_parse (value, LENGTH_DIR_HORIZONTAL);
            break;

        case RSVG_ATTRIBUTE_Y:
            data->y = data->pointsAtX = rsvg_length_parse (value, LENGTH_DIR_VERTICAL);
            break;

        case RSVG_ATTRIBUTE_Z:
            data->z = data->pointsAtX = rsvg_length_parse (value, LENGTH_DIR_BOTH);
            break;

        case RSVG_ATTRIBUTE_POINTS_AT_X:
            data->pointsAtX = rsvg_length_parse (value, LENGTH_DIR_HORIZONTAL);
            break;

        case RSVG_ATTRIBUTE_POINTS_AT_Y:
            data->pointsAtY = rsvg_length_parse (value, LENGTH_DIR_VERTICAL);
            break;

        case RSVG_ATTRIBUTE_POINTS_AT_Z:
            data->pointsAtZ = rsvg_length_parse (value, LENGTH_DIR_BOTH);
            break;

        case RSVG_ATTRIBUTE_SPECULAR_EXPONENT:
            data->specularExponent = g_ascii_strtod (value, NULL);
            break;

        default:
            break;
        }
    }

    rsvg_property_bag_iter_end (iter);
}

RsvgNodeLightSource *
find_light_source_in_children (RsvgNode *node)
{
    RsvgNodeChildrenIter *iter;
    RsvgNode *child;
    RsvgNodeLightSource *source;

    iter = rsvg_node_children_iter_begin (node);

    while (rsvg_node_children_iter_next_back (iter, &child)) {
        if (rsvg_node_get_type (child) == RSVG_NODE_TYPE_LIGHT_SOURCE) {
            break;
        }

        child = rsvg_node_unref (child);
    }

    rsvg_node_children_iter_end (iter);

    if (child == NULL)
        return NULL;

    g_assert (rsvg_node_get_type (child) == RSVG_NODE_TYPE_LIGHT_SOURCE);

    source = rsvg_rust_cnode_get_impl (child);
    child = rsvg_node_unref (child);

    return source;
}

RsvgNode *
rsvg_new_node_light_source (const char *element_name, RsvgNode *parent, const char *id, const char *klass)
{
    RsvgNodeLightSource *data;

    data = g_new0 (RsvgNodeLightSource, 1);

    data->specularExponent = 1;

    if (strcmp (element_name, "feDistantLight") == 0)
        data->type = DISTANTLIGHT;
    else if (strcmp (element_name, "feSpotLight") == 0)
        data->type = SPOTLIGHT;
    else if (strcmp (element_name, "fePointLight") == 0)
        data->type = POINTLIGHT;
    else
        g_assert_not_reached ();

    data->limitingconeAngle = 180;

    return rsvg_rust_cnode_new (RSVG_NODE_TYPE_LIGHT_SOURCE,
                                parent,
                                id,
                                klass,
                                data,
                                rsvg_node_light_source_set_atts,
                                g_free);
}
