/* brushlib - The MyPaint Brush Library
 * Copyright (C) 2007-2008 Martin Renold <martinxyz@gmx.ch>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#ifndef MAPPING_C
#define MAPPING_C

#include <stdlib.h>
#include <assert.h>

#include "mapping.h"

#include "helpers.h"

// user-defined mappings
// (the curves you can edit in the brush settings)

typedef struct {
  // a set of control points (stepwise linear)
  float xvalues[8];
  float yvalues[8];
  int n;
} ControlPoints;

struct _Mapping {
    float base_value; // FIXME: accessed directly from mypaint-brush.c

    int inputs;
    ControlPoints * pointsList; // one for each input
    int inputs_used; // optimization
};

Mapping * mapping_new(int inputs_)
{
    Mapping *self = (Mapping *)malloc(sizeof(Mapping));

    self->inputs = inputs_;
    self->pointsList = (ControlPoints *)malloc(sizeof(ControlPoints)*self->inputs);
    int i = 0;
    for (i=0; i<self->inputs; i++) self->pointsList[i].n = 0;

    self->inputs_used = 0;
    self->base_value = 0;

    return self;
}

void mapping_free(Mapping *self)
{
    free(self->pointsList);
    free(self);
}

float mapping_get_base_value(Mapping *self)
{
    return self->base_value;
}

void mapping_set_base_value(Mapping *self, float value)
{
    self->base_value = value;
}

void mapping_set_n (Mapping * self, int input, int n)
{
    assert (input >= 0 && input < self->inputs);
    assert (n >= 0 && n <= 8);
    assert (n != 1); // cannot build a linear mapping with only one point
    ControlPoints * p = self->pointsList + input;

    if (n != 0 && p->n == 0) self->inputs_used++;
    if (n == 0 && p->n != 0) self->inputs_used--;
    assert(self->inputs_used >= 0);
    assert(self->inputs_used <= self->inputs);

    p->n = n;
}


int mapping_get_n (Mapping * self, int input)
{
    assert (input >= 0 && input < self->inputs);
    ControlPoints * p = self->pointsList + input;
    return p->n;
}

void mapping_set_point (Mapping * self, int input, int index, float x, float y)
{
    assert (input >= 0 && input < self->inputs);
    assert (index >= 0 && index < 8);
    ControlPoints * p = self->pointsList + input;
    assert (index < p->n);

    if (index > 0) {
      assert (x >= p->xvalues[index-1]);
    }

    p->xvalues[index] = x;
    p->yvalues[index] = y;
}

void mapping_get_point (Mapping * self, int input, int index, float *x, float *y)
{
    assert (input >= 0 && input < self->inputs);
    assert (index >= 0 && index < 8);
    ControlPoints * p = self->pointsList + input;
    assert (index < p->n);

    *x = p->xvalues[index];
    *y = p->yvalues[index];
}

gboolean mapping_is_constant(Mapping * self)
{
    return self->inputs_used == 0;
}

int mapping_get_inputs_used_n(Mapping *self)
{
    return self->inputs_used;
}

float mapping_calculate (Mapping * self, float * data)
{
    int j;
    float result;
    result = self->base_value;

    // constant mapping (common case)
    if (self->inputs_used == 0) return result;

    for (j=0; j<self->inputs; j++) {
      ControlPoints * p = self->pointsList + j;

      if (p->n) {
        float x, y;
        x = data[j];

        // find the segment with the slope that we need to use
        float x0, y0, x1, y1;
        x0 = p->xvalues[0];
        y0 = p->yvalues[0];
        x1 = p->xvalues[1];
        y1 = p->yvalues[1];

        int i;
        for (i=2; i<p->n && x>x1; i++) {
          x0 = x1;
          y0 = y1;
          x1 = p->xvalues[i];
          y1 = p->yvalues[i];
        }

        if (x0 == x1) {
          y = y0;
        } else {
          // linear interpolation
          y = (y1*(x - x0) + y0*(x1 - x)) / (x1 - x0);
        }

        result += y;
      }
    }
    return result;
}

// used in python for the global pressure mapping
float mapping_calculate_single_input (Mapping * self, float input)
{
    assert(self->inputs == 1);
    return mapping_calculate(self, &input);
}
#endif //MAPPING_C
