/***************************** 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 *************************************/

#ifndef VALUE_H__
#define VALUE_H__

//#pragma once

#include <inc_iostream.h>
#include <vector>
#include "date.h"
/* #define USEMARS */

// Define needed on Solaris
#define vtype _vtype

//GNU 2.96: type clash!
//typedef argtype _vtype; // from mars.h
typedef int _vtype;

class Value;

using std::string;

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

class Notify {

public:
   Notify() {}
   ~Notify() {}

   virtual void trigger(void*) = 0;
};

class Reference {
	int     count;
	Notify  *notify;

	void suicide()    {  if(notify) notify->trigger(this); delete this; }

public:
	int RefCount(void)        { return count;                 }
	Reference()               { count = 0; notify = 0;        }
	void Attach(void)         { count++;                      }
	void Detach(void)         { 
		if(--count <= 0) 
			suicide();  
	}
	void SetNotify(Notify *n) { notify = n;                   }
	virtual ~Reference() {};

//============================================================================
	void* operator new(size_t s)   { return fast_new(s,transient_mem); }
	void  operator delete(void *p) { fast_delete(p,transient_mem);     }

};

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

class CNil;
class CList;
class CGrib;
class CImage;
class CMatrix;
class CVector;
class CBufr;
class CGeopts;
class CFile;
class Image;
class CNetCDF;
class CTable;
class CObject;
#ifdef METVIEW_ODB
class COdb;
#endif

class Content : public Reference {

	// No copy
	Content(const Content&);	
	void operator=(const Content&);

	const char *TypeName(void);
	vtype type;

	void Fail(const char *s) ;

protected:
	// Utility
	static int  CopyFile(const char *fname,FILE *to);
	static int  Error(const char *fmt,...);

public:

	static void Perl(request*);

	virtual void Sync(Value*)     {};

	Content(vtype t)  : type(t)     { }


	virtual int  Write(FILE*);
	virtual vtype GetType(const char**n) { if(n) *n = TypeName();return type; };
//	virtual void Print(void)       { cout << form("<%s>",TypeName());};
	virtual void Print(void)       { cout << TypeName();}
	virtual Content* Clone(void)   { Fail("Clone");return this; };

	virtual void ToNumber(double     &x) { Fail("double")   ; x=0;};
	virtual void ToDate(Date         &x) { Fail("date")     ; x=0.0;};
	virtual void ToString(const char*      &x) { Fail("string")   ; x=0;};
	virtual void ToRequest(request*  &x) { Fail("request")  ; x=0;};
	virtual void ToFieldset(fieldset*&x) { Fail("fieldset") ; x=0;};
	virtual void ToList(CList*       &x) { Fail("list")     ; x=0;};
	virtual void ToGrib(CGrib*       &x) { Fail("grib")     ; x=0;};
	virtual void ToBufr(CBufr*       &x) { Fail("bufr")     ; x=0;};
	virtual void ToGeopts(CGeopts*   &x) { Fail("geopoints"); x=0;};
	virtual void ToImage(Image*      &x) { Fail("image")    ; x=0;};
	virtual void ToMatrix(CMatrix*   &x) { Fail("matrix")   ; x=0;};
	virtual void ToVector(CVector*   &x) { Fail("vector")   ; x=0;};
	virtual void ToFile(CFile*       &x) { Fail("file")     ; x=0;};
	virtual void ToNetCDF(CNetCDF*   &x) { Fail("netcdf")   ; x=0;};
	virtual void ToTable(CTable*     &x) { Fail("table")    ; x=0;};
	virtual void ToObject(CObject*   &x) { Fail("object")   ; x=0;};
#ifdef METVIEW_ODB 
	virtual void ToOdb(COdb*   &x) { Fail("odb"); x=0;};
#endif
	virtual void Dump(int);
	virtual void Dump1() { Print(); }
	virtual void Dump2() { Print(); }

	int Check(int,Value&,int,Value*,vtype,int,...);
	virtual void SetSubValue(Value&,int,Value*) { Fail("Index");};
	//
};

//=============================================================================
// Classes that are in pool, like grib or other

class InPool : public Content {

protected:
	boolean isIcon; // If it is an icon form MetviewUI
	char    *iconName;

public:

	InPool(vtype t,request *r);
	InPool(vtype t,const char    *n = NULL);
	~InPool();

	//SetName(char *n);
	const char *GetName(void)    { return iconName;}
	boolean IsIcon()       { return isIcon; }
	void IsIcon(boolean n) { isIcon = n;}
};

//=============================================================================
class CNumber : public Content {
	double number;
	static long sPrec;
	static long sDefaultPrec;
	virtual void ToRequest(request* &);
	virtual void ToString(const char* &);
	virtual void ToNumber(double &x) { x = number; };
//	virtual void Print(void)  { cout << form("%g",number); };
	virtual void Print(void); //  { cout << number;            };
	virtual int  Write(FILE*);
public:
	CNumber(double d) : Content(tnumber) { number = d; };
	static void SetPrecision(long p) { sPrec = p; };
	static long GetPrecision() { return sPrec; };
	static long GetDefaultPrecision() { return sDefaultPrec; };
};
//=============================================================================
class CDate : public Content {
	Date date;
	virtual void ToNumber(double &);
	virtual void ToString(const char* &);
	virtual void ToDate(Date &x) { x = date; };
	virtual void Dump2();
	virtual void Print(void)  { date.Print(); };
public:
	CDate(Date d) : Content(tdate) { date = d; };
};
//=============================================================================
class CString : public Content {
	char *string;
	virtual void ToRequest(request* &);
	virtual void ToString(const char* &x) { x = string; };
	virtual void Print(void)  { cout << string; };
	virtual void Dump1();
	virtual void Dump2();
	virtual int  Write(FILE*);
public:
	CString(const char* s) : Content(tstring) { string = strcache(s); };
	~CString() { strfree(string); };
};

//=============================================================================
// Should maybe be a InPool

class CRequest : public Content
{
	request *r;
	virtual void ToRequest(request* &x) { x = r; };
	virtual void Print(void);
	virtual void Dump2(void);
	virtual Content *Clone(void)       { return new CRequest(r); };
	virtual void SetSubValue(Value&,int,Value*);
	virtual int  Write(FILE*);

public:

	void SetRequest(request *s)             {free_all_requests(r);
											 r = clone_all_requests(s);};
	request *GetRequest(void)               { return r;                  };
	CRequest(request *s):Content(trequest)  { r = clone_all_requests(s); };
	~CRequest()                             { free_all_requests(r);      };
};

//=============================================================================
class CError : public Content {

	err error;
	char *msg;

	virtual void Print(void);
	virtual void ToRequest(request* &x);

public:
	CError(err e,const  char *m) : Content(terror) 
		{ error = e; msg = strcache(m);};
	~CError()               { strfree(msg); }
};

//=============================================================================
class CNil : public Content {
	 virtual void ToRequest(request* &x) { x = NULL; }
	 virtual void Dump2();
public:
	CNil() : Content(tnil) {};
};

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

class CGrib  : public InPool {

   fieldset  *fs;
   hypercube *cube;
   string    path_;  // It stores an absolute path. This is important
                     // specially if request parameter PATH contains
                     // a relative path. 
   
   virtual void ToFieldset(fieldset* &x)
      { x = fs; }

   virtual void ToRequest(request* &);
   virtual void ToGrib(CGrib* &x)
      { x = this; }

   virtual void Print(void);
   virtual int  Write(FILE*);
   virtual Content *Clone(void);
   virtual void SetSubValue(Value&,int,Value*);
   virtual void Dump2();
   void DestroyContent();

public:
   CGrib(const char*);
   CGrib(request*);
   CGrib(fieldset*);

   ~CGrib();

   fieldset *GetFieldset(void)
      { return fs; };
      
   void SetFileTempFlag(boolean);
   hypercube* get_cube();
};


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

class Image;
class CImage  : public InPool {
/*
	int   temp;
	const char *path;
	char *buffer;
	long length;
	long offset;
	long size;
	long width;
	long height;
	*/
	Image *image;
	virtual void ToRequest(request* &);
	//virtual void ToImage(CImage* &x)   { x = this; };
	virtual void ToImage(Image* &x)   { x = image; };
	virtual int  Write(FILE*);
public:
	//CImage(CImage*);
	CImage(Image*);
	CImage(request  *r);
	~CImage();
};

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

class ASync : public CRequest {

	friend class Value;

	static void Input(svcid*,request*,void*);
	static void Reply(svcid*,request*,void*);
	static void Definition(svcid*,request*,void*);
	static void Progress(svcid*,request*,void*);

	boolean Ready;
	virtual void Sync(Value*);
	void   Print(void) { cout << "<waiting>"; };

public:

	static svc  *Svc;
	static int   RequestMax;
	static int   RequestCnt;
	static char *Name;

	static void Connect(void);
	static void IconStatus(const char *,const char *,const char * = NULL);
	static void Disconnect(void) { if(Svc) destroy_service(Svc); Svc = NULL;};
	static void     Store(const char *name,request *r);
	static request *Fetch(const char *name);

	ASync(const char *n,request *s);
	request *Wait(void);
	void SetAttachedContent(std::vector<Content *> v) {attachedContent = v;};
    void AvoidDuplication(Content *c);

	virtual void ToRequest(request* &x) { x = Wait();};

private:

	// asynchronous requests require a bit more effort for cleaning up any
	// temporary files that they use; if data is passed to another module,
	// Macro should not delete the underlying data until it knows that the
	// module has finished with it, ie the module has finished. So for each
	// asynchronous request, we store a list of data (Content *) which has been
	// passed to it; this can then be destroyed (or at least have its reference
	// counter decremented) when the module returns. GRIB presents an additional
	// problem because the GRIB filter module can return data which is itself
	// dependent on the input data - we still do not deal with this correctly,
	// so we simply allow that data to remain.

	std::vector<Content *> attachedContent;

};

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

class CList;

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

class Value {

	Content *c;

	void CleanUp(void);
	void Copy(const Value&);

	void _s(void) { c->Sync(this); }
public:

	Value()               { c = new CNil;       c->Attach(); }
	Value(double x)       { c = new CNumber(x); c->Attach(); }
	Value(const Date& d)  { c = new CDate(d);   c->Attach(); }
	Value(request *r)     { c = new CRequest(r);c->Attach(); }
	Value(fieldset *v)    { c = new CGrib(v);   c->Attach(); }
	Value(const char *p)        { c = new CString(p); c->Attach(); }
	Value(Content *x)     { c = x;              c->Attach(); }
	Value(err e,const char* m)  { c = new CError(e,m);c->Attach(); }

	Value(const char*,request*);
	Value(const char*,request*,std::vector<Content *>);
	Value(const Value&);
	void SetContent(Content *x); 
	void SetContent(request *r); 
	Content *GetContent(void)  { return c; };

	void Write(void)  { if(c->RefCount()>1) SetContent(c->Clone()); };

	~Value();

	void   Sync() { _s(); }

	Value& operator=(const Value&);
	void   SetSubValue(Value& a,int b,Value* v){Write();c->SetSubValue(a,b,v); }
	vtype  GetType(const char**name = NULL) { _s();return c->GetType(name);};

	void GetValue(int       &x) { double d;c->ToNumber(d); x = (int)d;  };
	void GetValue(long      &x) { double d;c->ToNumber(d); x = (long)d; };
	void GetValue(double    &x) { c->ToNumber(x);  };
	void GetValue(Date      &x) { c->ToDate(x);    };
	void GetValue(const char* &x) { c->ToString(x);};
	void GetValue(request*  &x) { c->ToRequest(x); };
	void GetValue(fieldset* &x) { c->ToFieldset(x);};
	void GetValue(CList*    &x) { c->ToList(x);    };
	void GetValue(CGrib*    &x) { c->ToGrib(x);    };
	void GetValue(CBufr*    &x) { c->ToBufr(x);    };
	void GetValue(CGeopts*  &x) { c->ToGeopts(x);  };
	void GetValue(CMatrix*  &x) { c->ToMatrix(x);  };
	void GetValue(CNetCDF*  &x) { c->ToNetCDF(x);  };
	void GetValue(CTable*   &x) { c->ToTable(x);   };
	void GetValue(CObject*  &x) { c->ToObject(x);  };
	void GetValue(CVector*  &x) { c->ToVector(x);  };
	void GetValue(Image*    &x) { c->ToImage(x);   };
	void GetValue(CFile*    &x) { c->ToFile(x);    };
#ifdef METVIEW_ODB
	void GetValue(COdb*  &x) { c->ToOdb(x);  };
#endif
	void Print(void)    { c->Print(); };
	void Dump(int trace)     { c->Dump(trace); };
	int  Write(FILE* f) { return c->Write(f); }

	static const char *TypeName(vtype);
	static vtype NameType(const char*);

};

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

class CList : public Content {
	int    count;
	Value *values;

	void Copy(const CList&);
	void CleanUp(void)       { delete[] values;                 };
	virtual void ToList(CList* &x) { x = this; };
	virtual void ToRequest(request* &);
	virtual int  Write(FILE*);
	virtual Content* Clone(void);
	virtual void SetSubValue(Value&,int,Value*);

public:

	CList(const CList&);
	CList& operator=(const CList&);

	CList(int n) :Content(tlist)         { count = n; values = new Value[n]; };
	~CList()                 { CleanUp();                        };
	Value& operator[](int n) { return values[n];                 };

	int    Count(void)       { return count;  };
	Value* Values(void)      { return values; };
	void   Add(const Value& v);

	void Print(void);
	void Dump2(void);
};

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

// A note about VECTOR_MISSING_VALUE: this is currently set to 
// mars.grib_missing_value so that we can efficiently copy field values
// directly to a vector without having to do any conversion on the missing
// values. The macro functions gridvals() and set_gridvals() make this
// assumption; therefore, if VECTOR_MISSING_VALUE is changed, then we would
// need to revise these functions so that they consider missing values
// more carefully. Functions that convert geopoints values into vectors
// already have to do a conversion.

#define VECTOR_MISSING_VALUE  mars.grib_missing_value

class CVector : public Content {
	int     size;
	double *values;
	void    Copy(const CVector&);
	void    CleanUp(void)                 { if (values) delete[] values; };

	virtual int  Write(FILE*);
	virtual void Dump(int);
	virtual void Print(void);
	virtual void ToRequest(request* &);
	virtual void ToVector(CVector* &x)    { x = this; };
	virtual Content *Clone(void)       { return new CVector(*this); };
	virtual void SetSubValue(Value&,int,Value*);

public:
	enum PercentileInterpolationType {PERCENTILE_NEAREST};
	CVector(int n, bool zeroValues = false);
	CVector(std::vector<double> &v);
	CVector(request *r);
	~CVector() {  CleanUp(); }
	CVector(const CVector& x) : Content(tvector),values(0) { Copy(x);};
	CVector&  operator=(const CVector& x) { CleanUp(); Copy(x); return *this;};
	void CopyValues(int targetIndex, const CVector& source, int sourceIndex, int numValues);
	int IndexOfFirstValidValue();
	void Resize(int newSize);
	void Sort(char op);
	void SortIndices(char op);
	void Percentile(CVector &percs, PercentileInterpolationType interp, CVector &result);

	double&  operator[](int n)            { return values[n]; };
	int      Count(void) const            { return size;      };

};

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

class CMatrix : public Content {
	int     row;
	int     col;
	double *values;

	void    Copy(const CMatrix&x);
	void    CleanUp(void)              { delete[] values;  };
	virtual void ToMatrix(CMatrix* &x) { x = this; };

	virtual Content *Clone(void)       { return new CMatrix(*this); };
	virtual void SetSubValue(Value&,int,Value*);

public:
	virtual void Print(void);
	virtual void Dump(int);
	CMatrix(int r,int c);
	CMatrix(const CMatrix& x) : Content(tmatrix) { Copy(x);};
	CMatrix& operator=(const CMatrix& x)   { CleanUp(); Copy(x); return *this;};
	~CMatrix()                                   { CleanUp(); };

#if 0
	double&  operator()(int r,int c)    { return values[r+c*row]; };
#else
	double&  operator()(int r,int c)    { 
	if(r<0 || r>=row) {marslog(LOG_INFO,"Bad row %d",r);abort();}
	if(c<0 || c>=col) {marslog(LOG_INFO,"Bad col %d",c);abort();}
	int x = r+c*row;
	if(x < 0 || x>= row*col) {marslog(LOG_INFO,"Bad x %d",x);abort();}
	return values[r+c*row]; };
#endif
	int      Row(void)                  { return row;     };
	int      Col(void)                  { return col;     };
	virtual int  Write(FILE*);
};

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

class Context;
class CObject  : public Content {

	char*    name;
	Context* context;

	Value    members;
	Value    defaults;
	Value super();
	Context* code() { return context; }



	void SetContext(CObject*);
	void GetContext();

	// virtual void ToRequest(request* &);
	virtual void Print(void);
	virtual Content *Clone(void);
	// virtual void Dump2();

	virtual void ToObject(CObject*     &x) { x = this;  }

public:
	CObject(const char *,Context*);
	~CObject();

	CObject(const CObject&);
	CObject& operator=(const CObject&);

	Value Method(const char*,int,Value*);

	void GetInheritance(Context**,int& count);
};

#endif
//VALUE_H__

