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

/********************************************************************************
 Important information:

 1. The Bottom/Top Levels will be computed automatically from the data, if
    they are not given as a input parameters.

 2. Relationship among fields and interpolation:

                  ML    ML&LNSP   PL    PL&LNSP
         INTY    ok      ok       ok     ok1
         INTN    ok     error     ok     ok1

   INTY/N = interpolation yes/no
   ok1    = LNSP is ignored

 3. If fieldset contains ML&LNSP, a conversion to PL will be computed automatically
 ********************************************************************************/

#define INITIALISE_HERE

#include "Xsect.h"
#include "Average.h"
#include "CrossS.h"
#include "MvMiscelaneous.h"
#include "Vprofile.h"

Xsect::Xsect(const char* kw) :
             MvService(kw),
             ncName_("toto.nc"),
             ntime_(0),
             geoIndexVar_(XS_VARLON)
{}

void Xsect::serve(MvRequest& in,MvRequest& out)
{
cout << "request IN" << endl;
in.print();

   // Get Input parameters
   origReq_ = in;
   MvRequest data;
   ApplicationInfo appInfo;
   if ( !this->getInputParameters( in, data, appInfo ) )
      return;

   // Process data
   if ( strcmp(data.getVerb(),"GRIB") == 0 )
   {
      // Process a grib data and build the output request
      if ( !this->processData(data,appInfo,out) )
         return;
   }
   else
   {
      // It is a netCDF data and it has been already processed.
      // Build the output request
      out = this->createOutputRequest( in );
   }

   // Add hidden values to tne output request
   // out1("_VERB") = "NETCDF_MATRIX";
   if ( (const char *)in("_NAME") )
      out("_NAME") = in("_NAME");

cout << "request OUT" << endl;
out.print();

   return;
}

bool Xsect::processData( MvRequest& data, ApplicationInfo& appInfo, MvRequest& out )
{
   // Initialize fildset iterator
   MvFieldSet fs(data);
   MvFieldSetIterator iter(fs);
   MvField field = iter();

   // Retrieve initial info
   double X1,X2,Y1,Y2;
   double north, south, east, west;
   this->getInitialFieldInfo(field,appInfo,north,south,east,west);
   appInfo.getAreaLine( X1,X2,Y1,Y2 );

   // Compute number of vertical points
   int npoint = this->computeGeographicalPoints(appInfo,&field);
   if( npoint == 0 )
   {
      setError(1,"Invalid Area/Line!");
      return false;
   }
   if( appInfo.viaPole() )
      setError( 0, "Xsect line via Pole" );

   // Allocate memory/initialise auxiliary variables
   double* xint = new double [ npoint ];
   ParamMap params;

   // First loop: build field keystring and get the Level info.
   // The LNSP matrix data is computed, if it exists, but the other
   // matrices are computed in the second loop.
   boolean isPL = false;
   boolean isML = false;
   string keystr;
   bool foundU = false, foundV = false, foundW = false;
   int iparam, idate, itime, istep;
   const char* expver = 0;
   iter.rewind();
   while( field = iter() )
   {
      // Retrieve info from the field request
      MvRequest rq=field.getRequest();
      iparam = rq("PARAM");
      const char *lev = rq("LEVELIST");
      istep  = rq("STEP");
      idate  = rq.getBaseDate();
      itime  = rq("TIME");
      expver = rq("EXPVER");
      isML = field.isModelLevel();
      if ( !isML)
         isPL = field.isPressureLevel();

      // Check that this field's vital statistics match those of the others
      // - this is important only in the Average application
      if ( !fieldConsistencyCheck(field,north,south,east,west) )
      {
         deleteAuxiliaryMemory(xint,params);
         return false;
      }

      // Update level info accordingly
      if( !lev || !strcmp(lev, "0") || field.levelTypeString() == cML_UKMO_ND_STR )
      {
         double dlev = field.level();
         if( field.levelTypeString() == cML_UKMO_ND_STR )
            dlev = field.level_L2();

         const int cLVLBUF = 64;
         static char buf[ cLVLBUF ];
         ostrstream s( buf, cLVLBUF );
         s << dlev << ends;

         lev = buf;
      }

      // Generate keys and insert level info
      // Calculate the data matrix, only for LNSP or GHBC fields
      generateKey(keystr,iparam,idate,itime,istep,expver);
      if ( appInfo.isLNSP(iparam) || appInfo.isGHBC(iparam) )
      {
         if ( appInfo.isLNSP(iparam) )
             appInfo.haveLNSP(true);
         else
             appInfo.haveGHBC(true);

         // Calculate values along the transection line
         bool ok = fillValues( appInfo,field,xint );
         if ( !ok )
         {
            // Error in computing data values. The error message is already written
            // in the marslog file. That said, the user should be told, otherwise
            // they stare at  a blank plot!
            setError(1, "Error when filling the values.");
            deleteAuxiliaryMemory(xint,params);
            return false;
         }

         // Create a new ParamInfo instance
         ParamInfo* par = new ParamInfo( iparam,idate,itime,istep,
                                         field.getGribKeyValueString("shortName"),
                                         field.getGribKeyValueString("name"),
                                         field.getGribKeyValueString("units"),
                                         field.getGribKeyValueString("levelType")
                                       );

         // Insert ParamInfo and the 'level' values
         ParamInsertPair inserted = params.insert(ParamPair(keystr,par));
         ParamInfo *pp = (*(inserted.first)).second;
         pp->Level(xint,lev,npoint,1.);
         pp->ExpVer(expver?expver:"_");
      }
      else
      {
         if ( iparam == U_FIELD )
            foundU = true;
         else if ( iparam == V_FIELD )
            foundV = true;
         else if ( iparam == W_FIELD )
            foundW = true;

         // Create a new ParamInfo instance
         ParamInfo* par = new ParamInfo( iparam,idate,itime,istep,
                                         field.getGribKeyValueString("shortName"),
                                         field.getGribKeyValueString("name"),
                                         field.getGribKeyValueString("units"),
                                         field.getGribKeyValueString("levelType")
                                       );

         // Insert ParamInfo and only the 'level'info (not the values)
         ParamInsertPair inserted = params.insert(ParamPair(keystr,par));
         ParamInfo *pp = (*(inserted.first)).second;
         pp->AddLevel(lev);
         pp->ExpVer(expver?expver:"_");
      }
   } //end while (first loop)

   // Update level processing type, i.e. XS_ML/XS_PL/XS_ML_LNSP/XS_ML_GHBC
   // This function will update an internal flag according to the type of
   // level from the input data. This information is only available after
   // the end of the First Loop above.
   int levType = appInfo.levelType(isML,isPL);

   // Check if the input fieldset is compatible with the application
   // and with the parameters given in the UI
   if ( this->consistencyCheck( params,appInfo ) == false )
   {
      setError(1,"ERROR: Fieldset consistency check failed");
      deleteAuxiliaryMemory(xint,params);
      return false;
   }

   // Update/initialize level type info
   appInfo.updateLevels(params);
   bool modlev = appInfo.levelTypeFlag();

   // Set default interpolation flag.
   // This can be updated later, e.g. velocityValues
   appInfo.setVerticalInterpolationFlag();

   // Update flags
   this->setHorCompFlags(foundU,foundV,foundW);

   // Update min/max vertical axis values and number of levels
   int nlevel;
   double pBottom,pTop;
   appInfo.getMinMaxLevels(pBottom,pTop,nlevel);
   if ( this->updateTopBottomLevels( appInfo, params, levType, iter, pBottom, pTop, nlevel ) )
      appInfo.setMinMaxLevels( pBottom, pTop, nlevel );

   // Create/initialise netCDF structure
   MvNetCDF netcdf;
   iter.rewind();
   field = iter();
   int ntimes;
   if ( !this->initialiseNetcdf(netcdf,params,appInfo,ntimes,field,xint) )
   {
      deleteAuxiliaryMemory(xint,params);
      return false;
   }

   // Update application info: number of times
   appInfo.NTimes (ntimes);

   // Initialize variables
   string lastKey        = "FIRSTFIELD";
   int currentGenerated  = 0, lastNrGenerated = -1;
   MvField lastField;

   // Initialize fieldset iterator
   iter.rewind();
   iter.sort("LEVELIST");
   iter.sort("STEP");
   iter.sort("EXPVER");
   iter.sort("TIME");
   iter.sort("DATE");
   iter.sort("PARAM");

   // Second loop: generate data
   ParamIterator paramIter;
   while( field = iter() )
   {
      currentGenerated++;
      MvRequest r = field.getRequest();
      iparam = r("PARAM");
      idate  = r.getBaseDate();
      itime  = r("TIME");
      istep  = r("STEP");
      expver = r("EXPVER");

      // No need to process GHBC fields
      if ( appInfo.isGHBC(iparam) )
          continue;

      // Update level info accordingly
      const char *clev = r("LEVELIST");
      if( !clev || !strcmp(clev, "0") || field.levelTypeString() == cML_UKMO_ND_STR )
      {
         double myLevel = field.level();
         if( field.levelTypeString() == cML_UKMO_ND_STR )
            myLevel = field.level_L2();

         const int cLVLBUF = 64;
         static char buf[ cLVLBUF ];
         ostrstream s( buf, cLVLBUF );
         s << myLevel << ends;

         clev = buf;
      }

      // Generate key and insert level info
      // Calculate the data matrix for non LNSP and GHBC fields
      generateKey(keystr,iparam,idate,itime,istep,expver);
      if ( !appInfo.isLNSP(iparam) && !appInfo.isGHBC(iparam)) // already done in the first loop
      {
         bool ok = fillValues(appInfo,field,xint);
         if ( !ok )
         {
            // Error in computing data values. The error message is already written
            // in the marslog file. That said, the user should be told, otherwise
            // they stare at  a blank plot!
            setError(1, "Error when filling the values.");
            deleteAuxiliaryMemory(xint,params);
            return false;
         }

         if ( ( paramIter = params.find(keystr) ) == params.end() )
         {
            setError(0,"Something strange ??");
            continue;
         }
         else
            (*paramIter).second->Level(xint,clev,npoint,modlev);
      }

      // Write out the data if we have the end of what can be grouped together.
      if ( lastKey != keystr && lastKey != string("FIRSTFIELD") )
      {
         if ( !generateData(appInfo,params,netcdf,lastField,lastKey) )
         {
            setError(1,"Could not generate NETCDF data: error on fieldset or fieldset not suitable");
            deleteAuxiliaryMemory(xint,params);
            return false;
         }
         lastNrGenerated = currentGenerated;
      }

      lastKey = keystr;
      lastField = field;
   }  // end while (second loop)

   // Write out the last input field data
   if ( lastNrGenerated <=  currentGenerated )
   {
      if ( !generateData(appInfo,params,netcdf,lastField,lastKey) )
      {
         setError(1,"Could not generate NETCDF data: error on fieldset or fieldset not suitable");
         deleteAuxiliaryMemory(xint,params);
         return false;
      }
   }

   // Write additional fields, if it is needed
   if ( ! this->generateExtraData(netcdf,appInfo,params,iter) )
   {
      setError(1,"Could not generate NETCDF extra data");
      deleteAuxiliaryMemory(xint,params);
      return false;
   }

   // Close netCDF file
   netcdf.close();

   // Get default parameter to be plotted
   ParamInfo* parInfo = plotVariable(appInfo,params);
   if ( !parInfo )
   {
      deleteAuxiliaryMemory(xint,params);
      return false;
   }

   // Create output request
   out = this->createOutputRequest( appInfo, parInfo );

   deleteAuxiliaryMemory(xint,params);

   return true;
}

void Xsect::generateKey( string &str, int par, int date, int time,
                        int step, const char *expver )
{
   // Format: pPPPPPPYYYYMMDDHHmmSSSS
   char key[40];
   ostrstream oss( key, sizeof( key ) );
   oss << setfill( '0' )
      << "p"
      << setw( 6 ) << par
      << setw( 8 ) << date
      << setw( 4 ) << time
      << setw( 4 ) << step
      << setw( 4 ) << ( expver ? expver : "_" )
      << ends;

   str = key;
}

bool Xsect::updateTopBottomLevels( ApplicationInfo& appInfo, ParamMap &params, int levType, MvFieldSetIterator &iter, double &pBottom, double &pTop, int &nlevel )
{
   // Only computes min/max if bottom and top levels are not initialized
   if ( pTop || pBottom )
      return false;

  // Find the min/max levels for the first parameter that
  // is not LNSP or a surface field. It assumes that the
  // remaining fields have the same number of levels.
  double p;
  pTop = 50000000.;
  pBottom = -50000000.;
  ParamIterator ii;
  LevelMap lmap;
  LevelIterator jj;
  for ( ii = params.begin(); ii != params.end(); ii++ )
  {
     ParamInfo* par = (*ii).second;
     if ( appInfo.isLNSP(par->Parameter()) || par->IsSurface() )
     {
        if ( params.size() == 1 )
        {
           // There is only the LNSP or a surface field
           nlevel = 1;
           pTop = pBottom = 1;
        }
        continue;
     }

     lmap = par->Levels();
     nlevel = lmap.size();
     for ( jj = lmap.begin(); jj != lmap.end();jj++ )
     {
        p = (*jj).second->YValue();
        pTop = MIN(pTop,p);
        pBottom = MAX(pBottom,p);
     }
     break;
  }

   // Compute min/max pressure levels from ML&LNSP fieldset
   if ( levType == XS_ML_LNSP )
   {
      MvField field;
      iter.rewind();
      while( field = iter() )
      {
         // Retrieve info from the field
         MvRequest rq=field.getRequest();
         int iparam = rq("PARAM");
         if ( appInfo.isLNSP(iparam) )
         {
            double lnspMax = field.getGribKeyValueDouble("maximum");
            double lnspMin = field.getGribKeyValueDouble("minimum");
            pTop = field.meanML_to_Pressure_byLNSP(lnspMin,pTop);
            pBottom = field.meanML_to_Pressure_byLNSP(lnspMax,pBottom);
            break;
         }
      }
   }

   return true;
}

bool Xsect::isVisualise( ApplicationInfo& appInfo )
{
   // If the output is not 'visualisation' related: actions
   // "not visualise" or "prepare" (in certain cases), then
   // send back the "netCDF" request
   string actionMode = appInfo.actionMode();
   int    procType   = appInfo.processType();
   if ( actionMode != "visualise" &&
        !(actionMode == "prepare" && procType == XS_DATA_MODULE_DROPPED) &&
        !(actionMode == "prepare" && procType == XS_DATA_DROPPED) )
      return false;
   else
      return true;
}

// Retrieve parameters from the input request and make a consistency check.
// There are 4 types of input request:
// 1. DATA_DROPPED: Grib dropped into a View: MXSECT, DATA(), _CONTEXT(),...
// 2. DATA_MODULE_DROPPED: Data module dropped into a View: MXSECT, DATA(),...
// 3. DATA_MODULE: Process a data module: MXSECT, ...
// 4. netCDF dropped in a View:
//
// Actions for each 4 types:
// 1. Use the original View.
// 2. Use the original View and check if the input XSectData parameters are
//    consistent with the View.
// 3. Build a new View.
// 4. Use the original View and check if the netCDF parameters are consistent
//    with the View.
//
bool Xsect::getInputParameters( MvRequest& in, MvRequest& data, ApplicationInfo& appInfo )
{
   // Retrieve fieldset
   in.getValue(data,"DATA");
   if ( !(int)in.countValues("DATA") ) {
      setError(1, "No Data files specified...");
      return false;
   }

   // Retrieve original View if exists, e.g. if a data or a
   // xs/av/vpdata module was dropped into a view
   MvRequest viewRequest;
   if ( (const char*)in("_CONTEXT") )
      viewRequest = in("_CONTEXT");

   // Retrieve xsectdata parameters if exist, e.g. if a xs/av/vpata module
   // was dropped into a view or if it was called to be processed
   const char* verb = (const char*)in("_VERB");
   bool moduleDropped = this->isDataModuleDropped(verb);

   // Check the type of input request to be processed
   MvRequest commonRequest;
   int procType;
   if ( viewRequest )
      procType = moduleDropped ? XS_DATA_MODULE_DROPPED : XS_DATA_DROPPED;
   else
      procType = XS_DATA_MODULE;

   // Type of input request is a DATA_DROPPED into a View
   // Get information from the View request
   if ( procType == XS_DATA_DROPPED )
   {
      // Retrieve 'original' request from the View
      commonRequest = getAppView( viewRequest );

      // Retrieve parameters
      if ( !getAppParameters(commonRequest, appInfo) )
         return false;
   }
   // Process a data module
   // Get information from the module icon parameters
   else if ( procType == XS_DATA_MODULE )
   {
      // Retrieve parameters
      if ( !this->getAppParameters(in, appInfo) )
         return false;
   }
   // Process a data module dropped into a view
   // Get information from the module and view. Check if they are compatible.
   else if ( procType == XS_DATA_MODULE_DROPPED )
   {
      // Retrieve 'original' request from the View
      commonRequest = getAppView( viewRequest );

      // Retrieve 'original' request from the data module
      MvRequest modRequest = data.getSubrequest("_ORIGINAL_REQUEST");
      if ( !modRequest )
         modRequest = in;

      // Consistency check, only for non-default View request
      if ( !((const char*)commonRequest("_DEFAULT") && (int)commonRequest("_DEFAULT") == 1) )
         if ( !this->consistencyCheck(commonRequest, modRequest) )
            return false;

      // Retrieve application specific parametes
      if ( !getAppParameters( modRequest, appInfo ) )
         return false;
   }

   // Retrieve action mode. Default value is "prepare"
   string actionMode = (const char*)in("_ACTION") ? (const char*)in("_ACTION") : "prepare";

   // Save info
   appInfo.actionMode(actionMode);
   appInfo.processType(procType);

   return true;
}

bool Xsect::checkCoordinates( double& X1, double& Y1, double& X2, double& Y2)
{
   // Update coordinates
   if ( X1 > X2 )
   {
      double W = X1;
      X1 = X2;
      X2 = W;
   }

   if( Y2 > Y1 )
   {
      double W = Y1;
      Y1 = Y2;
      Y2 = W;
   }

   if ( X1 == X2 && Y1 == Y2  )
   {
      setError(1,"Not valid AREA or LINE coordinates");
      return false;
   }

   return true;
}

void Xsect::getInitialFieldInfo( MvField& field, ApplicationInfo& appInfo, double& north, double& south, double& east, double& west )
{
   // Get area and grid from the field
   north   = field.north();
   south   = field.south();
   east    = field.east();
   west    = field.west();
   double Grid_ns = field.gridNS();    //-- may return cValueNotGiven!
   double Grid_ew = field.gridWE();    //-- may return cValueNotGiven!

   //appInfo.setGlobe(North,South,East,West);
   appInfo.Grid(Grid_ns,Grid_ew);

   // Check model/pressure level settings
   int levType = XS_PL;   // -1 = ML, 0 = PL, 1 = LNSP+ML, cML_UKMO_ND = UKMO New Dynamics
   if ( field.isModelLevel() )
   {
      if ( field.levelTypeString() == cML_UKMO_ND_STR )
         levType = field.levelType();
      else
         levType = XS_ML;
   }
   appInfo.levelType(levType);

   // Check field's geographical grid point positions
   fieldConsistencyCheck( field, appInfo );
}

MvRequest Xsect::getAppView ( MvRequest& viewRequest )
{
   // Check if the original view request has been already processed,
   // e.g. it has been already replaced by the CartesianView
   if ( (const char*)viewRequest("_ORIGINAL_REQUEST") )
      return viewRequest("_ORIGINAL_REQUEST");

   // The original view request has not been processed yet.
   // Retrieve it from the input request
   string sview = viewRequest.getVerb();
   if ( view_ == sview )
      return viewRequest;

   return MvRequest();
}

bool Xsect::consistencyCheck( ParamMap &params, ApplicationInfo& appInfo )
{
   // 1) Number of parameters to be processed must be greater than 0
   if (params.size() == 0 )
   {
      setError(1,"ERROR: GRIB file invalid, no parameters found");
      return false;
   }

   // 2) If there is only one parameter, it must contain several levels
   if (params.size() == 1 )
   {
      ParamIterator ii = params.begin();
      if ( (*ii).second->NrLevels() <= 1 )
      {
         setError(1,"ERROR: Number of levels must be greater than 1");
         return false;
      }
   }

   // 3) Options related to the level_selection_type parameters in the
   // user interface must be compatible to the input fieldset
   if ( appInfo.levelType() == XS_ML && appInfo.levelSelectionType() != "FROM_DATA" )
   {
      setError(1,"ERROR: Invalid value for parameter LEVEL_SELECTION_TYPE. It must be FROM_DATA because the input data contains Model Level fields");
      return false;
   }

   return true;
}

// Write Time info to the netCDF file.
// The current implementation performs only a simple consistency check:
// the dimension has to be the same for all variables.
bool Xsect::writeTimeInfoNetcdf( MvNetCDF& cdf, ParamMap &params, int& ntimes )
{
   // Loop through all parameters
   vector<int> stepS;
   int idNew;
   bool first = true;
   int ntimesNew = 0;
   ParamIterator ii = params.begin();
   int id = (*ii).second->Parameter();  // parameter id
   MvDate refDate = (*ii).second->ReferenceDate();
   while ( ii != params.end() )
   {
      // Accumulate and check time consistency
      idNew = (*ii).second->Parameter();
      if ( id != idNew )
      {
         if ( first )
         {
            // Save number of times
            ntimes = ntimesNew;
            first = false;
         }
         else
         {
            // Check time consistency
            if ( ntimes != ntimesNew )
            {
               setError(1,"ERROR: Data has more than one Time range");
               return false;
            }
         }
         ntimesNew = 0;
         id = idNew;
      }
      else if (first)  // Add new time to the list
      {
         MvDate newDate = (*ii).second->VerificationDate();
         double diff = (newDate - refDate);
         int step = (int)(diff*86400.);  //seconds
         stepS.push_back(step);
      }
      ntimesNew++;
      ii++;
   }

   // To handle the last variable time range
   if ( first )
      ntimes = ntimesNew;
   else
   {
      // Check time consistency
      if ( ntimes != ntimesNew )
      {
         setError(1,"ERROR: Data has more than one Time range");
         return false;
      }
   }

   // Add the 'time' variable to netCDF
   vector<long> levels_dimsize(1,ntimes);
   vector<string> levels_name(1,XS_VARTIME);
   MvNcVar *nctime = cdf.addVariable(levels_name[0],ncInt,levels_dimsize,levels_name);
   char buf[128];
   refDate.Format(refDate.StringFormat(),buf);
   nctime->addAttribute("long_name", "Time");
   nctime->addAttribute("units", "seconds");
   nctime->addAttribute("reference_date", buf);
   nctime->put(stepS,(int)ntimes);

   return true;
}

bool Xsect::writeGlobalAttributesNetcdf ( MvNetCDF& netcdf, ParamMap& params, ApplicationInfo& appInfo )
{
   // Add application info
   netcdf.addAttribute("_FillValue",XMISSING_VALUE );
   netcdf.addAttribute("_View",view_.c_str() );
   netcdf.addAttribute("type",type_.c_str());

   // Add title to the Global Attributes.
   // Currently, because Magics only checks the Global Attributes to
   // produce a title and this module plots only the first variable then
   // we are adding this code below. REMOVE it when Magics is capable to
   // produce a title from the Variable Attributes.
   ParamInfo* parInfo = this->plotVariable(appInfo,params);
   if ( !parInfo )
      return false;

   string title = this->titleVariable(appInfo,parInfo);
   return netcdf.addAttribute("title",title.c_str());
}

ParamInfo* Xsect::plotVariable ( ApplicationInfo& appInfo, ParamMap& params )
{
   // Return the first variable that has more than 1 level and
   // it is not a GHBC 
   for ( ParamIterator ii = params.begin(); ii != params.end(); ii++ )
   {
      if ( (*ii).second->NrLevels() > 1 && !appInfo.isGHBC((*ii).second->Parameter()))
         return (*ii).second;
   }

   setError(1,"ERROR: Invalid variable to be plotted - less than 2 levels");
   return 0;
}

bool Xsect::writeGeoCoordsNetcdf( MvNetCDF& cdf, ApplicationInfo& appInfo )
{
   // Compute lat/long interpolation points
   int npoint = appInfo.NrPoints();
   const vector<double>& lon = appInfo.getLongitude();
   const vector<double>& lat = appInfo.getLatitude();

   // Add Latitude value
   vector<string> coord_sdim(1,XS_VARLAT);
   vector<long> coord_ndim(1,npoint);
   MvNcVar *nccoord = cdf.addVariable(coord_sdim[0],ncDouble,coord_ndim,coord_sdim);
   nccoord->addAttribute("long_name", "latitude");
   nccoord->addAttribute("units", "degrees_north");
   nccoord->put(lat,(long)npoint);

   // Add Longitude value
   coord_sdim[0] = XS_VARLON;
   nccoord = cdf.addVariable(coord_sdim[0],ncDouble,coord_ndim,coord_sdim);
   nccoord->addAttribute("long_name", "longitude");
   nccoord->addAttribute("units", "degrees_east");
   nccoord->put(lon,(long)npoint);

   // Add a 'latitude for plotting' variable?
   if (appInfo.viaPole())
   {
       if (appInfo.haveLatsForPlotting())
       {
          const vector<double>& latForPlotting = appInfo.getLatitudeForPlotting();
          coord_sdim[0] = XS_VARLATPLOT;
          nccoord = cdf.addVariable(coord_sdim[0],ncDouble,coord_ndim,coord_sdim);
          nccoord->addAttribute("long_name", "latitude_plot");
          nccoord->addAttribute("units", "degrees_north");
          nccoord->put(latForPlotting,(long)npoint);
       }

       if (appInfo.haveLonsForPlotting())
       {
          const vector<double>& lonForPlotting = appInfo.getLongitudeForPlotting();
          coord_sdim[0] = XS_VARLONPLOT;
          nccoord = cdf.addVariable(coord_sdim[0],ncDouble,coord_ndim,coord_sdim);
          nccoord->addAttribute("long_name", "longitude_plot");
          nccoord->addAttribute("units", "degrees_east");
          nccoord->put(lonForPlotting,(long)npoint);
       }
   }
   
   return true;
}

// Generate contour data ( for xsection or average).
bool Xsect::contourValues ( ApplicationInfo& appInfo, ParamMap& params,
                            MvNetCDF &cdf, MvField& field, string key )
{
   vector<double> y_values;
   vector<double> cp;
   if (!appInfo.computeLevelMatrixValues ( params,key,y_values,cp,field ) )
      return false;

   // Get parameter info
   ParamIterator ii = params.find(key);
   ParamInfo *par = (*ii).second;

   // Add/update variable to the netCDF
   int nrLevels = appInfo.outputLevels();
   int nrPoints = appInfo.NrPoints();
   string varname = this->getNetcdfVarname(par->ParamName());
   string levname = this->getNetcdfLevelVarname(varname);
   MvNcVar* nclevels = cdf.getVariable(levname);
   MvNcVar* ncv = cdf.getVariable(varname);
   if ( !ncv || !nclevels )   // Create new variables
   {
      ntime_ = -1;

      // Initialise dimensions
      vector<long> values_ndim;
      values_ndim.push_back(appInfo.NTimes());
      values_ndim.push_back(nrLevels);
      vector<string> values_sdim;
      values_sdim.push_back(XS_VARTIME);
      values_sdim.push_back(XS_VARLEVEL);

      // Create variable 'levels'
      if ( !nclevels )
      {
         string levname = getNetcdfLevelVarname(varname);
         nclevels = cdf.addVariable(levname,ncDouble,values_ndim,values_sdim);
         nclevels->addAttribute("long_name", "Atmospheric Levels");
         nclevels->addAttribute("units", "hPa");
         nclevels->addAttribute("positive", "down");
      }

      // Create variable matrix to be processed
      if ( !ncv )
      {
         values_ndim.push_back(nrPoints);
         values_sdim.push_back(getGeoIndexVar());
         ncv = cdf.addVariable(varname,ncDouble,values_ndim,values_sdim);
         ncv->addAttribute("long_name", par->ParamLongName().c_str());
         ncv->addAttribute("units", par->Units().c_str());
         ncv->addAttribute("_FillValue", XMISSING_VALUE);
         ncv->addAttribute("title",titleVariable(appInfo,par).c_str());
      }
   }

   // Write level values to tne netCDF file
   int i;
   ntime_++;
   nclevels->setCurrent(ntime_);
//   double factor = (appInfo.levelType() != XS_ML_LNSP) ? 1. : 100.;
   double factor = (appInfo.levelType() == XS_ML) ? 1. : 100.;
   for ( i = 0; i < nrLevels; i++ )
      y_values[i] /= factor;

   nclevels->put(y_values,1,(long)y_values.size());

   // Write matrix values to the netCDF file
   for ( i = 0; i < nrLevels; i++ )
   {
      ncv->setCurrent(ntime_,i);
      ncv->put(&cp[i*nrPoints],1,1,(long)nrPoints);
   }

   return true;
}

// Variable name in netCDF has some restrictions, e.g. can not
// start with a non-alphabetic letter.
// RTTOV specifics:
// a) variable name SKT is replaced by t_ski
// b) variable name starting with non-alphabetic letter is
//    replaced by the following scheme: all non-alphabetic
//    letters are moved to the end, e.g. 10u becomes u_10
string Xsect::getNetcdfVarname(const string& name)
{
   // RTTOV specific
   if ( name == "skt" )
      return string("t_skin");

   // Netcdf only accepts variable name starting with a alphabetic letter
   // Copy initial numbers
   unsigned int j = 0, i;
   string snum;
   for ( i = 0; i < name.size(); i++ )
   {
      if ( isalpha(name[i]) )
         break;

      snum += name[i];
      j++;
   }

   // The name does not have numbers in the begining
   if ( j == 0 )
      return name;

   // The name has only numbers. Add a prefix.
   else if ( j == name.size() )
   {
      string newname = "a_" + snum;
      return newname;
   }

   // The name has numbers in the begining. Move them to the end.
   string newname = name.substr(i, name.size() - i) + "_";
   newname += snum;

   return newname;
}

bool Xsect::isDataModuleDropped( const char* verb )
{
   return (verb && (strcmp(verb,"MXSECTION")  == 0 || strcmp(verb,"MXAVERAGE") == 0 ||
                 strcmp(verb,"MVPROFILE")  == 0 || strcmp(verb,"PM_XSECT" ) == 0 ||
                 strcmp(verb,"PM_AVERAGE") == 0 || strcmp(verb,"PM_VPROF")  == 0 ));
}

MvRequest Xsect::createOutputRequest( MvRequest& in)
{
   // Create NetCDF output request
   MvRequest data = in.getSubrequest("DATA");
   MvRequest out1 = data.getSubrequest("_VISUALISE");
   MvRequest viewReq = data.getSubrequest("_VIEW_REQUEST");
   data.unsetParam("_VISUALISE");        // to avoid duplication of info
   data.unsetParam("_VIEW_REQUEST");    // to avoid duplication of info
   data.unsetParam("_ORIGINAL_REQUEST"); // to avoid duplication of info
   out1("NETCDF_DATA") = data;

   // Final output request
   // If an icon was dropped into a view, uPlot will ignore it.
   // Mode-specific options
   MvRequest out = viewReq + out1;
   return out;
}

void Xsect::deleteAuxiliaryMemory(double* xint, ParamMap& params)
{
   // Delete auxiliary array
   if (xint)
   {
      delete [] xint;
      xint = 0;
   }

   // Free memory for parameters
   for ( ParamIterator paramIter = params.begin(); paramIter != params.end(); paramIter++)
      delete (*paramIter).second;
}

bool Xsect::initialiseNetcdf( MvNetCDF& netcdf, ParamMap& params, ApplicationInfo& appInfo, int& ntimes, MvField& field, double* xint )
{
   // Create a temporary netCDF file
   ncName_ = (const char*)marstmp();
   netcdf.init(ncName_,'w');
   if ( !netcdf.isValid() )
   {
      setError(1,"ERROR: Could not open netCDF file");
      return false;
   }

   // Write netCDF global attributes
   if ( !this->writeGlobalAttributesNetcdf(netcdf,params,appInfo) )
      return false;

   // Write initial variables to the netCDF file: levels, time,
   // geographical coordinates
   if ( !this->writeLevelInfoNetcdf(netcdf,appInfo,params,&field,xint) )
      return false;

   if ( !this->writeTimeInfoNetcdf(netcdf,params,ntimes) )
      return false;

   if ( !this->writeGeoCoordsNetcdf( netcdf, appInfo ) )
      return false;

   return true;
}

MvNcVar* Xsect::getNetcdfVariable( MvNetCDF& cdf, const string& varname, ApplicationInfo& appInfo, const string& longname, ParamInfo* param )
{
   MvNcVar *ncv = cdf.getVariable(varname);
   if ( !ncv )   // Create new variable
   {
      // Initialise dimensions
      vector<long> values_ndim;
      values_ndim.push_back(appInfo.NTimes());
      values_ndim.push_back(appInfo.outputLevels());
      values_ndim.push_back(appInfo.NrPoints());
      vector<string> values_sdim;
      values_sdim.push_back(XS_VARTIME);
      values_sdim.push_back(XS_VARLEVEL);
      values_sdim.push_back(XS_VARLON);

      // Create variable
      ncv = cdf.addVariable(varname,ncDouble,values_ndim, values_sdim);
      ncv->addAttribute("long_name", longname.c_str());
      ncv->addAttribute("units", param->Units().c_str());
      ncv->addAttribute("_FillValue", XMISSING_VALUE);
      ncv->addAttribute("title",titleVariable(appInfo,param).c_str());
   }

   return ncv;
}

MvNcVar* Xsect::getNetcdfVariableLevel( MvNetCDF& cdf, const string& varname, ApplicationInfo& appInfo )
{
   MvNcVar *ncv = cdf.getVariable(varname);
   if ( !ncv )   // Create new variable
   {
      // Initialise dimensions
      vector<long> values_ndim;
      values_ndim.push_back(appInfo.NTimes());
      values_ndim.push_back(appInfo.outputLevels());
      vector<string> values_sdim;
      values_sdim.push_back(XS_VARTIME);
      values_sdim.push_back(XS_VARLEVEL);

      // Create variable
      ncv = cdf.addVariable(varname,ncDouble,values_ndim, values_sdim);
      ncv->addAttribute("long_name", "Atmospheric Levels");
      ncv->addAttribute("units", "hPa");
      ncv->addAttribute("positive", "down");
      ncv->addAttribute("_FillValue", XMISSING_VALUE);
   }

   return ncv;
}

MvRequest Xsect::buildMacroConverterRequest()
{
   const char* caux = origReq_.getVerb();
   MvRequest oreq( caux );
   oreq("_MACRO_DECODE_TAG") = 1;  // extra info for the Macro Converter
   oreq("_CLASS") = (const char*)origReq_("_CLASS") ? (const char*)origReq_("_CLASS") : caux;
   oreq("_VERB") = (const char*)origReq_("_VERB") ? (const char*)origReq_("_VERB") : caux;
   if ( strcmp((const char*)oreq("_CLASS"),"RETRIEVE") == 0 ||
        strcmp((const char*)oreq("_CLASS"),"GRIB") == 0 )
   {
      MvRequest retReq = origReq_("DATA");
      if ( (const char*)retReq("_NAME") )
         oreq("_NAME") = (const char*)retReq("_NAME");
   }
   else if ( (const char*)origReq_("_NAME") )
      oreq("_NAME") = (const char*)origReq_("_NAME");

   return oreq;
}

//------------------------------------------------------------------------------

int main(int argc,char **argv)
{
   MvApplication theApp(argc,argv);
   CrossS	c;
   Average	a;
   Vprofile v;

   // The applications don't try to read or write from pool, this should
   // not be done with the new PlotMod.
   a.saveToPool(false);
   v.saveToPool(false);
   c.saveToPool(false);

   // These are Metview 3 definitions used for backwards compatibility.
   // Remove them later.
   CrossSM3   cM3;
   AverageM3  aM3;
   VprofileM3 vM3;
   cM3.saveToPool(false);
   aM3.saveToPool(false);
   vM3.saveToPool(false);

   theApp.run();
}

