/*
 * Copyright 2005-2017 ECMWF.
 *
 * This software is licensed under the terms of the Apache Licence Version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
 *
 * In applying this licence, ECMWF does not waive the privileges and immunities granted to it by
 * virtue of its status as an intergovernmental organisation nor does it submit to any jurisdiction.
 */

#include "grib_api_internal.h"

/*
   This is used by make_class.pl

   START_CLASS_DEF
   CLASS      = nearest
   SUPER      = grib_nearest_class_gen
   IMPLEMENTS = init
   IMPLEMENTS = find;destroy
   MEMBERS = const char* J
   MEMBERS = const char* K
   MEMBERS = const char* M
   END_CLASS_DEF

 */

/* START_CLASS_IMP */

/*

Don't edit anything between START_CLASS_IMP and END_CLASS_IMP
Instead edit values between START_CLASS_DEF and END_CLASS_DEF
or edit "nearest.class" and rerun ./make_class.pl

*/


static void init_class              (grib_nearest_class*);

static int init               (grib_nearest* nearest,grib_handle* h,grib_arguments* args);
static int find(grib_nearest* nearest, grib_handle* h,double inlat, double inlon, unsigned long flags, double* outlats,double* outlons, double *values,double *distances, int *indexes,size_t *len);
static int destroy            (grib_nearest* nearest);

typedef struct grib_nearest_sh{
  grib_nearest nearest;
/* Members defined in gen */
	const char* values_key;
	const char* radius;
	int cargs;
/* Members defined in sh */
	const char* J;
	const char* K;
	const char* M;
} grib_nearest_sh;

extern grib_nearest_class* grib_nearest_class_gen;

static grib_nearest_class _grib_nearest_class_sh = {
    &grib_nearest_class_gen,                         /* super                     */
    "sh",                         /* name                      */
    sizeof(grib_nearest_sh),      /* size of instance          */
    0,                              /* inited */
    &init_class,                    /* init_class */
    &init,                          /* constructor               */
    &destroy,                       /* destructor                */
    &find,                          /* find nearest              */
};

grib_nearest_class* grib_nearest_class_sh = &_grib_nearest_class_sh;


static void init_class(grib_nearest_class* c)
{
}
/* END_CLASS_IMP */

#define LEGENDRE_SIZE(L) (L+1)*(L+2)/2

static void grib_trigs(int m,double lambda,double* c,double* s);
static int grib_invtrans_legendre(int L,double x, double* RI,double* TR,double* TI);
static double grib_invtrans_trig(int L,double* TR,double* TI,
        double *c,double *s);
static int grib_invtrans(grib_context *c,int L,double latdeg,double londeg,double* values, double* result);

static int init(grib_nearest* nearest,grib_handle* h,grib_arguments* args)
{
    grib_nearest_sh* self = (grib_nearest_sh*) nearest;
    self->J  = grib_arguments_get_name(h,args,self->cargs++);
    self->K  = grib_arguments_get_name(h,args,self->cargs++);
    self->M  = grib_arguments_get_name(h,args,self->cargs++);
    return 0;
}

static int find(grib_nearest* nearest, grib_handle* h,
        double inlat, double inlon,unsigned long flags,
        double* outlats,double* outlons,
        double *outvalues,double *distances,int* indexes, size_t *len)
{
    grib_nearest_sh* self = (grib_nearest_sh*) nearest;
    long J,K,M;
    double *values;
    int size,i,ret;
    size_t vsize=0;
    double val;

    if( (ret =  grib_get_long(h,self->J,&J))!= GRIB_SUCCESS)
        return ret;
    if( (ret =  grib_get_long(h,self->K,&K))!= GRIB_SUCCESS)
        return ret;
    if( (ret =  grib_get_long(h,self->M,&M))!= GRIB_SUCCESS)
        return ret;

    size=2*LEGENDRE_SIZE(J);
    vsize=size;
    values=(double*)grib_context_malloc_clear(h->context,sizeof(double)*size);
    if (!values) {
        grib_context_log(h->context,GRIB_LOG_ERROR,
                "nearest_sh: unable to allocate %d bytes",
                sizeof(double)*size);
        return GRIB_OUT_OF_MEMORY;
    }

    if( (ret =  grib_get_double_array(h,self->values_key,values,&vsize))
            != GRIB_SUCCESS)
        return ret;

    Assert(vsize==size);

    ret=grib_invtrans(h->context,J,inlat,inlon,values, &val);
    if (ret != GRIB_SUCCESS) return ret;

    grib_context_free(h->context,values);

    for (i=0;i<4;i++) {
        outlats[i]=inlat;
        outlons[i]=inlon;
        outvalues[i]=val;
        indexes[i]=-1;
    }

    return GRIB_SUCCESS;
}

static int destroy(grib_nearest* nearest)
{
    return GRIB_SUCCESS;
}

static void grib_trigs(int m,double lambda,double* c,double* s)
{
    int i;
    double a,b;

    b=sin(lambda);
    a=1-2*sin(lambda/2.0)*sin(lambda/2.0);
    c[0]=1.0; s[0]=0.0;

    for (i=1;i<=m;i++) {
        c[i]=a*c[i-1]-b*s[i-1];
        s[i]=a*s[i-1]+b*c[i-1];
    }

}

static double grib_invtrans_trig(int L,double* TR,double* TI,double *c,double *s)
{
    double ret=0;
    int m=0;

    for (m=1;m<=L;m++) {
        ret+= TR[m] * c[m];
        printf("++ %d ++ %.20e %g %g\n",m,ret,TR[m],c[m]);
        ret-= TI[m] * s[m];
        printf("+- %d ++ %.20e %g %g\n",m,ret,TI[m],s[m]);
    }
    ret=2*ret+TR[0];

    return ret;
}

static int grib_invtrans_legendre(int L,double x,
        double* RI,double* TR,double* TI)
{
    int l,m;
    double y2;
    double f,of,fx,p0;
    double *pP,*oP,*pRI;

    if (fabs(x) > 1.0) {
        printf("grib_legendreP: invalid x=%g must be abs(x)>0\n",x);
        return GRIB_INVALID_ARGUMENT;
    }
    if (L<0) {
        printf("grib_legendreP: invalid L=%d must be >0\n",L);
        return GRIB_INVALID_ARGUMENT;
    }

    pP=(double*)malloc(sizeof(double)*(L+1));
    if (!pP) {
        printf("unable to allocate %d bytes\n",(int)sizeof(double)*(L+1));
        return GRIB_OUT_OF_MEMORY;
    }

    y2=(1.0-x*x); fx=1; p0=1; oP=pP; pRI=RI;
    for (m=0;m<L;m++) {
        /* printf("\n"); */
        pP[0]=sqrt((2*m+1)*p0);
        if (m & 1) pP[0]=-pP[0];
        TR[m]=pP[0] * *(pRI++);
        TI[m]=pP[0] * *(pRI++);
        printf("-- (%d,%d) %.20e %.20e\n",m,m,TR[m],pP[0]);
        of=sqrt(2*m+3);
        pP[1]=x*of*pP[0];
        TR[m]+=pP[1] * *(pRI++);
        TI[m]+=pP[1] * *(pRI++);
        printf("-- (%d,%d) %.20e %.20e\n",m+1,m,TR[m],pP[1]);
        for (l=m+2;l<=L;l++) {
            f=sqrt((4.0*l*l-1.0)/(l*l-m*m));
            pP[2]=(x*pP[1]-pP[0]/of)*f;
            TR[m]+=pP[2] * *(pRI++);
            TI[m]+=pP[2] * *(pRI++);
            printf("-- (%d,%d) %.20e %.20e\n",l,m,TR[m],pP[2]);
            of=f;
            pP++;
        }
        pP=oP;
        p0*=y2*fx/(fx+1.0);
        fx+=2;
    }
    /* printf("\n"); */
    pP[0]=sqrt((2*L+1)*p0);
    if (L & 1) pP[0]=-pP[0];
    TR[L]=pP[0] * *(pRI++);
    TI[L]=pP[0] * *(pRI++);
    printf("-- (%d,%d) %.20e %.20e\n",L,L,TR[m],pP[0]);
    return GRIB_SUCCESS;
}

static int grib_invtrans(grib_context* context, int L, double latdeg, double londeg, double* values,
        double* result)
{
    double *c,*s,*TR,*TI;
    double sinlat,deg2rad,lonrad;
    int Lp1=L+1;
    int err = 0;

    deg2rad=acos(0.0)/90.0;
    sinlat=sin(latdeg*deg2rad);
    lonrad=londeg*deg2rad;

    c=(double*)grib_context_malloc_clear(context,sizeof(double)*Lp1);
    if (!c) {
        grib_context_log(context,GRIB_LOG_ERROR,
                "nearest_sh: unable to allocate %d bytes",sizeof(double)*Lp1);
        return GRIB_OUT_OF_MEMORY;
    }

    s=(double*)grib_context_malloc_clear(context,sizeof(double)*Lp1);
    if (!s) {
        grib_context_log(context,GRIB_LOG_ERROR,
                "nearest_sh: unable to allocate %d bytes",sizeof(double)*Lp1);
        return GRIB_OUT_OF_MEMORY;
    }

    grib_trigs(L,lonrad,c,s);

    TR=(double*)grib_context_malloc_clear(context,sizeof(double)*Lp1);
    if (!TR) {
        grib_context_log(context,GRIB_LOG_ERROR,
                "nearest_sh: unable to allocate %d bytes",sizeof(double)*Lp1);
        return GRIB_OUT_OF_MEMORY;
    }
    TI=(double*)grib_context_malloc_clear(context,sizeof(double)*Lp1);
    if (!TI) {
        grib_context_log(context,GRIB_LOG_ERROR,
                "nearest_sh: unable to allocate %d bytes",sizeof(double)*Lp1);
        return GRIB_OUT_OF_MEMORY;
    }

    err = grib_invtrans_legendre(L,sinlat,values,TR,TI);
    if(err) return err;

    *result=grib_invtrans_trig(L,TR,TI,c,s);

    grib_context_free(context,c);
    grib_context_free(context,s);
    grib_context_free(context,TR);
    grib_context_free(context,TI);

    return GRIB_SUCCESS;
}
