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

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

#include "IconClass.h"

#include "ConfigLoader.h"
#include "Service.h"
#include "IconInfo.h"
#include "IconObject.h"
#include "IconFactory.h"
#include "Items.h"
#include "Folder.h"
#include "Language.h"
#include "MvRequestUtil.hpp"
#include "MvScanFileType.h"

#include <algorithm>

static map<string,const IconClass*> classes;

// All icon classed subclass from this one
static IconClass super("*",0);

static set<string> allActions;

IconClass::IconClass(const string& name,request* r,const IconClass* sup):
	super_(sup),
	name_(name),
	request_(r)
{
	classes[name] = this;
	if(!super_ && this != &super)
		super_ = &super;
}

IconClass::~IconClass()
{
	// Never called
	classes.erase(name_);
	free_all_requests(request_);
}

void IconClass::scan(ClassScanner& s)
{
	map<string,const IconClass*>::iterator j;
	for(j = classes.begin(); j != classes.end() ; ++j)
		s.next(*(*j).second);
}

const string& IconClass::name() const
{
	return name_;
}

bool IconClass::isValid(const string& name)
{
  	map<string,const IconClass*>::iterator j = classes.find(name);
	return (j != classes.end());
}  

const IconClass& IconClass::find(const string& name)
{
	 map<string,const IconClass*>::iterator j = classes.find(name);
	 if(j != classes.end())
		return *(*j).second;

	 return *(new IconClass(name,empty_request(0)));
}

const IconClass& IconClass::find(const string& name,const string& defaultType,bool caseSensitive)
{
  	 if(caseSensitive)
	 {
	   	map<string,const IconClass*>::iterator j = classes.find(name);
	 	if(j != classes.end())
			return *(*j).second;
	 }
	 else
	 {
	   	for(map<string,const IconClass*>::iterator it=classes.begin(); it != classes.end(); it++)
		{
		  	//We convert the class name to uppercase for comparison
		  	std::string str=it->first;
		  	std::transform(str.begin(),str.end(),str.begin(),::toupper);
	  		std::string ucName=name;
			std::transform(ucName.begin(),ucName.end(),ucName.begin(),::toupper);
			if( str == ucName)
			{
				return *(*it).second;
			}
		}
	 }
	 
	 return find(defaultType);
}

void IconClass::find(const string& str,vector<const IconClass*>& res,bool onlyCreatable)
{  
	for(map<string,const IconClass*>::iterator it=classes.begin(); it != classes.end(); it++)
	{	  
		const IconClass* kind=it->second;
		
		if(onlyCreatable && kind->canBeCreated() == false)
			continue;  
		
		string s;
		
		string strTerm=str;
		std::transform(strTerm.begin(), strTerm.end(), strTerm.begin(), ::tolower);
		
		//Default name
		s=kind->defaultName();
	  	std::transform(s.begin(), s.end(), s.begin(), ::tolower);
		if(s.find(strTerm) != string::npos)
		{
		  	res.push_back(kind);
			continue;
		}
		
		//Name
		s=kind->name();
	  	std::transform(s.begin(), s.end(), s.begin(), ::tolower);
		if(s.find(strTerm) != string::npos)
		{
			res.push_back(kind);
			continue;
		}
		
		//Type
		s=kind->type();
	  	std::transform(s.begin(), s.end(), s.begin(), ::tolower);
		if(s.find(strTerm) != string::npos)
		{
		  	res.push_back(kind);
			continue;
		}
		
		//icon box
		s=kind->iconBox();
	  	std::transform(s.begin(), s.end(), s.begin(), ::tolower);
		if(s.find(strTerm) != string::npos)
		{
		  	res.push_back(kind);
			continue;
		}
	}
}

void IconClass::load(request* r)
{
	new IconClass(get_value(r,"class",0),r);
}

bool IconClass::isSubClassOf(const IconClass& other) const 
{
	return this == &other;
}

string IconClass::editor() const
{
	const char* e = get_value(request_,"editor_type",0);
	return e ? e : "NoEditor";
}

bool IconClass::canBecome(const IconClass& other) const 
{
	if ( isSubClassOf(other) ) return true;
	
	for(set<const IconClass*>::const_iterator k = outputs_.begin();
		k != outputs_.end(); ++k)
	{
	        if ( 
		(*k)->canBecome(other) ) return true;	
	}
	return false;
}

//The list of supported actions (can_) is now cached! We rely on the fact that when can() is
//called for the first time all the initailisation has been done! We also suppose that there is
//at least one action defined for any of the classes so after the first call of can() can_cannot be 
//empty!
set<string> IconClass::can() const
{
	if(can_.empty())
	  	can_=generateCan();
	
	return can_;
}

//Generate a list of the supported actions (can_) 
set<string> IconClass::generateCan() const	
{
	set<string> c;
  	
	//First check if all the actions are allowed for the class
	static Action all("*","*");
	if(services_.find(all) != services_.end())
	{
	  	c=allActions;
		
		//Now we need to remove some actions if they are not defined explicitly for this 
		//class or its output classes in OnjectList
		static std::set<string> nonAllActions;
		if(nonAllActions.empty())
		{
	  		nonAllActions.insert("extract");
			nonAllActions.insert("analyse");
			nonAllActions.insert("save");
			nonAllActions.insert("clear");
			nonAllActions.insert("visualise");
			nonAllActions.insert("examine");
		}	
	
		//Loop for the actions to be checked
		for(set<string>::const_iterator it = nonAllActions.begin(); it != nonAllActions.end(); it++)
		{		  	
			bool need=false;
			
			//Flag the action if it is not present in the services!			
			Action nonAllAc(*it,"*");
			map<Action,Service*>::const_iterator itS=services_.find(nonAllAc);
			if(itS != services_.end())
			{
			  	need=true;
			  	continue;
			}
			
			//Flag the action if it is not present in the otput classes
			for(set<const IconClass*>::const_iterator k = outputs_.begin();
				k != outputs_.end(); ++k)
			{
				// cout << name_ << " can, adding " << (*k)->name_ << endl;
				set<string> oc = (*k)->can();
				set<string>::const_iterator itIcs=oc.find(*it);
				if(itIcs != oc.end())
				{
				  	need=true;
				  	break;
				}
			}
			
			//If the action is flagged we remove it
			if(!need)
				c.erase(*it);
			  
		}
	}	
		
	//Otherwise compiles a set of action from services and outpusts etc.
	else
	{  
		if(super_)
		{
	  		c = super_->can();
		}	
	
		for(map<Action,Service*>::const_iterator j = services_.begin(); 
			j != services_.end(); ++j)
	  	{
		  		c.insert((*j).first.name());
		}	
		
			
		for(set<const IconClass*>::const_iterator k = outputs_.begin();
			k != outputs_.end(); ++k)
		{
			// cout << name_ << " can, adding " << (*k)->name_ << endl;
			set<string> oc = (*k)->can();
			c.insert(oc.begin(),oc.end());
		}	
	}
	
	//Check editor
	if(editor() == "NoEditor")	
		c.erase("edit");
	else
	  	c.insert("edit");
		
	return c;
}

void IconClass::service(const Action& action,const IconClass* output,Service* s)  const
{
	IconClass* self = const_cast<IconClass*>(this);
	if(output) self->outputs_.insert(output);

	if(services_.find(action) != services_.end())
		return;

	allActions.insert(action.name());

	//cout << name_ << " can " << action << " ";
	//if(output) cout << " output " << output->name_;
	//cout << " with " << *s << endl;

	self->services_[action] = s;
}

Service* IconClass::service(const Action& action) const
{
	//cout << "IconClass::service class " << name_ << endl;
	//cout << "IconClass::service for " << action << endl;
	map<Action,Service*>::const_iterator j = services_.find(action);
	if(j == services_.end())
	{
		Service* s = super_?super_->service(action):0;
		if(s == 0 && !(action.name() == "*"))
			return service(Action("*",action.mode()));
		else
			return s;
	}
	 cout << * ( (*j).second ) << endl;
	return (*j).second;
}

static SimpleLoader<IconClass> loadClasses("object",0);

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


#if 0
//
// stuff moved to library
//


struct File {            //-- wrapper function always closes the file

	FILE *f_;

public:
	File(const char *name,char *mode) { f_ = fopen(name,mode); };
	~File()                     { if(f_) fclose(f_);   };
};

//
// NOTE: a close cousin of scan_file(...) is in ./src/libMars/guess.c !!!
//

static const char *scan_file(const char *file)
{

  const int cMAXSHIFT = 8; //-- search tolerance for word "GRIB" or "BUFR"
  const int cMINREAD  = 4; //-- need to be able to read at least this much

	bool isBinary = IsBinaryOrMissingFile( file );

	File f(file,"r");
	if(!f.f_)
	  return "BAD";                 //-- missing or non-readable file

	union {
		char  c;
		short s;
		long  l;
		char  ch[ cMAXSHIFT + 4 ];
	} buf;
	memset(&buf,0,sizeof(buf));

	int readCount = fread((char*)&buf,1,sizeof(buf),f.f_);
	
	if( readCount < cMINREAD )      //-- if not enough bytes for safe tests
	  {
	    return isBinary ? "BINARY" : "NOTE";
	  }


	if( isBinary )                  //-- first check for binary types
	  {
	    for( int s=0; s <= cMAXSHIFT; ++s ) //-- for GRIB & BUFR allow some leading rubbish
	      {
		if(strncmp(buf.ch+s,"GRIB",4) == 0) 
		  return "GRIB";

		if(strncmp(buf.ch+s,"BUFR",4) == 0) 
		  return "BUFR";
	      }

	    if(strncmp(buf.ch,"TIDE",4) == 0) 
	      return "GRIB";

	    if(strncmp(buf.ch,"BUDG",4) == 0) 
	      return "GRIB";


	    if(strncmp(buf.ch,"CDF",3) == 0) 
	      return "NETCDF";

	    if(strncmp(buf.ch,"V5D",3) == 0) 
	      return "VIS5D_FILE";

	    if(strncmp(buf.ch+1,"PNG",3) == 0) 
	      return "PNG";

	    if(strncmp(buf.ch+6,"JFIF",4) == 0) 
	      return "JPEG";                //--  JPEG - JFIF

	    if(strncmp(buf.ch+6,"Exif",4) == 0) 
	      return "JPEG";                //--  JPEG - EXIF/DCF
	  }

	if( isBinary )                  //-- rest of the known types are all text files
	    return "BINARY";            //-- thus this one is of unknown binary type


	                                //-- check for text types that cannot be requests
	if(strncmp(buf.ch,"%!",2) == 0) 
	  return "PSFILE";

	if(strncmp(buf.ch,"#!",2) == 0) 
	  return "SHELL";

	if(strncmp(buf.ch,"#GEO",4) == 0) 
	  return "GEOPOINTS";

	if(strncmp(buf.ch,"#LLM",4) == 0) 
	  return "LLMATRIX";

	if(strncmp(buf.ch,"#LLV",4) == 0) 
	  return "LLVALUE";

	if(strncmp(buf.ch,"#MACRO",6) == 0)
	  return "MACRO";

	if(strncmp(buf.ch,"#Metview",8) == 0)
	  return "MACRO";

	if(strncmp(buf.ch,"# Metview",9) == 0)
	  return "MACRO";

	if(strncmp(buf.ch,"<magics>",8) == 0) 
	  return "MAGML";

	                                //-- check if a Metview request

	                                     //-- WARNING (2004-05-10):
	request *r = read_request_file(file);//-- a file NOT ending in CR can crash parser!!!
	if(r != 0)
	  {
	    string name = r->name?r->name:"";
	    free_all_requests(r);

	    map<string,const IconClass*>::iterator j = classes.find(name);
	    if(j != classes.end())
	      {
		return (*j).second->name().c_str();  //-- it is Metview request
	      }
	  }


	rewind(f.f_);

	unsigned char c = ' ';          //-- look for a Vis5D control script
	char line[1024];
	int i = 0;
	line[0] = 0;
	while(!feof(f.f_))
	  {
	    fread((char*)&c,1,1,f.f_);

	    if(i == sizeof(line) || c == '\n')
	      {
		i = 0;
		if(strncmp("vis5d_",line,6) == 0)
		  return "VIS5D_SCRIPT";
	      }
	    else
	      line[i++] = c;
	  }


	fclose( f.f_ );                 //-- destructor will not close (end-of-file => !f.f_)
	f.f_ = 0;                       //-- but after fclose destructor would try and...

	return "NOTE";                  //-- unknown text file; make it NOTE
}


static string guess_file(const char* file)
{

	struct stat buf;
	
	if(stat(file,&buf) <0)
		return "BAD";

	switch(buf.st_mode & S_IFMT)
	{
		case S_IFDIR:
			return "FOLDER";

		case S_IFREG:
			return scan_file(file);

		default:
			return "SPECIAL";
	}
	
}
#else

const string missing("///");

#if 0

static bool is_request(const char* file, string& s)
{
    const off_t LARGEST_REQUEST_FILE = 1024*1024;

	                                //-- check if a Metview request
	s = missing;


    // first check whether this file is abnormally large, because it it is,
    // then read_request_file() will spend ages trying to parse it (if it
    // looks like a valid request file).
    
    Path file_path(file);
    off_t size = file_path.sizeInBytes();
    
    if (size < LARGEST_REQUEST_FILE)
    {
	                                         //-- WARNING (2004-05-10):
	    request *r = read_request_file(file);//-- a file NOT ending in CR can crash parser!!!
	    if(r != 0)
	      {
	        string name = r->name?r->name:"";
	        free_all_requests(r);

	        map<string,const IconClass*>::iterator j = classes.find(name);
	        if(j != classes.end())
	          {
		    s = (*j).second->name().c_str();  //-- it is Metview request
	          }
	      }
        }

	return s != missing;
}

#endif

static bool is_request(const char* file, string& s)
{
  	if(!file)
	   return false;
  
	ifstream in(file);
	
	int maxLines=5;
	int actLine=0;
	string typeStr;
	
	if(in.good())
	{		
		string line;		
		while(getline(in,line) && actLine < maxLines)
		{
			vector<string> vec;
		        stringstream sst(line);
			string s;
			while(sst >> s)
			{
		  	   vec.push_back(s);				
			} 
			
			if(vec.size() >= 1 && vec.at(0).substr(0) != "#")
			{
			  	typeStr=vec.at(0);
			  	if(typeStr.find(",") == typeStr.size()-1)
				{
				  	typeStr=typeStr.substr(0,typeStr.size()-1);
				}
				
				//We convert the string to uppercase for comparison
				std::transform(typeStr.begin(), typeStr.end(), typeStr.begin(), ::toupper);				
				break;
			}
			
			actLine++;
		}	
	}
	
	//Close the file
	in.close();
			
	if(!typeStr.empty())		  
	{
		for(map<string,const IconClass*>::iterator it=classes.begin(); it != classes.end(); it++)
		{
		  	//We convert the class name to uppercase for comparison
		  	std::string str=it->first;
		  	std::transform(str.begin(),str.end(),str.begin(),::toupper);
	  		if( str == typeStr)
			{
			  	s=it->first;
				return true;
			}
		}
	}	
        return false;		
}

static string guess_file(const char* file)
{
	struct stat buf;
	
	if(stat(file,&buf) <0)
		return "BAD";

	switch(buf.st_mode & S_IFMT)
	{
		case S_IFDIR:
		     return ScanFileType(file);
		
		case S_IFREG:
			//return scan_file(file);
			{
			  string ft = ScanFileType(file);
			  if( ft != "NOTE" )
			    return ft;

			  string ft2;
			  if( is_request( file, ft2 ) )
			    return ft2;
			  else
			    return ft;
			}

		default:
			return "SPECIAL";
	}
	
}

#endif

const IconClass& IconClass::guess(const Path& file)
{
	return guess(file.str());
}

const IconClass& IconClass::guess(const string& file)
{
	string kind = guess_file(file.c_str());
	return find(kind);
}

//We need to ensure that this call does not create a new iconClass. So it could be called from another thread.
//On top of that we cannot call anything that requires mars parsing because it is not safe!
void IconClass::guess(const std::string& fullName,std::string& icName,std::string& icType)
{
	if(!icName.empty())
	{	  	
		const IconClass& ic=IconClass::find(icName,"NOTE",true);
  		icType=ic.type();	
	}
	else
	{
	  	//try dot file
		Path icp(fullName);
		Path dot=icp.dot();
		
		//We need to parse the dot file by ourselves because requests cannot be used here (they are not thread safe).
		if(dot.exists())
		{
			ifstream in(dot.str().c_str());
			string str;
			while(getline(in,str))
			{
				if(str.find("ICON_CLASS") != string::npos)
				{
				  	std::size_t pos=str.find("=");
					if(pos != string::npos && pos < str.size()-1)
					{
					  	str=str.substr(pos+1,str.size());
						string type;
						for(unsigned int i=0; i < str.size(); i++)
						{
						  	if(str[i] != ' ' && str[i] != ',' && str[i] != '\n')
							  	type+=str[i];
						}	
						
						const IconClass&  ic = IconClass::find(type,"NOTE",false);
						icName=ic.name();
						icType=ic.type();
						
					}	
					break;  
				}
			}	
			in.close();	  	
		}
		//If there is no dotfile we guess the file type
		else
		{	
		  	//::guesss() does not create a new iconclass!
			const IconClass& ic=IconClass::guess(fullName);
			icName=ic.name();
			icType=ic.type();
		}
	}
}

IconObject* IconClass::createOne(Folder* f) const
{
	return createOne(f,IconInfo::undefX(),IconInfo::undefY());
}

IconObject* IconClass::createOne(Folder* f,int x,int y) const
{
	IconObject *obj=defaultObject();
    if(obj)
        return obj->clone(f,x,y,false);
    
    return 0;
}

IconObject* IconClass::createOneFromRequest(Folder* f) const
{
	return createOneFromRequest(f,IconInfo::undefX(),IconInfo::undefY());
}

IconObject* IconClass::createOneFromRequest(Folder* f,int x,int y) const
{
	Request r(name_);
  	return IconFactory::create(f,r,x,y);
}

IconObject* IconClass::defaultObject() const
{
	Folder* f       = Folder::folder("defaults");
	string name=defaultName();
	
	IconObject *obj=f->find(name);
	if(obj)
		return obj;
	else	  	
		return IconFactory::create(f,name,*this);
		//return createOneFromRequest(f);
	
	return 0;
}

string IconClass::defaultName() const
{
	const char* def = get_value(request_,"default_name",0);
	return def ? string(def) : name_;
}

string IconClass::iconBox() const
{
	const char* def = get_value(request_,"icon_box",0);
	return def ? string(def) : string();
}

string IconClass::helpPage() const
{
	const char* def = get_value(request_,"help_page",0);
	return def ? string(def) : name_;
}

string IconClass::doubleClickMethod() const
{
  	const char* def = get_value(request_,"doubleclick_method",0);
	return def ? string(def) : string();
}

string IconClass::defaultMethod() const
{
  	const char* def = get_value(request_,"default_method",0);
	return def ? string(def) : "edit";
}

bool IconClass::skipDepandancies(const string& action) const
{
  	const char* def = get_value(request_,"skip_dependancies",0);
	return def ? (strcmp(def,action.c_str()) == 0) : false;
}

Path IconClass::pixmap() const
{
  	const char* def = get_value(request_,"pixmap",0);
	if(def) 
	{  		
	  	Path p(def);
		string fname=p.name();
		string::size_type pos=fname.rfind(".icon");
		if(pos != string::npos)
		{
		   	fname=fname.substr(0,pos);
		}   	
	
		string s = getenv("METVIEW_DIR_SHARE");
		string svgName=s + "/icons_mv5/" +  fname + ".svg";
		
		Path psvg(s + "/icons_mv5/" +  fname + ".svg");
		if(psvg.exists())
		{  
			//cout << "svg " << psvg.str() << endl;
			return psvg;
		}
		else
		{	  
			Path pxpm(s + "/icons_mv5/" +  fname + ".xpm");
			if(pxpm.exists())
			{  
				//cout << "svg " << psvg.str() << endl;
				return pxpm;
			}
		}
		
		return Path("");
	}
	
	return Path("");
}

bool IconClass::canBeCreated() const
{  
	const char* def=get_value(request_,"can_be_created",0);
	if(def)
	{
	  	return (strcmp(def,"True") == 0 || strcmp(def,"true") == 0)?true:false;
	}
	
	return false;
}	

string IconClass::type() const
{
	const char* def = get_value(request_,"type",0);
	return def ? string(def) : name_;
}

Path IconClass::definitionFile() const
{
	const char* def = get_value(request_,"definition_file",0);
	if(def == 0) def = "/dev/null";
	return Path(def);
}

Path IconClass::rulesFile() const
{
	const char* def = get_value(request_,"rules_file",0);
	if(def == 0) def = "/dev/null";
	return Path(def);
}

long IconClass::expandFlags() const
{
	const char* def = get_value(request_,"expand",0);
	return def?atol(def):EXPAND_MARS;
}

Language& IconClass::language() const
{
	return Language::find(this);
}

// This should be in ObjectList...

// Visdef -> 1
// Data,File, Macro, Family, ..   -> 2
// View   -> 3
// Window -> 4

int IconClass::priority() const
{
	// Very, very UGLY if....

	string t = type();

	if(t == "Visdef") return 1;
	if(t == "View")   return 3;
	if(t == "Window") return 4;

	return 2;
}
