/***************************** LICENSE START ***********************************

 Copyright 2012 ECMWF and INPE. This software is distributed under the terms
 of the Apache License version 2.0. In applying this license, ECMWF does not
 waive the privileges and immunities granted to it by virtue of its status as
 an Intergovernmental Organization or submit itself to any jurisdiction.

 ***************************** LICENSE END *************************************/

//-- What should we do with these functions?         (vk/Sep09)
//--
//-- The old code used memory mapping for images, accessing each
//-- 8-bit pixel values directly, without any need to decode.
//-- This required 'offset' variable to tell where the data started...
//--
//-- With GRIB_API there is no method to get 'offset' (at least
//-- I have not spotted such a method)...
//--

#include "macro.h"
#include "grib_api.h"
#include "arith.h"
#include <math.h>
#include <sys/types.h>
/* Stupid hp as no extern C */
extern "C" {
#include <sys/mman.h>
}

//-- this one only while grib_api_port_missing() is called!
#include "MvFieldSet.h"

//-- to have an easy switch between gribex (1) and gribapi (0)
#define USE_OLD_GRIBEX_CODE 0

// 'round' clashes with math.h on hpux 11 => rename as 'l_round':
inline long l_round(double a) { return ((long)((a) + (((a)>=0) ? 0.5 : -0.5)));}

inline unsigned char scale(double d)
{
	d *= 255;
	if(d<0)   d = 0;
	if(d>255) d = 255;
	return  (unsigned char)d;
}

inline double  scale(unsigned char c)
{
	return (double)c / 255.0;
}


class Image {

	long  nx;		//-- pixels along x-axis
	long  ny;		//-- pixels along y-axis
	char *name;		//-- unique name for Macro prints/debugs
	char *path;		//-- path to satellite GRIB file

//-- DO WE NEED TO USE MEMORY MAPPING ALSO WITH GRIB_API????

	unsigned char *buffer;	//-- memory mapped GRIB file/msg
	unsigned char *data;	//-- data part in memory mapped GRIB msg
	int length;		//-- length of the GRIB file/msg
	long offset;		//-- start of data in memory mapped GRIB msg
	size_t count;		//-- total number of pixel values
	int mapped;		//-- OBSOLETE? only set, not checked at all!!!?

	FILE *file;		//-- store file descr between Map() and Unmap()

public:
	int Count(void) { return count; };
	int Nx(void)    { return nx; };
	int Ny(void)    { return ny; };

	void Map(void);
	void Unmap(void);
	unsigned char& operator [](int n)       { return data[n];      };
	unsigned char& operator ()(int i,int j) { return data[i+nx*j]; };

	const char *Path() { return path; }
	void ToRequest(request* &);
	Image(Image*);
	Image(Image*,long);
	Image(const char *p,const char *n);
	~Image();

};

#if USE_OLD_GRIBEX_CODE
//-- you also need to link with libMars-old.a !!!
extern "C" {
err cgribex(fortint ksec0[], fortint ksec1[], fortint ksec2[], fortfloat rsec2[], fortint ksec3[], fortfloat rsec3[], fortint ksec4[], fortfloat rsec4[], fortint sec4len, char *buffer, fortint *buflen, char *op);
}
#endif

Image::Image(const char *p, const char *n)
{
#if USE_OLD_GRIBEX_CODE
	name   = n?strcache(n):strcache(Context::UniqueName());
	path   = strcache(p);
	buffer = 0;
	length = 0;
	offset = 0;
	mapped = 0;
	file   = 0;
	nx     = 0;
	ny     = 0;
	count  = 0;

	fortint  ksec0[ISECTION_0];
	fortint  ksec1[ISECTION_1];
	fortint  ksec2[ISECTION_2];
	fortfloat zsec2[RSECTION_2];
	fortint  ksec3[ISECTION_3];
	fortfloat zsec3[RSECTION_3];
	fortint  ksec4[ISECTION_4];
	fortfloat zsec4[20];
	int   len4 = 20;

	Map();
	fortint len = length;
    	err e = cgribex( ksec0,ksec1,
			ksec2,zsec2, ksec3,zsec3,
			ksec4, zsec4, len4, (char*)buffer, &len, "G");

	offset = (int)ksec4[33]/8;
	count  = (int)ksec4[0];
	nx     = (int)ksec2[1];
	ny     = (int)ksec2[2];
	Unmap();
#else

	name   = n?strcache(n):strcache(Context::UniqueName());
	path   = strcache(p);

	FILE* in = fopen( path, "r" );
	if( ! in )
	   marslog( LOG_EROR, "Image::Image - unable to open input file" );

	int ret = 0;
	grib_handle *h = grib_handle_new_from_file( 0, in, &ret );
	if( ! h )
	   marslog( LOG_EROR, "Image::Image - unable to get grib_handle" );

	//ret = grib_get_long( h, "x", &offset ); //-- do we need offset with grib_api???
	//if( ! ret ) ...
	ret = grib_get_size( h, "values", &count );
	if( ret != 0 )
	   marslog( LOG_EROR, "Image::Image - unable to read image size" );

	ret = grib_get_long( h, "numberOfPointsAlongXAxis", &nx );
	if( ret != 0 )
	   marslog( LOG_EROR, "Image::Image - unable to read numberOfPointsAlongXAxis" );

	ret = grib_get_long( h, "numberOfPointsAlongYAxis", &ny );
	if( ret != 0 )
	   marslog( LOG_EROR, "Image::Image - unable to read numberOfPointsAlongYAxis" );

return;

#endif
}

Image::Image(Image *i)
{
	name   = strcache(Context::UniqueName());
	path   = strcache(marstmp());
	buffer = 0;
	length = i->length;
	offset = i->offset;
	count  = i->count ;
	nx     = i->nx;
	ny     = i->ny;
	mapped = 0;


	file = fopen(path,"r+");
	fseek(file,length-1,0);
	char c = 0;
	fwrite(&c,1,1,file);
	fclose(file);

	Map();  i->Map();

/*
	memcpy(buffer,i->buffer,length);
	*/

	memcpy(buffer,i->buffer,offset);
	memcpy(buffer+offset+count,
	    i->buffer+offset+count,
		length-offset-count);
	Unmap(); i->Unmap();

	data = buffer + offset;
}

Image::~Image()
{
	strfree(path);
	strfree(name);
}

void Image::Map(void)
{
	file = fopen(path,"r+");
	if(file == 0)
		marslog(LOG_EXIT|LOG_PERR,"%s",path);

	fseek(file,0,2);
	length = (int)ftell(file);

	buffer = (unsigned char*)mmap(0,length,
		PROT_READ|PROT_WRITE,MAP_SHARED,fileno(file),0);

	if(buffer == 0)
		marslog(LOG_EXIT|LOG_PERR,"mmap");

	data = buffer + offset;
}

void Image::Unmap(void)
{
	munmap((caddr_t)buffer,length);
	fclose(file);
}


void Image::ToRequest(request* &s)
{
	static request *r = 0;
	free_all_requests(r);
	r = empty_request("IMAGE");
	set_value(r,"PATH","%s",path);
	set_value(r,"_NAME","%s",name);
	set_value(r,"_CLASS","MACRO");
	s = r;
}

int CImage::Write(FILE* f)
{
	return CopyFile(image->Path(),f);
}

void CImage::ToRequest(request* &s)
{
	image->ToRequest(s);
	// Temp fix : Some one want the path, so don't delete it
	Attach();
}

CImage::CImage(Image *i) : InPool(timage)
{
	image = i;
}


/*
gribex 'g' opt:
psec4(1)  -> ref value;
psec4(2)  -> scale value;
ksec4(34) -> bit pointer
*/

CImage::CImage(request *r) : InPool(timage,r)
{
	const char *path   = get_value(r,"PATH",0);
	image = new Image(path,GetName());
}

CImage::~CImage()
{
	delete image;
}

//=============================================================

class ImageConvol : public Function {
public:
	ImageConvol(char *n) : Function(n)
	{info = "Applies a convolution matrix to an image.";}
	virtual Value Execute(int arity,Value *arg);
	virtual int  ValidArguments(int arity,Value *arg);
};

int  ImageConvol::ValidArguments(int arity,Value *arg)
{
	if(arity != 2) return false;
	if(arg[0].GetType() != timage) return false;
	if(arg[1].GetType() != tlist ) return false;

	CList *l;
	arg[1].GetValue(l);

	if(l->Count() != 9) return false;
	for(int i = 0 ; i< 9 ; i++)
		if((*l)[i].GetType() != tnumber) return false;
	return true;
}

Value ImageConvol::Execute(int,Value *arg)
{
	Image *p;
	CList *l;

	arg[0].GetValue(p);
	arg[1].GetValue(l);

	double a[9];

	int n;
	for( n = 0;n<9;n++)
		(*l)[n].GetValue(a[n]);

	double d = 0;
	for(n = 0;n<9;n++)
		d += a[n];

	if(fabs(d) < 1e-10) d = 1;

	d *= 255;
	for(n = 0;n<9;n++)
		a[n] /= d;

	Image *q = new Image(p);

	p->Map(); q->Map();

	for(int i=1;i<p->Nx()-1;i++)
		for(int j=1;j<p->Ny()-1;j++)
			(*q)(i,j) = scale(

					(*p)(i-1,j-1) * a[0] +
					(*p)(i-1,j  ) * a[1] +
					(*p)(i-1,j+1) * a[2] +
					(*p)(i  ,j-1) * a[3] +
					(*p)(i  ,j  ) * a[4] +
					(*p)(i  ,j+1) * a[5] +
					(*p)(i+1,j-1) * a[6] +
					(*p)(i+1,j  ) * a[7] +
					(*p)(i+1,j+1) * a[8]

				);

	p->Unmap(); q->Unmap();

	return Value(new CImage(q));
}

//=============================================================

class ImageLut : public Function {
public:
	ImageLut(char *n) : Function(n)
	{info = "Remaps an image's pixel values.";}
	virtual Value Execute(int arity,Value *arg);
	virtual int  ValidArguments(int arity,Value *arg);
};

int  ImageLut::ValidArguments(int arity,Value *arg)
{
	if(arity != 2) return false;
	if(arg[0].GetType() != timage) return false;
	if(arg[1].GetType() != tlist ) return false;

	CList *l;
	arg[1].GetValue(l);

	if(l->Count() != 256) return false;
	for(int i = 0 ; i< 256 ; i++)
	{
		if((*l)[i].GetType() != tnumber) return false;
		double d;
		(*l)[i].GetValue(d);
		if(d<0 || d > 255) return false;
	}
	return true;
}

Value ImageLut::Execute(int,Value *arg)
{
	Image *p;
	CList *l;

	arg[0].GetValue(p);
	arg[1].GetValue(l);

	unsigned char a[256];

	for(int n = 0;n<256;n++)
	{
		double d;
		(*l)[n].GetValue(d);
		a[n] = (unsigned char)d;
	}

	Image *q = new Image(p);

	p->Map(); q->Map();

	for(int i=0;i<p->Count();i++)
		(*q)[i] = a[ (*p)[i] ];

	p->Unmap(); q->Unmap();

	return Value(new CImage(q));
}

//=============================================================
//=============================================================

class ImageUnOp : public Function {

	uniproc F_;

public:
	ImageUnOp(const char *n,uniproc f) : Function(n,1,timage) { F_ = f; }
	virtual Value Execute(int arity,Value *arg);
};

class ImageBinOp : public Function {

	binproc F_;

public:
	ImageBinOp(const char *n,binproc f) : Function(n) { F_ = f; }
	virtual Value Execute(int arity,Value *arg);
	virtual int  ValidArguments(int arity,Value *arg);
};

class ImageMulOp : public Function {

	typedef double (*binproc)(double,double);
	binproc F_;

public:
	ImageMulOp(const char *n,binproc f) : Function(n) { F_ = f; }
	virtual Value Execute(int arity,Value *arg);
	virtual int  ValidArguments(int arity,Value *arg);
};

//=============================================================

Value ImageBinOp::Execute(int,Value *arg)
{

	if(arg[0].GetType() == timage && arg[1].GetType() == timage)
	{
		Image *a,*b,*c;
		arg[0].GetValue(a);
		arg[1].GetValue(b);

		c = new Image(a);
		a->Map();b->Map();c->Map();
		for(int i=0;i<a->Count();i++)
			(*c)[i] = scale(F_(scale((*a)[i]),scale((*b)[i])));
		a->Unmap();b->Unmap();c->Unmap();

		return Value(new CImage(c));
	}
	else if(arg[0].GetType() == tnumber)
	{
		Image *b,*c;
		double d;
		arg[0].GetValue(d);
		arg[1].GetValue(b);

		c = new Image(b);
		b->Map();c->Map();
		for(int i=0;i<b->Count();i++)
			(*c)[i] = scale(F_(d,scale((*b)[i])));
		b->Unmap();c->Unmap();

		return Value(new CImage(c));
	}
	else
	{
		Image *a,*c;
		double d;
		arg[0].GetValue(a);
		arg[1].GetValue(d);

		c = new Image(a);
		a->Map();c->Map();
		for(int i=0;i<a->Count();i++)
			(*c)[i] = scale(F_(scale((*a)[i]),d));
		a->Unmap();c->Unmap();

		return Value(new CImage(c));
	}

}

int ImageBinOp::ValidArguments(int arity,Value *arg)
{
	if(arity != 2 ) return false;

	int image = 0;

	for(int i = 0;i<arity;i++)
		switch(arg[i].GetType())
		{
			case timage:
				image++;
				break;

			case tnumber:
				break;

			default:
				return false;
		}
	return image != 0;
}

Value ImageUnOp::Execute(int,Value *arg)
{
	Image *p;
	arg[0].GetValue(p);

	Image *q = new Image(p);

	p->Map(); q->Map();

	for(int i=0;i<p->Count();i++)
		(*q)[i] = scale(F_(scale((*p)[i])));

	p->Unmap(); q->Unmap();

	return Value(new CImage(q));
}

int ImageMulOp::ValidArguments(int arity,Value *arg)
{
	if(arity < 1) return false;
	for(int i = 0;i<arity;i++)
		if(arg[i].GetType() != timage)
			return false;
	return true;
}

Value ImageMulOp::Execute(int arity,Value *arg)
{
	double d;
	arg[0].GetValue(d);

	for(int i = 1 ; i < arity; i++)
	{
		double x;
		arg[i].GetValue(x);
		d = F_(d,x);
	}

	return Value(d);
}

//=============================================================

/* height of Meteosat above earth centre devided by earth radius */
#define MSATALT         6.610839

Image::Image(Image*i,long scale)
{
#if USE_OLD_GRIBEX_CODE
	fortint  ksec0[ISECTION_0];	fortint  ksec1[ISECTION_1];	fortint  ksec2[ISECTION_2];
	fortfloat zsec2[RSECTION_2];	fortint  ksec3[ISECTION_3];	fortfloat zsec3[RSECTION_3];
	fortint  ksec4[ISECTION_4];	fortfloat zsec4[20];		int   len4 = 20;

	// Get info from gribex
	i->Map();	fortint len = i->length;
	cgribex( ksec0,ksec1,ksec2,zsec2,ksec3,zsec3,ksec4,zsec4,len4,(char*)i->buffer, &len,"G");
	i->Unmap();

	//Get scaled pict

	/* nx/ny: number of points along x/y-axis */
	ksec2[1] = ksec2[1] / scale;	ksec2[2] = ksec2[1];

	/* dx/y-apparent diameter of the earth in grid lenght in the x/y dir */
	double r = (M_PI/10.0)/(double)ksec2[1];
	ksec2[6] = l_round((double)2.0*asin((double)(1.0/(double)MSATALT))/r);	ksec2[7] = ksec2[6];

	/* x/yp: x/y-coordinate of sub-satellite point: ksec2[1]/2 */
	ksec2[8] = l_round((double)ksec2[1] / (double)2.0);	ksec2[9] = ksec2[8];


	/* # of data values in psec4: columns x rows */
	ksec4[0]=ksec2[1]*ksec2[2];

	name   = strcache(Context::UniqueName());	path   = strcache(marstmp());
	buffer = 0;
	count  = (int)ksec4[0];
	length = i->length - i->count + count;
	offset = i->offset;
	mapped = 0;	file   = 0;
	nx     = (int)ksec2[1];		ny = (int)ksec2[2];


	file = fopen(path,"r+");	fseek(file,length-1,0);
	char c = 0;
	fwrite(&c,1,1,file);		fclose(file);

	Map();  i->Map();

	len = length;

	len4 = count;
	fortfloat *sec4 = new fortfloat[len4];

	cgribex(ksec0,ksec1,ksec2,zsec2,ksec3,zsec3,ksec4,sec4,len4,(char*)buffer,&len,"C");

	delete[] sec4;

	data = buffer + offset;

	for(int j = 0;j<nx;j++)
		for(int k = 0;k<ny;k++)
			(*this)(j,k) = (*i)(j*scale,k*scale);

	Unmap(); i->Unmap();
#else

grib_api_port_missing("Image::Image(Image*,long)");
return;

#endif
}

//=============================================================

class ImageReduce : public Function {
public:
	ImageReduce(char *n) : Function(n,2,timage,tnumber)
	{info = "Reduces the size of an image by a given scaling factor.";}
	virtual Value Execute(int arity,Value *arg);
};

Value ImageReduce::Execute(int ,Value *arg)
{
	Image *i;
	double d;
	arg[0].GetValue(i);
	arg[1].GetValue(d);
	return Value(new CImage(new Image(i,d)));
}

//=============================================================

static void install(Context *c)
{

	int i;

	for(i=0;BinOps[i].symb;i++)
		c->AddFunction(new ImageBinOp(BinOps[i].symb,BinOps[i].proc ));

// Note : MulOps for BinOp

	for(i=0;MulOps[i].symb;i++)
		c->AddFunction(new ImageBinOp(MulOps[i].symb,MulOps[i].proc ));

	for(i=0;UniOps[i].symb;i++)
		c->AddFunction(new ImageUnOp(UniOps[i].symb,UniOps[i].proc ));

	c->AddFunction(new ImageConvol("convolution"));
	c->AddFunction(new ImageReduce("reduce"));
	c->AddFunction(new ImageLut("filter"));
}

static Linkage linkage(install);

