/**
* Copyright 1981-2016 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.h"
#include "emos.h"


fortint int2_estima();
fortint int2_intin (const char* param, fortint iv[], fortfloat dv[], const char* cv);
fortint int2_intout(const char* param, fortint iv[], fortfloat dv[], const char* cv);
fortint int2_intuvu(fortfloat vort_in[], fortfloat div_in[], fortint in_array_length, fortfloat *vort_out, fortfloat *div_out, fortint *out_array_length);
fortint int2_outrep();
fortint int2_setrep(fortint output_flag);
int copy_spec_from_ksec(grib_util_grid_spec* spec, grib_util_packing_spec* packing_spec);


fortint intuvp2_(char* vort_grib_in, char* div_grib_in, fortint* length_in, char* vort_grib_out, char* div_grib_out, fortint* length_out)
{

    static double *vort_values_in = NULL;
    static double *div_values_in  = NULL;
    static double *vort_values_out = NULL;
    static double *div_values_out  = NULL;
    static size_t values_in_len = 0;
    static size_t values_out_len = 0;


    static float *vort_values_in1 = NULL;
    static float *div_values_in1  = NULL;
    static float *vort_values_out1 = NULL;
    static float *div_values_out1  = NULL;
    static size_t values_in_len1 = 0;
    static size_t values_out_len1 = 0;

    grib_handle* hu = NULL;
    grib_handle* hv = NULL;

    size_t inlen;
    size_t outlen  = *length_out;
    size_t outlen1  = *length_out;

    long long_inlen  = *length_in;
    fortint fortint_inlen  = *length_in;
    fortint fortint_outlen = outlen;

    int jpeg = 0, resetOutput = 0, what = 0;
    long ii = 0;

    char *text = "";
    fortint   intv[4];
    fortfloat realv[4];
    long truncation;
    grib_handle *handle1 = NULL, *handle2 = NULL;
    grib_handle *outh_u = NULL, *outh_v = NULL;
    int err = 0;
    int grib_err = 0;
    long accuracy = 0;

    fortint out_length = 0;

    const void* temp1;
    const void* temp2;
    char *intf2_debug ;
    char *intf2_write ;
    fortint outputRepresentation;
    int gribex_compatibility;
    grib_util_grid_spec spec={0,};
    grib_util_packing_spec packing_spec={0,};

    gribex_compatibility=grib_get_gribex_mode(0);

    intf2_debug = getenv("INTF2_DEBUG");
    intf2_write = getenv("INTF2_WRITE_TO_FILE");

    outputRepresentation = int2_outrep();

    if(outputRepresentation)
        if(intf2_debug) {
            printf("INTUVP2: Output Representation is Not set by user\n");
        }

    handle1 = grib_handle_new_from_message_copy(0,vort_grib_in,long_inlen);
    if(!handle1) {
        err = -1;
        goto cleanup;
    }

    handle2 = grib_handle_new_from_message_copy(0,div_grib_in,long_inlen);
    if(!handle2) {
        err = -1;
        goto cleanup;
    }

    /*----------------------------------*/
    if ((err = grib_get_size(handle1,"values",&inlen)))
    {
        fprintf(stderr,"INTUVP2: Cannot get size for vorticity %s\n",grib_get_error_message(err));
        goto cleanup;
    }

    if(intf2_debug) { printf("INTUVP2: inlen for vorticity: %lu\n",inlen); }

    if(inlen > values_in_len)
    {
        if(vort_values_in) free(vort_values_in);
        vort_values_in = (double*)malloc(sizeof(double)*inlen);
        values_in_len = inlen;

        if(!vort_values_in)
        {
            err = -1;
            fprintf(stdout,"INTUVP2: Cannot allocate vort_values_in %ld\n",inlen);
            goto cleanup;
        }

        if(div_values_in) free(div_values_in);
        div_values_in = (double*)malloc(sizeof(double)*inlen);
        if(!div_values_in)
        {
            err = -1;
            fprintf(stderr,"INTUVP2: Cannot allocate input array for divergency %lu\n",inlen);
            goto cleanup;
        }
    }

/*
    if( err = grib_get_size(handle2,"values",&inlen))
    {
        fprintf(stderr,"INTUVP2: Cannot get size for divergency %s\n",grib_get_error_message(err));
        goto cleanup;
    }
*/
    if(intf2_debug) { printf("INTUVP2: inlen for divergency: %lu\n",inlen); }


    /* Default Accuracy */
    if((err = grib_get_long(handle1,"bitsPerValue",&accuracy))!= GRIB_SUCCESS)
    {
        fprintf(stderr,"INTUVP2: Cannot get accuracy %s\n",grib_get_error_message(err));
        return err;
    }
    intv[0] = accuracy;
    if ((err = int2_intin("accuracy",intv,realv,text)))
    {
        fprintf(stderr,"INTUVP2: Accuracy setup INTIN failed %d\n",err);
    }
    if(intf2_debug)
    {
        printf("INTUVP2: Input Accuracy  %ld\n",accuracy);
    }

    /*----------------------------------*/

    /* Get decoded values vorticity */
    if ((err = grib_get_double_array(handle1,"values",vort_values_in,&inlen)))
    {
        fprintf(stderr,"INTUVP2: Cannot get decoded values %s\n",grib_get_error_message(err));
        goto cleanup;
    }

    /* Get decoded values divergency */
    if ((err = grib_get_double_array(handle2,"values",div_values_in,&inlen)))
    {
        fprintf(stderr,"INTUVP2: Cannot get decoded values %s\n",grib_get_error_message(err));
        goto cleanup;
    }
    /*----------------------------------*/

    /* get Truncation */
    if ((err = grib_get_long(handle1,"J", &truncation)))
    {
        fprintf(stderr,"INTUVP2: Cannot get Truncation %s\n",grib_get_error_message(err));
        goto cleanup;
    }
    intv[0] = truncation;
    if ((err = int2_intin("truncation",intv,realv,text)))
    {
        fprintf(stderr,"INTUVP2: Truncation  setup INTIN failed %d\n",err);
        goto cleanup;
    }
    if(outputRepresentation)
    {
        if ((err = int2_intout("truncation",intv,realv,text)))
        {
            fprintf(stderr,"INTUVP2: Truncation  setup INTOUT failed %d\n",err);
            goto cleanup;
        }
    }

    /*=============  INTUVY ====================================*/
   if(outputRepresentation){
        out_length = inlen;
    }
    else{
        out_length = int2_estima();
        if(!out_length){
            fprintf(stdout,"INTUVP2: Estimate for length of output array is 0 \n");
            err = -1;
            goto cleanup;
        }
    }

    if(out_length > values_out_len)
    {
        if(vort_values_out) free(vort_values_out);
        vort_values_out = (double*)malloc(sizeof(double)*out_length);
        values_out_len = out_length;

        if(!vort_values_out)
        {
            fprintf(stdout,"INTUVP2: Cannot allocate vort_values_out %d\n",out_length);
            err = -1;
            goto cleanup;
        }
        if(div_values_out) free(div_values_out);
        div_values_out = (double*)malloc(sizeof(double)*out_length);

        if(!div_values_out)
        {
            fprintf(stdout,"INTUVP2: Cannot allocate div_values_out %d\n",out_length);
            err = -1;
            goto cleanup;
        }
    }


    fortint_inlen = inlen;
    fortint_outlen = outlen;
#ifdef REAL_8
    if ((err = int2_intuvu(vort_values_in, div_values_in, fortint_inlen, vort_values_out, div_values_out, &fortint_outlen)))
    {
        fprintf(stderr,"INTUVP2 failed %d\n",err);
        goto cleanup;
    }
#else
    if(inlen > values_in_len1)
    {
        if(vort_values_in1) free(vort_values_in1);
        vort_values_in1 = (float*)malloc(sizeof(float)*inlen);
        values_in_len1 = inlen;

        if(!vort_values_in1)
        {
            err = -1;
            fprintf(stdout,"INTUVP2: Cannot allocate vort_values_in %lu\n",inlen);
            goto cleanup;
        }

        if(div_values_in1) free(div_values_in1);
        div_values_in1 = (float*)malloc(sizeof(float)*inlen);
        if(!div_values_in1)
        {
            err = -1;
            fprintf(stderr,"INTUVP2: Cannot allocate input array for divergency %lu\n",inlen);
            goto cleanup;
        }
    }
    if(out_length > values_out_len1)
    {
        if(vort_values_out1) free(vort_values_out1);
        vort_values_out1 = (float*)malloc(sizeof(float)*out_length);
        values_out_len1 = out_length;

        if(!vort_values_out1)
        {
            fprintf(stdout,"INTUVP2: Cannot allocate vort_values_out %lu\n",out_length);
            err = -1;
            goto cleanup;
        }
        if(div_values_out1) free(div_values_out1);
        div_values_out1 = (float*)malloc(sizeof(float)*out_length);

        if(!div_values_out1)
        {
            fprintf(stdout,"INTUVP2: Cannot allocate div_values_out %lu\n",out_length);
            err = -1;
            goto cleanup;
        }
    }
    for( ii = 0 ; ii < fortint_inlen; ii++){
            vort_values_in1[ii] = vort_values_in[ii];
   }
    for( ii = 0 ; ii < fortint_inlen; ii++){
            div_values_in1[ii] = div_values_in[ii];
   }
    if ((err = int2_intuvu(vort_values_in1, div_values_in1, fortint_inlen, vort_values_out1, div_values_out1, &fortint_outlen)))
    {
        fprintf(stderr,"INTUVP2 failed %d\n",err);
        goto cleanup;
    }
    for( ii = 0 ; ii < fortint_outlen; ii++){
            vort_values_in[ii] = vort_values_in1[ii];
   }
    for( ii = 0 ; ii < fortint_outlen; ii++){
            div_values_in[ii] = div_values_in1[ii];
   }
#endif

    *length_out = fortint_outlen;
    outlen     = fortint_outlen;

    if(intf2_debug)
    {
        int i;
        printf("INTUVP2: outlen: %lu\n",outlen);
        for(i=0; i<10 ; i++)
        {
            printf("INTUVP2: output data values U -  %d -  %f \n",i,vort_values_out[i]);
            printf("INTUVP2: output data values V -  %d -  %f \n",i,div_values_out[i]);
        }
    }

    if(outlen)
    {
        err=copy_spec_from_ksec(&spec,&packing_spec);

        packing_spec.accuracy=GRIB_UTIL_ACCURACY_USE_PROVIDED_BITS_PER_VALUES;
        packing_spec.packing=GRIB_UTIL_PACKING_USE_PROVIDED;

        if (err) {
                  fprintf(stdout,"INTUVP2: ERROR - copy_grid_spec_from_ksec unable to copy spec: %d\n", err);
                  goto cleanup;
        }
        if (spec.grid_type==GRIB_UTIL_GRID_SPEC_SH) {
            if (0 && gribex_compatibility) {
                double laplacianOperator=0;
                long P=0;
                grib_get_long(handle2,"P",&P);
                if ((err = grib_get_double(handle2,"laplacianOperator",&laplacianOperator)))
                {
                    fprintf(stderr,"INTUVP2: Cannot Get P %s\n",grib_get_error_message(err));
                    goto cleanup;
                }
                /*
                if(err = grib_set_long(handle2,"computeLaplacianOperator",0))
                {
                    fprintf(stderr,"INTUVP2: Cannot Set computeLaplacianOperator %s\n",grib_get_error_message(err));
                    goto cleanup;
                }
                if(err = grib_set_long(handle1,"computeLaplacianOperator",0))
                {
                    fprintf(stderr,"INTUVP2: Cannot Set computeLaplacianOperator %s\n",grib_get_error_message(err));
                    goto cleanup;
                }
                if(err = grib_set_long(handle1,"P",P))
                {
                    fprintf(stderr,"INTUVP2: Cannot Set P %s\n",grib_get_error_message(err));
                    goto cleanup;
                }
                if(err = grib_set_long(handle2,"P",P))
                {
                    fprintf(stderr,"INTUVP2: Cannot Set P %s\n",grib_get_error_message(err));
                    goto cleanup;
                }
                */
                packing_spec.computeLaplacianOperator=0;
                packing_spec.truncateLaplacian=1;
                packing_spec.laplacianOperator=laplacianOperator;
            } else {
                packing_spec.computeLaplacianOperator=1;
            }
        }

        hu = grib_util_set_spec(handle1,&spec, &packing_spec, 0,vort_values_out, outlen, &err);
        if(!hu)  {
                   fprintf(stdout,"INTUVP2: ERROR - grib_util_set_spec: %d\n", err);
                   if(!err) err = 1;
                   goto cleanup;
        }

        hv = grib_util_set_spec(handle2,&spec, &packing_spec, 0,div_values_out, outlen, &err);
        if(!hv)  {
                   fprintf(stdout,"INTUVP2: ERROR - grib_util_set_spec: %d\n", err);
                   if(!err) err = 1;
                   goto cleanup;
        }

        /* U velocity*/
        if ((err = grib_set_long(hu,"paramId",131)))
        {
            fprintf(stderr,"INTUVP2: Cannot Set V %s\n",grib_get_error_message(err));
            goto cleanup;
        }

        err = grib_get_message(hu,&temp1,&outlen);
        if(err!=0)
        {
            fprintf(stderr,"INTUVP2: ERROR - grib_get_message unable to generate message (U): %d\n",err);
            goto cleanup;
        }
        if(temp1) {
            if(intf2_debug) {
                printf("INTUVP2: outlen u-comp -> %lu\n", outlen);
            }
            memcpy(vort_grib_out,temp1,outlen);
        }
        else
            fprintf(stderr,"INTUVP2: Error u-comp \n");
        outlen = *length_out;

        /* V velocity*/
        if ((err = grib_set_long(hv,"paramId",132)))
        {
            fprintf(stderr,"INTUVP2: Cannot Set V %s\n",grib_get_error_message(err));
            goto cleanup;
        }
        grib_get_message(hv,&temp2,&outlen);
        if(err!=0)
        {
            fprintf(stderr,"INTUVP2: ERROR - grib_get_message unable to generate message (V): %d\n",err);
            goto cleanup;
        }
        if(temp2) {
            if(intf2_debug) {
                printf("INTUVP2: outlen v-comp -> %lu\n", outlen);
            }
            memcpy(div_grib_out,temp2,outlen);
        }
        else
            fprintf(stderr,"INTUVP2: Error v-comp \n");
        *length_out = outlen;

        goto cleanup;

    }
    else {
        fprintf(stderr,"INTUVP2: ERROR - INTUVP2  OUTLEN is: %lu\n", outlen);
        err = 1;
    }

cleanup:

    if((resetOutput = int2_setrep(outputRepresentation))) printf("INTUVP2: Output Representation reset failed:  %d \n",resetOutput);
    if(hu && (hu != handle1))     grib_handle_delete(hu);
    if(hv && (hv != handle2))     grib_handle_delete(hv);
    if(handle1)     {grib_handle_delete(handle1);handle1=0;};
    if(handle2)     {grib_handle_delete(handle2);handle2=0;};

    return err;
}

fortint intuvp2(char* vort_grib_in, char* div_grib_in, fortint* length_in, char* vort_grib_out, char* div_grib_out, fortint* length_out)
{
    return intuvp2_(vort_grib_in,div_grib_in,length_in,vort_grib_out,div_grib_out,length_out);
}
