/**
* Copyright 1981-2012 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 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: %d  \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 %d\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: %d  \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  %d  \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 %ld\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 %ld\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 %ld\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 %d\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 %ld\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 %ld\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: %d  \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;
		}

		grib_get_message(hu,&temp1,&outlen);
		if(temp1) {
			if(intf2_debug) {
				printf("INTUVP2: outlen u-comp -> %d \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(temp2) {
			if(intf2_debug) {
				printf("INTUVP2: outlen v-comp -> %d \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: %d\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);
}
