/***************************** 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 <Assertions.hpp>
#include <MvPath.hpp>
#include <MvRequest.h>
#include <MvLanguage.h>
#include <MvRequestUtil.hpp>

#include "ObjectList.h"
#include "PlotModConst.h"

MvRequest&
ObjectList::Instance()
{
	static MvRequest objectList = mars.setup; // hides the global "mars.setup"

/*
	MvRequest reqObj = mars.setup; // hides the global "mars.setup"

	// Reads uPlot resources
	string filename(getenv("METVIEW_DIR_SHARE"));
	filename += "/etc/uPlotTable";
	MvRequest uPlotRes;
	uPlotRes.read(filename.c_str());

#if 0
int i = 0;
objectList.save("/tmp/dummy-user/data/ObjectList.txt");
objectList.rewind();
	while ( objectList )
	{
		i++;
		objectList.advance();
	}
	cout << "Number total resources = " << i << endl;


	i = 0;
uPlotRes.save("/tmp/dummy-user/data/uPlotTable.txt");
	uPlotRes.rewind();
	while ( uPlotRes )
	{
		i++;
		uPlotRes.advance();
	}
	cout << "Number total resources = " << i << endl;

i = 0;
objectList.rewind();
uPlotRes.rewind();
objectList = objectList + uPlotRes;
objectList.rewind();
	while ( objectList )
	{
		i++;
		objectList.advance();
	}
	cout << "Number total resources = " << i << endl;

objectList.rewind();
objectList.save("/tmp/dummy-user/data/ObjectList1.txt");
#else
//	Merge Mars setup and uPlot object list
	reqObj.rewind();
	uPlotRes.rewind();
	reqObj = reqObj + uPlotRes;
	static MvRequest objectList = reqObj;
#endif
*/
	return objectList;
}

MvRequest
ObjectList::Find ( const Cached& name, const Cached& classe)
{
	MvRequest& objectList = ObjectList::Instance();

	objectList.rewind();
	while ( objectList )
	{
		if ( objectList.getVerb() == name  &&
		     objectList ("class") == classe )
			return objectList.justOneRequest();

		objectList.advance();
	}

	return MvRequest ("EMPTY");
}

int
ObjectList::FindAll ( const Cached& name, MvRequest& list)
{
	MvRequest& objectList = ObjectList::Instance();
	objectList.rewind();
	int count = 0;
	while ( objectList )
	{
		if ( objectList.getVerb() == name )
		{
			list = list + objectList.justOneRequest();
			count++;
		}

		objectList.advance();
	}

	return count;
}

int
ObjectList::FindAll ( const Cached& name, const Cached& classe, MvRequest& list)
{
	MvRequest& objectList = ObjectList::Instance();
	objectList.rewind();
	int count = 0;
	while ( objectList )
	{
		if ( objectList.getVerb() == name &&
		     objectList("class") == classe )
		{
			list = list + objectList.justOneRequest();
			count++;
		}

		objectList.advance();
	}

	return count;
}

//
// -- METHOD :  Find
//
// -- PURPOSE: Given a parameter, the method finds the list of values 
//             associated to it. 
//
// -- INPUT :
//
// -- OUTPUT :  This method returns ALL the values
//               as a list in Mars Languade ( ex. "VALUE1/VALUE2/VALUE3" )
Cached
ObjectList::Find( const Cached& name, const Cached& classe, const Cached& param )
{
	MvRequest oneRequest = Find ( name, classe );
	Cached listValues = oneRequest( param );
	Cached oneValue;
	for ( int i=1; i<oneRequest.countValues( param ); i++ )
	{
		oneRequest.getValue( oneValue, param, i );
		listValues = listValues + "/" + oneValue;
	}

	return  listValues;
}

//
// -- METHOD :  FindMainRequestValue
//
// -- PURPOSE: Given a parameter, the method finds the dominant
//             request associated to the parameter, and returns
//             the associated value
//
//             The idea here is that there is always one dominant
//             request in a list of request (PLOT_SUPERPAGE, and
//             DROP are exemples of "dominant" requests).
//
// -- INPUT  :
//
// -- OUTPUT : 
Cached
ObjectList::FindMainRequestValue ( const Cached& requestVerb, 
				   const Cached& parameter )
{
	return Find ( "request", requestVerb, parameter );
}

// -- METHOD :  
//
// -- PURPOSE:   
//
// -- INPUT  :
//
// -- OUTPUT : 
Cached
ObjectList::FindGraphicsEngine ( const MvRequest& request )
{      
	// Option 1. Try to detect an Graphics Engine 
	//           associated with the subrequest
	Cached  geName = request ( "GRAPHICS_ENGINE" );
	if ( (const char*) geName ) // Found it
		return geName;

	// Option 2. Look for the default in the PM Table
	geName = Find ("graphics_engine", "Default", "choice");
	ensure ( (const char*) geName != 0);

	return geName;
}

Cached ObjectList::FindView(const MvRequest &request)
{
	// First check if there's any view field in the request.
	// This way an application can specify which view it wants
	// if the default view for the data unit is not the right one.
	Cached viewName = request("_VIEW");

	// If not, check the given view for data unit.
	if ( ! (const char *) viewName )
		viewName = Find("request",request.getVerb(),"view");
  
	if ( (const char *) viewName )
		return viewName;
	else
		return DEFAULTVIEW.c_str();
}

MvRequest
ObjectList::FindReqService(const char* classe, const char* action)
{
	const char* cval;
	bool found = false;

	// Find all requests associate to "class"
	MvRequest reqAll;
	FindAll("state",classe,reqAll);

	// Find service related to "action"
	while ( reqAll )
	{
		reqAll.iterInit( "action" );
		while ( reqAll.iterGetNextValue( cval ) )
		{
			if ( !action || strcmp ( cval, action ) == 0 )
			{
				found = true;
				break;
			}
		}

		if ( found )
			return reqAll.justOneRequest();

		reqAll.advance();
	}

	return MvRequest();
}

string
ObjectList::FindService(const char* classe, const char* action)
{
	// Find request 
	MvRequest req = FindReqService(classe,action);
	if ( req )
		return string(req("service"));

	return string("");
}

// -- METHOD :  
//
// -- PURPOSE:   
//
// -- INPUT  :
//
// -- OUTPUT : 
Cached
ObjectList::DefaultVisDefClass (const char* dataUnitVerb)
{
	require (dataUnitVerb != 0);

	// Find out the name of the VisDef from the Plot Mod Table which
	// contains the relation between DataUnits and VisDefs
	Cached defaultVisDefClass = Find ("dataunit", dataUnitVerb,"defaultVisDef");
	ensure ( (const char*) defaultVisDefClass != 0);

	return defaultVisDefClass;
}

// -- METHOD : CheckValidVisDef
//
// -- PURPOSE:
//
// -- INPUT  : 1. the name of a visDef 
//             2. the name of a data unit
//             3. the specific parameter to be checked (default=listVisDef)
//
// -- OUTPUT : Yes - The VisDef is accepted for that data unit
//             No -  The VisDef does not match the data unit 
bool
ObjectList::CheckValidVisDef ( const char* duClass, const char* vdClass, const char* vdPar )
{
	require ( duClass != 0 );
	require ( vdClass != 0 );

	// Retrieve the list of acceptable visdefs in the Object List
     const char* param = ( vdPar ) ? vdPar : "listVisDef";
	Cached listVisDef = Find ("dataunit", duClass, param);

     // If no visdefs found, try the default key "listVisDef" 
     if ( (const char*)listVisDef == 0 && vdPar )
          listVisDef = Find ("dataunit", duClass, "listVisDef");

	ensure ( (const char*)listVisDef != 0);
	
	// Verify if the visDefClass belongs to the VisDefList
	return strstr( (const char*)listVisDef, vdClass );
}

bool
ObjectList::CheckValidVisDef ( const char* duClass, const char* vdClass, int ndimFlag )
{
     require ( duClass != 0 );
     require ( vdClass != 0 );

     // Get visdef's key strings according to the input flag
     vector<string> vstr;
     VisDefNDimFlags ( ndimFlag,vstr );

     // Check if visdef is valid
     // Use the default key string
     if ( vstr.size() == 0 || vstr.size() > 1 )
          return ObjectList::CheckValidVisDef(duClass,vdClass,"listVisDef");

     return ObjectList::CheckValidVisDef(duClass,vdClass,vstr[0].c_str());
}

#if 0
// MAYBE METVIEW 4 WILL NEED THIS FUNCTION IF IT STARTS HANDLING PTACH

// -- METHOD :  CheckValidVisDef
//
// -- PURPOSE:  Check if VisDef is valid using the Specific Valid Visdef
//              entry. If there is no such entry in the PlotModTable,
//              try to check in the Common Valid Visdef entry.
//              The reasons are that:
//              1) PGRIB accepts PWIND during the Drop process because
//                 PlotMod does not open the grib file to check if it
//                 contains any VECTOR_FIELD. This is done during the
//                 drawing process.
//              2) PlotMod does not have an icon of the type PTACH, so
//                 unfortunately there is an special treatment to deal 
//                 with PTACHs.
//
//  -- INPUT :  1. the visDef request
//              2. the name of a data unit
//
//  -- OUTPUT:  Yes - The VisDef is accepted for that data unit
//              No -  The VisDef does not match the data unit 
bool
ObjectList::CheckValidVisDef ( const char* dataunitClass, const MvRequest& request )
{
	require ( dataunitClass != 0 );

        // Check if visdef is of type PTACH
	const char* visdefClass;
	if ( ObjectList::IsVisDefIsotachs (request) )
		visdefClass =  PTACH;
	else
		visdefClass = request.getVerb();

	// Retrieve the list of acceptable visdefs in the Object List
	Cached listVisDef = Find ("dataunit", dataunitClass, "listSpecVisDef");
	if ( (const char*) listVisDef )
	{
		// Verify if visDefClass belongs to the VisDefList
		if ( strstr( (const char*)listVisDef, visdefClass ) )
			return true;
		else
			return false;
	}
	else
		return CheckValidVisDef(dataunitClass,visdefClass);
}
#endif

bool
ObjectList::CheckValidVisDefList ( const char* duClass, MvIconList& visdefList, const char* vdpar )
{
	MvIconList tmplist;
	MvIcon     oneIcon;
	for ( MvListCursor jj = visdefList.begin(); jj != visdefList.end(); jj++ )
	{
		oneIcon = *jj;
		if ( ObjectList::CheckValidVisDef(duClass, oneIcon.Request().getVerb(),vdpar) )
			tmplist.push_back(oneIcon);
	}

	visdefList = tmplist;

	return  ( visdefList.size() > 0 ) ? true : false;
}

void
ObjectList::VisDefNDimFlags ( int dimFlag, vector<string>& vstr )
{
     // No info about data dimension
     vstr.clear();
     if ( dimFlag == 0 )
          return;

     // Data contains 1-D data
     if ( dimFlag & 1 )
          vstr.push_back("1D_VISDEF");

     // Data contains 2D data
     if ( dimFlag & 2 )
          vstr.push_back("2D_VISDEF");
}


MvRequest
ObjectList::UserPreferencesRequest(const char* name)
{
	// Read the objectFileName in the METVIEW ObjectList
	MvRequest prefRequest;
	const char* requestFileName = Find("object", name)("default_name");

	// Get the filename full path
	string tt = MakeUserPrefPath (requestFileName);
	const char* filename = tt.c_str();

	// Do we have a preference ?
	if ( ( FileCanBeOpened ( filename, "rw" ) == false ) || ( FileHasValidSize ( filename ) == false ))
	{
		// Try first to read the icon from the user Defaults directory.
		// If fails then creates a default version
		prefRequest = ObjectList::UserDefaultRequest  (name);
		prefRequest.save ( filename  );
		return prefRequest;
	}

	prefRequest.read ( filename );

	return prefRequest;
}

// -- METHOD :  UserDefaultRequest
//
// -- PURPOSE:  For each type of object, read a request 
//              based on the user's default.
//              Parameter EXPAND equals true means that if the output request
//              contains any parameter, which is an icon (subrequest), then it
//              will be expanded
//
// -- INPUT  :  request name
//              flag indicating to expand all subrequests
//
// -- OUTPUT :  request
//
MvRequest
ObjectList::UserDefaultRequest(const char* requestName, bool expand)
{
   require ( requestName != 0 );

   // Read the objectFileName in the METVIEW ObjectList
   const char* requestFileName = Find("object", requestName)("default_name");
   ensure ( requestFileName != 0);

   // Determine  the default path for the icon
   // Find the file which contains the request
   string fullName = MakeUserDefPath (requestFileName);
   string path = mdirname(fullName.c_str());

   // Create a new request and read its contents
   // for the file in the default directory
   MvRequest defaultRequest;
   if ( FileCanBeOpened ( fullName.c_str(), "r" ) == true )
   {
      defaultRequest.read (fullName.c_str(),expand);
      if ( defaultRequest.countParameters() == 0 )
         defaultRequest =  ObjectList::CreateDefaultRequest(requestName,0,path.c_str());

      if ( !(const char*)defaultRequest("_NAME") )
            defaultRequest("_NAME") = fullName.c_str();

      if ( !(const char*)defaultRequest("_CLASS") )
          defaultRequest("_CLASS") = requestName;
   }
   else
      defaultRequest =  ObjectList::CreateDefaultRequest(requestName,0,path.c_str());

   return defaultRequest;
}

// -- METHOD :  ExpandRequest
//
// -- PURPOSE:  Expand the request according to the Metview definition
//              and rules
//
// -- INPUT  :  Request, expansion flag
//
// -- OUTPUT :  Expanded request
MvRequest
ObjectList::ExpandRequest ( const MvRequest& reqst, long expandFlag )
{
	// Expand the request against the METVIEW definition file
	MvRequest objectDef = Find ( "object", reqst.getVerb() );
	Cached rulesFileName = objectDef ( "rules_file" );
	Cached defFileName   = objectDef ( "definition_file" );

	if ( expandFlag == 0 )
		expandFlag           = objectDef ( "expand" );	

	MvLanguage langMetview ( (const char*) defFileName,
				 (const char*) rulesFileName,
				 expandFlag );

	return langMetview.expandOne ( reqst );
}

// -- METHOD :  CreateDefaultRequest
//
// -- PURPOSE:   
//
// -- INPUT  :
//
// -- OUTPUT :

MvRequest
ObjectList::CreateDefaultRequest ( const char* requestName, int expandFlag, const char* path )
{
   MvRequest request ( requestName );
   MvRequest expRequest = ObjectList::ExpandRequest ( request,expandFlag );

   // Add hidden parameters
   expRequest( "_CLASS" ) = requestName;
   expRequest("_DEFAULT") = true;

   // Add parameter _NAME, including the path if it is given
   const char* name = (const char*)Find("object",requestName)("default_name");
   string defName;
   if ( name )
   {
      defName  = "<";
      defName += name;
      defName += ">";
   }
   else
      defName = "<default>";

   if ( path )
   {
      string relName = path + string("/") + defName;
      expRequest("_NAME") =  relName.c_str();
   }
   else
      expRequest("_NAME") = defName.c_str();

   return expRequest;
}

// -- METHOD :  
//
// -- PURPOSE:   
//
// -- INPUT  :
//
// -- OUTPUT : 
bool
ObjectList::IsDataUnit( const Cached& requestVerb )
{
        if ( !requestVerb )
	  	return false;

	MvRequest objectDef = Find ( "dataunit", requestVerb );

	return objectDef("class") == requestVerb;
}

// -- METHOD :  
//
// -- PURPOSE:   
//
// -- INPUT  :
//
// -- OUTPUT : 
bool
ObjectList::IsWindow ( const Cached& requestVerb )
{
        if ( !requestVerb )
		return false;

	MvRequest objectDef = Find ( "window", requestVerb );

	return objectDef ( "class" ) == requestVerb;
}

// -- METHOD :  
//
// -- PURPOSE:   
//
// -- INPUT  :
//
// -- OUTPUT : 
bool
ObjectList::IsView ( const Cached& requestVerb )
{
        if ( !requestVerb )
		return false;

	MvRequest objectDef = Find ( "view", requestVerb );
	
	return objectDef ( "class" ) == requestVerb;
}

bool
ObjectList::IsGeographicalView ( const string& verb )
{
    if ( verb.empty() )
        return false;

    // Find viewname in the registered view list
    MvRequest objectDef = Find ( "view", verb.c_str() );

    // Check if this is a geographical view
    string class1 = (const char*)objectDef ( "class" );
    return (!class1.empty() && (class1 == GEOVIEW || class1 == MAPVIEW) );
}

// -- METHOD :  
//
// -- PURPOSE:   
//
// -- INPUT  :
//
// -- OUTPUT : 
bool
ObjectList::IsVisDef( const Cached& requestVerb )
{
        if ( !requestVerb )
		return false;

	MvRequest objectDef = Find ( "visdef", requestVerb );
	
	return objectDef ( "class" ) == requestVerb;
}

// -- METHOD :  IsVisDefContour
//
// -- PURPOSE:  Indicate if the request is a contouring request
//
// -- INPUT  :  Request verb
//
// -- OUTPUT :  true/false

bool
ObjectList::IsVisDefContour ( const char* verb )
{
	return ( verb == MCONT || verb == PCONT );
}

// -- PURPOSE: Indicate if the request is a PTACH visdef  
//
// -- INPUT  : Request
//
// -- OUTPUT : true/false
bool
ObjectList::IsVisDefIsotachs( const MvRequest& request )
{
	//If it is PCONT, then check for Data_transformation
	if ( IsVisDefContour(request.getVerb() ) )
	{
		const char* data = (const char*)request("CONTOUR_DATA_TRANSFORMATION");
		if ( !data ) //no information about data_transformation
			return false;

		if ( strcmp(data,ISOTACHS) == 0 )
			return true;
		else
			return false;
	}

	return false;
}

// -- METHOD :  IsVisDefCoastlines
//
// -- PURPOSE:  Indicate if the request is a coastline request
//
// -- INPUT  :  Request verb
//
// -- OUTPUT :  true/false

bool
ObjectList::IsVisDefCoastlines ( const Cached& requestVerb )
{
   return ( requestVerb == MCOAST || requestVerb == PCOAST );
}

bool
ObjectList::IsVisDefText ( const char* requestVerb )
{
   return ( requestVerb == MTEXT || requestVerb == PTEXT );
}

bool
ObjectList::IsVisDefLegend( const char* requestVerb )
{
   return ( requestVerb == MLEGEND );
}

bool
ObjectList::IsVisDefImport( const char* requestVerb )
{
   return ( requestVerb == MIMPORT );
}

// -- METHOD :  
//
// -- PURPOSE:   
//
// -- INPUT  :
//
// -- OUTPUT : 
bool
ObjectList::IsAutomaticTitleText ( const MvRequest& request )
{
	Cached verb = request.getVerb();
	if ( ( !(ObjectList::IsVisDefText( verb )) ) ||
	     ( request ( "TEXT_ORIGIN" ) == Cached ( "USER" ) ) ||
	     ( request ( "TEXT_ORDER_MODE" ) == Cached ( "USER" ) ) )
		return false;

	return true;
}

// -- METHOD :  IsAxis
//
// -- PURPOSE:  Indicate if the request is a PAXIS request
//
// -- INPUT  :  Request verb
//
// -- OUTPUT :  true/false
bool
ObjectList::IsVisDefAxis ( const Cached& requestVerb )
{
	return ( requestVerb == MAXIS || requestVerb == PAXIS );
}

bool
ObjectList::IsVisDefGraph ( const Cached& requestVerb )
{
	return ( requestVerb == MGRAPH || requestVerb == PGRAPH );
}

bool
ObjectList::IsLayer ( const char* verb )
{
    return ( strcmp(verb,"LAYER") == 0 || strcmp(verb,"layer") == 0 );
}

// -- METHOD :  MacroName
//
// -- PURPOSE:  Find the macro name associated to a METVIEW object
//
// -- INPUT  :  The Object's class
//
// -- OUTPUT :  The macro name associated to the object
Cached
ObjectList::MacroName ( const Cached& iconClass )
{
	Cached retVal = Find ( "object", iconClass, "macro" );
	if ( ! retVal )
		retVal = iconClass;

	return retVal;
}

// -- METHOD :  CalledFromMacro
//
// -- PURPOSE:  Indicate if the request has been called from a macro
//
// -- INPUT  :  A request
//
// -- OUTPUT :  true/false
bool
ObjectList::CalledFromMacro ( const MvRequest& request )
{
	int i = request ( "_CALLED_FROM_MACRO" );
	if ( i == 1 || ( (const char*)request("_APPL") &&
        strcmp((const char*)request("_APPL"),"macro") == 0 ) )
		return true;

	return false;
}

// -- METHOD :  GetCompanion
//
// -- PURPOSE:  Return the expected parameter to make a pair in a vector field.
//              ex. U/V, D/T... and the correspondent wind mode
//
// -- INPUT  :  param: parameter name
//
// -- OUTPUT :  pair : pair name ("no_pair" for scalar data)
//              mode : wind mode plotting
//              first: true - if there is a companion and the input parameter 
//                            is the primarely/first element, i.e. it is not 
//                            the friend element
void ObjectList::GetCompanion ( const string& param, string& pair, string& mode, bool& first )
{
   MvRequest& objectList = ObjectList::Instance();
   objectList.rewind();

   string comp("companion");
   while ( objectList )
   {
      if ( objectList.getVerb() == comp )
      {
         if ( (const char*)objectList("class") == param )
         {
            pair = (const char*)objectList( "friend" );
            mode = (const char*)objectList("mode") ? (const char*)objectList("mode") : "";
            first = true;
            return;
         }
         else if ( (const char*)objectList("friend") == param )
         {
            pair = (const char*)objectList( "class" );
            mode = (const char*)objectList("mode") ? (const char*)objectList("mode") : "";
            first = false;
            return;
         }
      }
      objectList.advance();
   }

   pair = "no_pair";
   mode = "";
   first= false;
   return;
}

// Check if two variables are companions
bool ObjectList::AreCompanions ( const string& pair1, const string& pair2 )
{
   // Get the object list
   MvRequest& objectList = ObjectList::Instance();
   objectList.rewind();

   // Loop the list doing the checking
   string comp("companion");
   while ( objectList )
   {
      if ( objectList.getVerb() == comp  && (
           (((const char*)objectList("class") == pair1) && ((const char*)objectList("friend") == pair2)) ||
           (((const char*)objectList("class") == pair2) && ((const char*)objectList("friend") == pair1)) )
         )
         return true;

      objectList.advance();
   }

   return false;
}

// -- METHOD :  IsImage
//
// -- PURPOSE:  Return true if the input representation is space view ("SV").
//
// -- REVISION (2014-09-02) : now we always return false because at least for
//                            now we don't want or need to distinguish between
//                            satellite and non-satellite GRIB fields.

bool
ObjectList::IsImage ( const char* repres )
{
	//(repres != NULL && !strcmp(repres, "SV"));
	return false;
}

bool
ObjectList::IsService ( const char* classe, const char* action, bool context )
{
	MvRequest req = FindReqService(classe,action);
	if ( !req )
		return false;

	if ( context && !(int)req("context"))
		return false;

	return true;
}

bool
ObjectList::IsDefaultValue(MvRequest &in, const char *param )
{
	bool retVal = false;

	MvRequest defReq = CreateDefaultRequest(in.getVerb() );

	if ( IsParameterSet(in,param) && IsParameterSet(defReq,param) )
	{
		if ( is_number(in(param)) && is_number(defReq(param)) )
		   retVal = ( (double)in(param) == (double)defReq(param) );
		else
		   retVal = (strcmp(in(param),defReq(param)) == 0 );
	}

	return retVal;
}

bool
ObjectList::IsVisDefBackForeground( const char* verb )
{
   return ( IsVisDefCoastlines(verb) ||
             IsVisDefText(verb)      ||
             IsVisDefLegend(verb)
          );
}

bool
ObjectList::IsThermoView( const char* verb )
{
   return ( verb == THERMOVIEW );
}

bool
ObjectList::IsThermoGrid( const char* verb )
{
   return ( verb == MTHERMOGRID );
}
