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

#include "ObjectInfo.h"
#include "ObjectList.h"
#include "PlotModConst.h"
#include "MacroConverter.h"
#include "MvIcon.h"

typedef map<Cached,Cached> ExpandedMap;
typedef ExpandedMap::iterator ExpandedIterator;

NamesMap ObjectInfo::namesMap_;

// -- METHOD :
//
// -- PURPOSE:
//
// -- INPUT  :
//
// -- OUTPUT :
void
ObjectInfo::PutNewLine ( const Cached& newLine )
{
   description_.push_back( newLine );
}

void
ObjectInfo::ConvertRequestToMacro( MvRequest& inRequest,
				   MacroConversion lastComma,Cached varName,
				   Cached macroName, set<Cached> skipSet,
				   bool onlySub)
{
	// Initializations
	ExpandedMap expandedMap;
	int id = 1;
	MvRequest tmpRequest = inRequest;
	int count = inRequest.countParameters();
	int i;

	// Compute maximum parameters length names
	int size = MaximumParameterNameLength(inRequest);

	// Loop all parameters
	for( i = 0; i < count ; i++ )
	{
		const char* param = inRequest.getParameter(i);

		const char* value= 0;
		inRequest.getValue(value,param,0);
		if ( !value )
		{
			// No value, so check if there is a subrequest
			MvRequest subReq = inRequest.getSubrequest(param);
			if ( !subReq )
				continue;

			// There is a subrequest
			value = "#";
		}

		// Strip out the debug information and anything that should be skipped
		if ( ( strncmp(param, "_", 1 ) == 0 ) || ( skipSet.find(param) != skipSet.end()) )
		{
			tmpRequest.unsetParam(param);
			continue;
		}

		if ( inRequest.countValues(param) > 1 )
			continue;

		MvRequest subRequest;
		if ( strcmp(value, "#" ) == 0 )  // Subrequest
		{
			subRequest = inRequest.getSubrequest(param);
			bool subExpanded = false;
			Cached varName;

			// Can we follow subRequest also ?
			if ( (const char *)subRequest("_NAME") )
			{
				string fullName = MakeUserPath((const char*)subRequest("_NAME") );
				if ( FileCanBeOpened(fullName.c_str(),"r") )
				{
					Cached returnName = Convert(subRequest);
					if ( strcmp(returnName,"") )
					{
						varName = returnName;
						subExpanded = true;
					}
				}
			}

			// Could not follow the subRequest, expand it as it is.
			if ( ! subExpanded )
			{
				varName = Cached(param) + id++;
				ConvertRequestToMacro(subRequest,PUT_END,varName,
				ObjectList::MacroName(subRequest.getVerb()) );
			}

			tmpRequest.unsetParam(param);
			expandedMap[param] = varName;
		}

		else if ( ObjectInfo::IsAName ( value ) )
		{
			// Try to find out if this is a file reference.
			// We need to get the full path
			string tmpName = value;
			if ( IsParameterSet(inRequest,"_NAME") )
				tmpName = string(mdirname(inRequest("_NAME"))) + "/" + value;

			string refName = MakeUserPath(tmpName);
			if ( access( refName.c_str(), R_OK )  == 0 )
			{
				// If it is a DATA (binary) file, creates a dummy request.
				// The crucial information needed is parameter _NAME
				if ( ! IsBinaryOrMissingFile(refName.c_str()) )
					subRequest.read(refName.c_str());
				else
					subRequest.setVerb("DATA");

				if ( ! IsParameterSet(subRequest,"_NAME") )
				subRequest("_NAME") = tmpName.c_str();

				Cached returnName = Convert(subRequest);
				if ( strcmp(returnName,"") )
				{
					tmpRequest.unsetParam(param);
					expandedMap[param] = returnName;
				}
			}
		}
	}

	if ( onlySub == false )
	{
		// Now handle the fields that are not subrequests or references.
		// We have expanded all, now we need to print the header for the
		// original request.
		// Convert macroName to lower case.
		char *macrolower = new char[ strlen(macroName) + 1];
		strcpy(macrolower,macroName);
		for (i = 0; i < (int)strlen(macrolower); i++ )
         macrolower[i] = tolower(macrolower[i]);

      PutNewLine("");
		PutNewLine( ObjectInfo::SpaceToUnderscore(varName) + Cached(" = ") +  Cached(macrolower) + Cached("(") );

		delete [] macrolower;

		count = tmpRequest.countParameters();
		for( i = 0; i < count ; i++)
		{
			const char* param = tmpRequest.getParameter(i);
			int n = tmpRequest.countValues(param);
			Cached valueList;

			 // is there more than one value ?
			if ( n > 1 )
				valueList = Cached ("[ "); // add a list

			if ( n == 0 ) 
				continue;

			for(int j = 0; j < n ; j++)
			{
				// put a "comma" before the new value
				if ( j > 0 )
				valueList = valueList + Cached(",");

				// add a new value to the list
				const char *val = 0;
				tmpRequest.getValue(val,param,j);
				valueList = valueList + FormatValue(val, param, macroName);
			}

			// close the list, if needed
			if ( n > 1 )
				valueList = valueList + Cached ( " ]" );

			// add a new value to the description	
			// remember the last comma
			Cached newLine;
			Cached comma = ",";
			if  ( i == ( count - 1 ) )
			{
				if ( expandedMap.size() == 0  && ( lastComma == NO_LAST_COMMA || lastComma == PUT_END ) )
				comma = "";
			}

			FormatLine(param,valueList,comma,size);
		}

		// At last print out the fields that has been expanded
		int j= 0;
		ExpandedIterator ii = expandedMap.begin();
		for ( ; ii != expandedMap.end(); ii++,j++)
		{
			Cached comma = ",";
			if ( j == (signed)(expandedMap.size() -1 ) )
			{
				if ( lastComma == NO_LAST_COMMA ||lastComma == PUT_END )
					comma = "";
			}

			FormatLine((*ii).first,(*ii).second,comma,size);
		}
 
      if ( lastComma == PUT_END )
         this->PutNewLine ( "   )" );
   }
}

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

Cached
ObjectInfo::ConvertDataUnitToMacro ( MvIcon& dataUnit )
{
   // Retrieve the request associated to the dataUnit
   MvRequest duRequest = dataUnit.Request();
   Cached objectName;

   if ( isPrint_ )
   {
      Cached fileName;

      MvRequest svcRequest = dataUnit.SvcRequest ();
      if ( svcRequest.getVerb() == NETCDF )
         fileName = svcRequest ( "PATH" );
      else if ( duRequest.getVerb() == GRIB )
         fileName = duRequest ( "PATH" );

      if ( FileCanBeOpened ( fileName, "r" ) )
      {
         Cached objectName = Cached ("DataUnit") + dataUnit.Id();
         this->PutNewLine ( Cached ( "# Importing   ") + objectName );
         this->PutNewLine ( objectName + Cached(" = read(\"")  + fileName + Cached("\")") );
         return objectName;
      }
   }

   int use_mode = duRequest("_USE_MODE");
   if ( ! use_mode )
   {
      MvRequest reqAux = duRequest.getSubrequest("_ORIGINAL_REQUEST");
      if ( reqAux && (const char*)reqAux("_MACRO_DECODE_TAG") )
         objectName = Convert(reqAux);
      else
         objectName = Convert(duRequest);
   }
   else
   {
      MvRequest modeRequest = duRequest.getSubrequest("_MODE");
      if ( (const char *) duRequest("_NAME") )
         modeRequest("_NAME") = duRequest("_NAME");
      if ( (const char *) duRequest("_CLASS") )
         modeRequest("_CLASS") = duRequest("_CLASS");
      if ( (const char *) duRequest("_APPL") )
         modeRequest("_APPL") = duRequest("_APPL");

      // Call itself with the mode request.
      MvIcon tmpIcon(modeRequest);
      objectName = ConvertDataUnitToMacro(tmpIcon);
   }

   return objectName;
}

Cached
ObjectInfo::ConvertIconToMacro ( MvRequest& iconRequest, Cached defVal,int id )
{
  int first = 0;
  Cached returnName;

  // Cater for request containing more than one request.
  while ( iconRequest )
    {
      Cached uiName = ObjectInfo::ObjectName ( iconRequest,defVal,id );

      // Find out the iconClass and macro class name
      Cached iconClass = ObjectInfo::IconClass ( iconRequest );

      if ( ! strcmp("",uiName ) )
	uiName =  defVal + id;

      Cached macroName = ObjectList::MacroName ( iconClass );

      // Convert the user interface name to a macro readable name
      Cached iconName    = ObjectInfo::SpaceToUnderscore ( uiName );
 
      if ( first > 0 )
	iconName = iconName + Cached("_") + first;
 
      // Write the contents of the icon
      this->ConvertRequestToMacro ( iconRequest, PUT_END,iconName,macroName );
 
      if ( first > 0  ) 
	returnName = returnName + Cached(", ");
 
      returnName = returnName + iconName;
      first++;
      iconRequest.advance();
    }

  return returnName;
}

// -- METHOD :  Write Description
//
// -- PURPOSE:  Write a description to a file
//
// -- INPUT  :  A pointer to a file
//
// -- OUTPUT :  Description written in the file

void
ObjectInfo::WriteDescription ( FILE* filePtr )
{
  require ( filePtr != 0 );

  CachedList::iterator line = description_.begin();

  while ( line != description_.end() )
    {
      Cached newLine = (*line);
      fprintf ( filePtr, "%s%s\n", linePrefix_.c_str(), (const char*)newLine );

      ++line;
    }
}

// =======================================================
//
//  Static methods - Used in General by PlotMod
//


// -- METHOD : ObjectPath
//
// -- PURPOSE: Provide the complete path to the object
//
// -- INPUT  : A request which describes the object
//
// -- OUTPUT : Fully-qualified path

Cached
ObjectInfo::ObjectPath ( const MvRequest& request )
{
  Cached path;

  const char* mPath = request( "_PATH" );
  if ( mPath )
    //              Path contains file name, it shouldn't but .....
    //              If it's been corrected, use following commented line
    //		return Cached( mPath );
    {
      path  = Cached ( mdirname ( mPath ) );
      return path;
    }

  // Have we been called from a macro ??
  if ( ObjectList::CalledFromMacro ( request ) == true  )
    {
      // Macro should give us the fully qualified path
      const char* mvPath = request ( "_MACRO" );	
      path               = Cached ( mdirname ( mvPath ) );
    }
  else
    {
      // Obtain the fully qualified path to the object
      const char* mvPath = request ( "_NAME" );
      path = MakeUserPath( mdirname(mvPath) ).c_str();
    }

  // Try to open a directory - if failed, set the path to METVIEW_USER_DIR

  DIR* metDir = 0;
  if ( ( metDir = opendir ( (const char*) path ) ) == 0 )
    path = getenv("METVIEW_USER_DIRECTORY");
  else
    closedir ( metDir );

  return path;
}

// -- METHOD :  ObjectName
//
// -- PURPOSE:  Determine the object's name 
//
// -- INPUT  :  A request which describes the object, the object's type,
//              and the object's id;
//
// -- OUTPUT :  The "best guess" of the object's name
//
// -- NOTES  :  Usually, the object's name is contained in the request,
//              in the "_NAME" field. However, when called from a macro
//              this field contains the line of the macro.
//              Therefore, in the case of a macro, we have to create a
//              name for the object

Cached 
ObjectInfo::ObjectName ( const MvRequest& request, const char* objType, int id,
			 bool updateId)
{
  char tmpstr[100];
  Cached objName;

  if ( objType == 0 ||  ! strcmp(objType,"") )
    objName = request.getVerb();
  else
    objName = objType;

  // Have we been called from a macro ??
  if ( ObjectInfo::CalledFromMacro ( request ) == true  )
    {
      if ( id == 0 && updateId )
		id = GenerateId(objName);

      // Macro does not give us a proper name
      sprintf(tmpstr,"%s%d",(const char *)objName,id);
      return tmpstr;
    } 
  else
    {
      // Obtain the name of the object
      const char* mvName = request ( "_NAME" );
      // mvName should be checked, sometimes request has no _NAME
      if ( mvName == 0 )
	{
	  if ( id == 0 && updateId ) 
	    id = GenerateId(objName);
	  sprintf(tmpstr,"%s%d",(const char *)objName,id);
	  return tmpstr;
	}
      else
	return  mbasename ( mvName );
    }
}

Cached ObjectInfo::GenerateName(MvRequest req)
{
  return SpaceToUnderscore(ObjectName(req,"",0,true));
}

int ObjectInfo::GenerateId(const char *name )
{
  return ++namesMap_[name];
}

// -- METHOD :  IsAName
//
// -- PURPOSE:  Indicate is a value is a name
//
// -- INPUT  :  Value
//
// -- OUTPUT :  true - the value is a name
//              false - otherwise

bool
ObjectInfo::IsAName ( const char* value )
{
  for ( int i = 0; i < (signed int)strlen (value); i ++ )
    {
      if ( isalpha ( value[i] ) )
	return true;
    }
  return false;
}

// -- METHOD :  CalledFromMacro
//
// -- PURPOSE:  Indicate if the request is called within a macro
//
// -- INPUT  :  A request
//
// -- OUTPUT :  true/false

bool 
ObjectInfo::CalledFromMacro ( const MvRequest& request )
{
  Cached  applicationName, appClass;

  request.getValue ( applicationName, "_APPL", 0 );
  request.getValue( appClass,"_CLASS");

  if ( applicationName == Cached ( "macro" )  ||
       appClass == Cached("MACRO") )
    return true;
  else
    return false;

}

// -- METHOD :  IconClass
//
// -- PURPOSE:  Indicate the icon Class
//
// -- INPUT  :  Request
//
// -- OUTPUT :  The class of the icon
//
Cached
ObjectInfo::IconClass ( const MvRequest &req )
{
    // Get info from parameter _CLASS (Desktop)
    if ( (const char*)req("_CLASS") )
        return (const char*)req("_CLASS");

    // Return a default value
    return req.getVerb();
}

Cached 
ObjectInfo::StripExtension ( const char* value )
{
  int len = strlen ( value );
  Cached name;

  for ( int i = 0; i < len; i++ )
    {
      if ( *value ==  '.' )
	return name;
      else
	name = name + (*value);
      value++;
    }

  return name;
}

// -- METHOD :  SpaceToUnderscore
//
// -- PURPOSE:  Convert a name with spaces to a name with underscores
//              Converts parenthesis to underscores
//              Converts first character to underscore if is a number
//
// -- INPUT  :  an object name with might contain spaces
//
// -- OUTPUT :  an object name where the spaces have been converted to underscores
//
Cached
ObjectInfo::SpaceToUnderscore ( const char* nameWithSpaces )
{
  int len = strlen ( nameWithSpaces );
  char* nameWithUnderscores = (char*) malloc ( len+1 );

  for ( int i = 0; i < len; i++ )
    {
      if (( i == 0 ) && ( isdigit ( nameWithSpaces[i] ) ))
	nameWithUnderscores[i] = '_';
      else if (( nameWithSpaces[i]  == ' ' ) ||
	       ( nameWithSpaces[i]  == ')' ) ||
	       ( nameWithSpaces[i]  == '(' ) ||
	       ( nameWithSpaces[i]  == ',' ) ||
	       ( nameWithSpaces[i]  == ':' ) ||
	       ( nameWithSpaces[i]  == '.' ) ||
	       ( nameWithSpaces[i]  == '<' ) ||
	       ( nameWithSpaces[i]  == '-' ) ||
	       ( nameWithSpaces[i]  == '>' ) )

	nameWithUnderscores[i] = '_';
      else
	nameWithUnderscores[i] = nameWithSpaces[i];
    }
  nameWithUnderscores[ len ] = '\0';

  return nameWithUnderscores;
}

Cached
ObjectInfo::DefaultVisDefName ( int treeNode )
{
  Cached visdefName = Cached("(Contour") + treeNode + Cached(")");
  return visdefName;
}

bool
ObjectInfo::CheckVisDefClass (const MvRequest& req1, const MvRequest& req2)
{
   const char* class1 = (const char*)req1("_CLASS");
   const char* class2 = (const char*)req2("_CLASS");
   if ( !class1 || !class2 )
      return false;

   // Visdefs in Metview 3 and Metview 4 have the same name apart from
   // the first character (M for Metview 4 and P for Metview 3)
   if ( strcmp(&class1[1],&class2[1]) == 0 )
      return true; //different classes

   // Check if they are companions
   if ( ObjectList::AreCompanions(class1,class2) )
      return true;

#if 0 //FAMI1110 Metview 4 does not do ISOTACHS yet
   //If it is PCONT, then check for Data_transformation
   if ( ObjectList::IsVisDefContour(class1) )
   {
      const char* data1 = (const char*)req1("CONTOUR_DATA_TRANSFORMATION");
      const char* data2 = (const char*)req2("CONTOUR_DATA_TRANSFORMATION");
      if ( !data1 && !data2 ) //no information about data_transformation
         return true;

      if ( !data1 || !data2 )    //different data transformation
         return false;

      if ( strcmp(data1,data2) )
         return false; //different data transformation
   }
#endif
   return false;
}

Cached
ObjectInfo::Convert(MvRequest &req)
{
   int first = 0;
   Cached returnName, oneReqName;

   while ( req )
   {
      Cached iconClass = ObjectInfo::IconClass ( req );
      MacroConverter & converter = MacroConverter::Find(iconClass );

      string objName  = (const char*)ObjectInfo::ObjectName(req);
      string fileName = (const char*)ObjectInfo::ObjectPath(req);
      fileName += (fileName[fileName.size()-1] == '/') ? objName :  "/" + objName;

      oneReqName = "";
      if ( ! IsBinaryOrMissingFile( fileName.c_str() ) )
      {
         MvRequest fileReq;
         fileReq.read(fileName.c_str(),true);

         // Just in case further expansion is needed
         if ( !(const char*)fileReq("_PATH") && (const char*)req("_PATH") )
            fileReq("_PATH") = req("_PATH");
         if ( !(const char*)fileReq("_NAME") && (const char*)req("_NAME") )
            fileReq("_NAME") = req("_NAME");

         oneReqName = converter.Convert(fileReq, this);
      }

      if ( oneReqName == Cached("") )
         oneReqName = converter.Convert(req, this);

      if ( first == 1 )
         returnName = Cached("[") + returnName;

      if ( first > 0 )
         returnName = returnName + Cached(", ");

      returnName = returnName + oneReqName;
      first++;

      req.advance();
   }

   if ( first > 1 )
      returnName = returnName + Cached("] ");

   return returnName;
}

void ObjectInfo::FormatLine(const Cached &param, const Cached &val, const Cached &comma, int width)
{
   ostrstream str;
   str.setf(ios::left);
   str << "   ";str.width(width);
   str << param;
   str << ": " << val << comma << ends;

   const char *cStr = str.str();

   PutNewLine(cStr);
   delete [] cStr;
}

Cached ObjectInfo::FormatValue(const char *val, const char *param, const Cached &macroName)
{
   string sval;
   bool addQuotes = false;

   // Multiple lines
   if(strstr(val,"\n") != 0)
   {
      char *pch;
      int len = strlen(val);
      if(len > 0)
      {
         char *buff = (char*) calloc(len+1,sizeof(char));
         sprintf(buff,"%s",val);
         pch = strtok (buff,"\n");
         sval = sval + "\"" + pch + " \"";

         while (pch != NULL)
         {
            pch = strtok (NULL, "\n");
            if(pch != NULL)
            {
               sval += " &\n";
               sval = sval + "            \"" + pch + " \"";
            }
         }

         free(buff);
      }

      return Cached(sval.c_str());
   }

   // Single line
   // Put all names under '' symbols
   if ( ( ObjectInfo::IsAName ( val ) ) || ( strcmp(val,"") == 0 ) ||
      ( ( strcmp(param, "TIME" ) == 0 ) && ( macroName == Cached ("retrieve") ) ) )
         addQuotes = true;

   if ( addQuotes )
      sval = "\"";

   while ( *val )
   {
      if ( *val == '\\' )
         sval += '\\';

      // If dot is first character and it's a number, add a 0 at the beginning.
      if ( *val == '.' && sval.length() == 0 && is_number(val) )
         sval += "0";

      sval += *val;
      val++;
   }

   if ( addQuotes )
      sval += "\"";

   return Cached(sval.c_str());
}

int ObjectInfo::MaximumParameterNameLength( MvRequest& inRequest)
{
	int size = 0;
	for (int j = 0; j < inRequest.countParameters(); j++ )
	{
		const char* param = inRequest.getParameter(j);
		if ( size < (int)strlen(param) )
			size = strlen(param);
	}

	// Give one extra space
	return size+1;
}
