//      Grid data demo
//
// Copyright (C) 2004  Joao Cardoso
//
// This file is part of PLplot.
//
// PLplot 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.
//
// PLplot 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 PLplot; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
//

#include "plcdemos.h"

// Options data structure definition.

static PLINT         pts       = 500;
static PLINT         xp        = 25;
static PLINT         yp        = 20;
static PLINT         nl        = 16;
static int           knn_order = 20;
static PLFLT         threshold = 1.001;
static PLFLT         wmin      = -1e3;
static int           randn     = 0;
static int           rosen     = 0;

static PLOptionTable options[] = {
    {
        "npts",
        NULL,
        NULL,
        &pts,
        PL_OPT_INT,
        "-npts points",
        "Specify number of random points to generate [500]"
    },
    {
        "randn",
        NULL,
        NULL,
        &randn,
        PL_OPT_BOOL,
        "-randn",
        "Normal instead of uniform sampling -- the effective \n\
\t\t\t  number of points will be smaller than the specified."
    },
    {
        "rosen",
        NULL,
        NULL,
        &rosen,
        PL_OPT_BOOL,
        "-rosen",
        "Generate points from the Rosenbrock function."
    },
    {
        "nx",
        NULL,
        NULL,
        &xp,
        PL_OPT_INT,
        "-nx points",
        "Specify grid x dimension [25]"
    },
    {
        "ny",
        NULL,
        NULL,
        &yp,
        PL_OPT_INT,
        "-ny points",
        "Specify grid y dimension [20]"
    },
    {
        "nlevel",
        NULL,
        NULL,
        &nl,
        PL_OPT_INT,
        "-nlevel ",
        "Specify number of contour levels [15]"
    },
    {
        "knn_order",
        NULL,
        NULL,
        &knn_order,
        PL_OPT_INT,
        "-knn_order order",
        "Specify the number of neighbors [20]"
    },
    {
        "threshold",
        NULL,
        NULL,
        &threshold,
        PL_OPT_FLOAT,
        "-threshold float",
        "Specify what a thin triangle is [1. < [1.001] < 2.]"
    },

    {
        NULL,                   // option
        NULL,                   // handler
        NULL,                   // client data
        NULL,                   // address of variable to set
        0,                      // mode flag
        NULL,                   // short syntax
        NULL
    }                           // long syntax
};

void create_data( PLFLT **xi, PLFLT **yi, PLFLT **zi, int npts );
void free_data( PLFLT *x, PLFLT *y, PLFLT *z );
void create_grid( PLFLT **xi, int px, PLFLT **yi, int py );
void free_grid( PLFLT *x, PLFLT *y );

static void
cmap1_init( void )
{
    PLFLT i[2], h[2], l[2], s[2];

    i[0] = 0.0;         // left boundary
    i[1] = 1.0;         // right boundary

    h[0] = 240;         // blue -> green -> yellow ->
    h[1] = 0;           // -> red

    l[0] = 0.6;
    l[1] = 0.6;

    s[0] = 0.8;
    s[1] = 0.8;

    plscmap1n( 256 );
    c_plscmap1l( 0, 2, i, h, l, s, NULL );
}

PLFLT xm, xM, ym, yM;

int
main( int argc, const char *argv[] )
{
    PLFLT      *x, *y, *z, *clev;
    PLFLT      *xg, *yg, **zg;
    PLFLT      zmin, zmax, lzm, lzM;
    int        i, j, k;
    PLINT      alg;
    const char *title[] = { "Cubic Spline Approximation",
                            "Delaunay Linear Interpolation",
                            "Natural Neighbors Interpolation",
                            "KNN Inv. Distance Weighted",
                            "3NN Linear Interpolation",
                            "4NN Around Inv. Dist. Weighted" };

    PLFLT      opt[] = { 0., 0., 0., 0., 0., 0. };

    xm = ym = -0.2;
    xM = yM = 0.6;

    plMergeOpts( options, "x21c options", NULL );
    plparseopts( &argc, argv, PL_PARSE_FULL );

    opt[2] = wmin;
    opt[3] = (PLFLT) knn_order;
    opt[4] = threshold;

    // Initialize plplot

    plinit();

    // Use a colour map with no black band in the middle.
    cmap1_init();
    // Initialise random number generator
    plseed( 5489 );

    create_data( &x, &y, &z, pts ); // the sampled data
    zmin = z[0];
    zmax = z[0];
    for ( i = 1; i < pts; i++ )
    {
        if ( z[i] > zmax )
            zmax = z[i];
        if ( z[i] < zmin )
            zmin = z[i];
    }

    create_grid( &xg, xp, &yg, yp ); // grid the data at
    plAlloc2dGrid( &zg, xp, yp );    // the output grided data
    clev = (PLFLT *) malloc( (size_t) nl * sizeof ( PLFLT ) );

    plcol0( 1 );
    plenv( xm, xM, ym, yM, 2, 0 );
    plcol0( 15 );
    pllab( "X", "Y", "The original data sampling" );
    for ( i = 0; i < pts; i++ )
    {
        plcol1( ( z[i] - zmin ) / ( zmax - zmin ) );
        // The following plstring call should be the the equivalent of
        // plpoin( 1, &x[i], &y[i], 5 ); Use plstring because it is
        // not deprecated like plpoin and has much more powerful
        // capabilities.  N.B. symbol 141 works for Hershey devices
        // (e.g., -dev xwin) only if plfontld( 0 ) has been called
        // while symbol 727 works only if plfontld( 1 ) has been
        // called.  The latter is the default which is why we use 727
        // here to represent a centred X (multiplication) symbol.
        // This dependence on plfontld is one of the limitations of
        // the Hershey escapes for PLplot, but the upside is you get
        // reasonable results for both Hershey and Unicode devices.
        plstring( 1, &x[i], &y[i], "#(727)" );
    }
    pladv( 0 );

    plssub( 3, 2 );

    for ( k = 0; k < 2; k++ )
    {
        pladv( 0 );
        for ( alg = 1; alg < 7; alg++ )
        {
            plgriddata( x, y, z, pts, xg, xp, yg, yp, zg, alg, opt[alg - 1] );

            // - CSA can generate NaNs (only interpolates?!).
            // - DTLI and NNI can generate NaNs for points outside the convex hull
            //      of the data points.
            // - NNLI can generate NaNs if a sufficiently thick triangle is not found
            //
            // PLplot should be NaN/Inf aware, but changing it now is quite a job...
            // so, instead of not plotting the NaN regions, a weighted average over
            // the neighbors is done.
            //

            if ( alg == GRID_CSA || alg == GRID_DTLI || alg == GRID_NNLI || alg == GRID_NNI )
            {
                int   ii, jj;
                PLFLT dist, d;

                for ( i = 0; i < xp; i++ )
                {
                    for ( j = 0; j < yp; j++ )
                    {
                        if ( isnan( zg[i][j] ) ) // average (IDW) over the 8 neighbors

                        {
                            zg[i][j] = 0.; dist = 0.;

                            for ( ii = i - 1; ii <= i + 1 && ii < xp; ii++ )
                            {
                                for ( jj = j - 1; jj <= j + 1 && jj < yp; jj++ )
                                {
                                    if ( ii >= 0 && jj >= 0 && !isnan( zg[ii][jj] ) )
                                    {
                                        d         = ( abs( ii - i ) + abs( jj - j ) ) == 1 ? 1. : 1.4142;
                                        zg[i][j] += zg[ii][jj] / ( d * d );
                                        dist     += d;
                                    }
                                }
                            }
                            if ( dist != 0. )
                                zg[i][j] /= dist;
                            else
                                zg[i][j] = zmin;
                        }
                    }
                }
            }

            plMinMax2dGrid( (const PLFLT * const *) zg, xp, yp, &lzM, &lzm );

            lzm = MIN( lzm, zmin );
            lzM = MAX( lzM, zmax );

            // Increase limits slightly to prevent spurious contours
            // due to rounding errors
            lzm = lzm - 0.01;
            lzM = lzM + 0.01;

            plcol0( 1 );

            pladv( alg );

            if ( k == 0 )
            {
                for ( i = 0; i < nl; i++ )
                    clev[i] = lzm + ( lzM - lzm ) / ( nl - 1 ) * i;

                plenv0( xm, xM, ym, yM, 2, 0 );
                plcol0( 15 );
                pllab( "X", "Y", title[alg - 1] );
                plshades( (const PLFLT * const *) zg, xp, yp, NULL, xm, xM, ym, yM,
                    clev, nl, 1., 0, 1., plfill, 1, NULL, NULL );
                plcol0( 2 );
            }
            else
            {
                for ( i = 0; i < nl; i++ )
                    clev[i] = lzm + ( lzM - lzm ) / ( nl - 1 ) * i;

                plvpor( 0.0, 1.0, 0.0, 0.9 );
                plwind( -1.1, 0.75, -0.65, 1.20 );
                //
                // For the comparison to be fair, all plots should have the
                // same z values, but to get the max/min of the data generated
                // by all algorithms would imply two passes. Keep it simple.
                //
                // plw3d(1., 1., 1., xm, xM, ym, yM, zmin, zmax, 30, -60);
                //

                plw3d( 1., 1., 1., xm, xM, ym, yM, lzm, lzM, 30, -40 );
                plbox3( "bntu", "X", 0., 0,
                    "bntu", "Y", 0., 0,
                    "bcdfntu", "Z", 0.5, 0 );
                plcol0( 15 );
                pllab( "", "", title[alg - 1] );
                plot3dc( xg, yg, (const PLFLT * const *) zg, xp, yp, DRAW_LINEXY | MAG_COLOR | BASE_CONT, clev, nl );
            }
        }
    }

    plend();

    free_data( x, y, z );
    free_grid( xg, yg );
    free( (void *) clev );
    plFree2dGrid( zg, xp, yp );
    exit( 0 );
}


void
create_grid( PLFLT **xi, int px, PLFLT **yi, int py )
{
    PLFLT *x, *y;
    int   i;

    x = *xi = (PLFLT *) malloc( (size_t) px * sizeof ( PLFLT ) );
    y = *yi = (PLFLT *) malloc( (size_t) py * sizeof ( PLFLT ) );

    for ( i = 0; i < px; i++ )
        *x++ = xm + ( xM - xm ) * i / ( px - 1. );

    for ( i = 0; i < py; i++ )
        *y++ = ym + ( yM - ym ) * i / ( py - 1. );
}

void
free_grid( PLFLT *xi, PLFLT *yi )
{
    free( (void *) xi );
    free( (void *) yi );
}

void
create_data( PLFLT **xi, PLFLT **yi, PLFLT **zi, int npts )
{
    int   i;
    PLFLT *x, *y, *z, r;
    PLFLT xt, yt;

    *xi = x = (PLFLT *) malloc( (size_t) npts * sizeof ( PLFLT ) );
    *yi = y = (PLFLT *) malloc( (size_t) npts * sizeof ( PLFLT ) );
    *zi = z = (PLFLT *) malloc( (size_t) npts * sizeof ( PLFLT ) );

    for ( i = 0; i < npts; i++ )
    {
        xt = ( xM - xm ) * plrandd();
        yt = ( yM - ym ) * plrandd();
        if ( !randn )
        {
            *x = xt + xm;
            *y = yt + ym;
        }
        else // std=1, meaning that many points are outside the plot range
        {
            *x = sqrt( -2. * log( xt ) ) * cos( 2. * M_PI * yt ) + xm;
            *y = sqrt( -2. * log( xt ) ) * sin( 2. * M_PI * yt ) + ym;
        }
        if ( !rosen )
        {
            r  = sqrt( ( *x ) * ( *x ) + ( *y ) * ( *y ) );
            *z = exp( -r * r ) * cos( 2.0 * M_PI * r );
        }
        else
        {
            *z = log( pow( 1. - *x, 2. ) + 100. * pow( *y - pow( *x, 2. ), 2. ) );
        }
        x++; y++; z++;
    }
}

void
free_data( PLFLT *x, PLFLT *y, PLFLT *z )
{
    free( (void *) x );
    free( (void *) y );
    free( (void *) z );
}
