//
// Lol Engine - Sample math program: Chebyshev polynomials
//
// Copyright: (c) 2005-2011 Sam Hocevar <sam@hocevar.net>
//   This program is free software; you can redistribute it and/or
//   modify it under the terms of the Do What The Fuck You Want To
//   Public License, Version 2, as published by Sam Hocevar. See
//   http://sam.zoy.org/projects/COPYING.WTFPL for more details.
//

#if defined HAVE_CONFIG_H
#   include "config.h"
#endif

#include <cstring>

#include "core.h"

using namespace lol;

/* The order of the approximation we're looking for */
static int const ORDER = 3;

/* The function we want to approximate */
double myfun(double x)
{
    return exp(x);
}

real myfun(real const &x)
{
    return exp(x);
}

/* Naive matrix inversion */
template<int N> class Matrix
{
public:
    inline Matrix() {}

    Matrix(real x)
    {
        for (int j = 0; j < N; j++)
            for (int i = 0; i < N; i++)
                if (i == j)
                    m[i][j] = x;
                else
                    m[i][j] = 0;
    }

    Matrix<N> inv() const
    {
        Matrix a = *this, b(real(1.0));

        /* Inversion method: iterate through all columns and make sure
         * all the terms are zero except on the diagonal where it is one */
        for (int i = 0; i < N; i++)
        {
            /* If the expected coefficient is zero, add one of
             * the other lines. The first we meet will do. */
            if ((double)a.m[i][i] == 0.0)
            {
                for (int j = i + 1; j < N; j++)
                {
                    if ((double)a.m[i][j] == 0.0)
                        continue;
                    /* Add row j to row i */
                    for (int n = 0; n < N; n++)
                    {
                        a.m[n][i] += a.m[n][j];
                        b.m[n][i] += b.m[n][j];
                    }
                    break;
                }
            }

            /* Now we know the diagonal term is non-zero. Get its inverse
             * and use that to nullify all other terms in the column */
            real x = fres(a.m[i][i]);
            for (int j = 0; j < N; j++)
            {
                if (j == i)
                    continue;
                real mul = x * a.m[i][j];
                for (int n = 0; n < N; n++)
                {
                    a.m[n][j] -= mul * a.m[n][i];
                    b.m[n][j] -= mul * b.m[n][i];
                }
            }

            /* Finally, ensure the diagonal term is 1 */
            for (int n = 0; n < N; n++)
            {
                a.m[n][i] *= x;
                b.m[n][i] *= x;
            }
        }

        return b;
    }

    void print() const
    {
        for (int j = 0; j < N; j++)
        {
            for (int i = 0; i < N; i++)
                printf("%9.5f ", (double)m[j][i]);
            printf("\n");
        }
    }

    real m[N][N];
};


static int cheby[ORDER + 1][ORDER + 1];

/* Fill the Chebyshev tables */
static void make_table()
{
    memset(cheby, 0, sizeof(cheby));

    cheby[0][0] = 1;
    cheby[1][1] = 1;

    for (int i = 2; i < ORDER + 1; i++)
    {
        cheby[i][0] = -cheby[i - 2][0];
        for (int j = 1; j < ORDER + 1; j++)
            cheby[i][j] = 2 * cheby[i - 1][j - 1] - cheby[i - 2][j];
    }
}

int main(int argc, char **argv)
{
    make_table();

    /* We start with ORDER+1 points and their images through myfun() */
    real xn[ORDER + 1];
    real fxn[ORDER + 1];
    for (int i = 0; i < ORDER + 1; i++)
    {
        //xn[i] = real(2 * i - ORDER) / real(ORDER + 1);
        xn[i] = real(2 * i - ORDER + 1) / real(ORDER - 1);
        fxn[i] = myfun(xn[i]);
    }

    /* We build a matrix of Chebishev evaluations: one row per point
     * in our array, and column i is the evaluation of the ith order
     * polynomial. */
    Matrix<ORDER + 1> mat;
    for (int j = 0; j < ORDER + 1; j++)
    {
        /* Compute the powers of x_j */
        real powers[ORDER + 1];
        powers[0] = 1.0;
        for (int i = 1; i < ORDER + 1; i++)
             powers[i] = powers[i - 1] * xn[j];

        /* Compute the Chebishev evaluations at x_j */
        for (int i = 0; i < ORDER + 1; i++)
        {
            real sum = 0.0;
            for (int k = 0; k < ORDER + 1; k++)
                if (cheby[i][k])
                    sum += real(cheby[i][k]) * powers[k];
            mat.m[j][i] = sum;
        }
    }

    /* Invert the matrix and build interpolation coefficients */
    mat = mat.inv();
    real an[ORDER + 1];
    for (int j = 0; j < ORDER + 1; j++)
    {
        an[j] = 0;
        for (int i = 0; i < ORDER + 1; i++)
            an[j] += mat.m[j][i] * fxn[i];
        an[j].print(10);
    }

    return EXIT_SUCCESS;
}