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

#include "inc_stl.h"

#ifndef IconClass_H
#include "IconClass.h"
#endif

#ifndef ConfigLoader_H
#include "ConfigLoader.h"
#endif

#ifndef Service_H
#include "Service.h"
#endif

#ifndef IconObject_H
#include "IconObject.h"
#endif

#ifndef IconFactory_H
#include "IconFactory.h"
#endif

#ifndef Folder_H
#include "Folder.h"
#endif

#ifndef Language_H
#include "Language.h"
#endif

#ifndef MvRequestUtil_H
#include "MvRequestUtil.hpp"
#endif

#ifndef MvScanFileType_H
#include "MvScanFileType.h"
#endif

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):
	request_(r),
	name_(name),
	super_(sup)
{
	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_;
}

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)));
}

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);
	printf("%s\n",e);
	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;
}

set<string> IconClass::can() const
{

	static Action all("*","*");
	if(services_.find(all) != services_.end())
		return allActions;

	set<string> c;
	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());
	}

	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("///");

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;
}

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);
			//return "FOLDER";

		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)
{
	string kind = guess_file(file.str().c_str());
	return find(kind);
}

IconObject* IconClass::createOne(Folder* f) const
{
	return defaultObject()->clone(f);
}

IconObject* IconClass::defaultObject() const
{
	Folder* f       = Folder::folder("defaults");
	return IconFactory::create(f,defaultName(),*this);
}

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

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();
}

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) return string(def);

	string s = getenv("METVIEW_DIR_SHARE");

	return s + "/icons/" +  name_ + ".icon";
}

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;
}
