/***************************** 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 "MvXsectFrame.h"

// convienience method, (vk/1999-08-05)
// checks if x is between r1 and r2
bool isWithinRange( double r1, double r2, double x )
{
   if( r1 <= x && x <= r2 )
      return true;

   if( r2 <= x && x <= r1 )
      return true;

   return false;
}

// Deletes all info about a parameter, including all space used
// for the level data.
ParamInfo::~ParamInfo()
{
   LevelIterator ii;

   for ( ii = levels_.begin();ii != levels_.end(); ii++ )
   {
      if ((*ii).second )
         delete [] (*ii).second->xvalues_;
   }
}

bool ParamInfo::IsSurface()
{
   return (LevelType() == "sfc" || NrLevels() < 1) ? true : false;
}

string ParamInfo::ExpVerTitle()
{
   if( expver_ == "_" )      //-- missing ExpVer is stored as "_"
      return string("");
   else
      return string( "Expver " ) + expver_;
}

// Fill in data into a level in the LevelMap. Generate the level if needed,
// and delete any old values from the level.
void ParamInfo::Level(double* x, string lev, int n, bool modlev)
{
   double flev = atof(lev.c_str() );

   // Save data for one level
   LevelIterator ii = levels_.find(lev);
   if ( ii == levels_.end() )
   {
      levels_[ lev ] = new LevelInfo( flev, modlev );
      ii = levels_.find(lev);
   }
   else
   {
      if ( (*ii).second->xvalues_ )
      {
         delete [] (*ii).second->xvalues_;
         (*ii).second->xvalues_ = 0;
      }
      if ( (*ii).second->yvalue_ >= XMISSING_VALUE )
         (*ii).second->yvalue_ = modlev ? flev : flev * 100;
   }

   if (  (*ii).second->xvalues_ == 0 )
      (*ii).second->xvalues_ = new double[n];

   double *xx = (*ii).second->xvalues_;
   for (int i = 0; i < n; i++)
      xx[i] = x[i];
}

void ParamInfo::UpdateLevels(bool modlev)
{
   LevelIterator ii;
   for ( ii = levels_.begin();ii != levels_.end(); ii++ )
   {
      double flev = atof((*ii).first.c_str());
      (*ii).second->UpdateLevel(flev,modlev);
   }
}

void ParamInfo::AddLevel(string lev)
{
   if ( levels_.find(lev) == levels_.end() )
      levels_[ lev ] = new LevelInfo;
}

MvDate ParamInfo::ReferenceDate()
{
   double tt = date_ + time_/2400.; //YYYYMMDD + HHMM
   return MvDate(tt);
}

MvDate ParamInfo::VerificationDate()
{
   double tt = date_ + time_/2400. + step_/24.; //YYYYMMDD + HHMM + HH
   return MvDate(tt);
}

double* ParamInfo::getOneLevelValues( const string& clev )
{
   // Find the input level
   LevelIterator ll = levels_.find(clev);
   if ( ll == levels_.end() )
      return 0;

   return (*ll).second->XValues();
}

/////////////////// ApplicationInfo /////////////////////

// Constructor
ApplicationInfo::ApplicationInfo() :
                    x1_(0), x2_(0), y1_(0), y2_(0),
                    gridNS_(0), gridEW_(0),
                    topLevel_(0),bottomLevel_(0),
                    nrPoints_(0), nInputLevels_(0), nOutputLevels_(0),
                    levType_(XS_PL),
                    viaPole_(false), haveLatsForPlotting_(false), 
                    haveLonsForPlotting_(false), haveLNSP_(false),
                    haveGHBC_(false), paramGHBC_(-991),
                    hor_point_("INTERPOLATE"),
                    interpolate_(false),
                    uiLSType_("FROM_DATA"),
                    uiLSCount_(100),
                    uiVLinearScaling_(true)
{ }

int ApplicationInfo::levelType(bool isML, bool isPL)
{
   // isML and isPL are both true if a fieldset contains LNSP and PL data.
   // In this case, treat as PL with LNSP.
   if ( (this->haveLNSP() || this->haveGHBC()) && !isPL )
   {
      if( levType_ != cML_UKMO_ND )
      {
          if ( this->haveLNSP() )
              levType_ = XS_ML_LNSP;
          else
              levType_ = XS_ML_GHBC;
      }
   }
   else
   {
      if ( isML && !isPL )
         levType_ = XS_ML;
      else // it must be PL
         levType_ = XS_PL;
   }

   return levType_;
}

// Check the level type info and update the levels accordingly
// This info is not known when the parameters and levels were added.
void ApplicationInfo::updateLevels(ParamMap& params)
{
   // Get level type flag
   bool modlev = this->levelTypeFlag();

   // Update the levels
   ParamIterator ii;
   for ( ii = params.begin(); ii != params.end(); ii++)
      (*ii).second->UpdateLevels(modlev);
   
   return;
}

void ApplicationInfo::Grid( double ns, double ew )
{
  // treatment of missing x/y increments (as in reduced Gaussian grids) -
  // prior to Metview 4.8.1, there was a bug here. gridWE() would give a very
  // tiny value when given a reduced Gaussian grid (based on misinterpreting
  // the result of ecCodes when reading a 'MISSING' GRIB key), but it would
  // not be cValueNotGiven. The intention was to set a 1x1 degree increment
  // in this case, but this was not triggered;
  // instead a very tiny rx meant that the computation would take a very long time.
  // However, the Cross Section application took the max value of dx and dy,
  // which was always dy, and so the result was always reasonable, even though
  // the intention there was also to use a 1x1 grid in the case of missing
  // increments in the GRIB header. We now explicitly give this behaviour in
  // order a) to work, and b) to be consitent with what Cross Section did before.
  // See also JIRA issue METV-1562.
  //-- has to be synchronised with averageAlong() (020731/vk)
  if (ew == cValueNotGiven)
    ew = ns;

  if (ns == cValueNotGiven)
  {
    ew = ns = 1.0;
  }

   gridNS_ = ns;
   gridEW_ = ew;
}

// Compute geographical points from delta intervals
void ApplicationInfo::computeLine( int npoints )
{
   // Compute delta intervals
   nrPoints_ = npoints;  // initial value
   double  dx = double( x2_ - x1_ ) / double(nrPoints_ -1);
   double  dy = double( y2_ - y1_ ) / double(nrPoints_ -1);

   // Initialize vectors
   lon_.clear();
   lat_.clear();
   lon_.reserve(nrPoints_);
   lat_.reserve(nrPoints_);
   lon_.push_back(x1_);
   lat_.push_back(y1_);

   // Compute initial coordinates via pole
   if( viaPole() )
   {
      double lat_k_prev = y1_;            //-- xsect line over a pole
      bool overThePole  = false;
      double original_dy = dy;
      
      for( int k = 1;  k < nrPoints_; k++ )
      {
         double lat_k = lat_k_prev + dy;
         if( ( lat_k < -90.0 || lat_k > 90.0 ) && ! overThePole )  // have we just gone over the pole?
         {
            lat_k_prev += dy;           //-- compensate for mirroring
            dy = -dy;                   //-- start mirroring backwards
            if( lat_k < -90.0 )
               lat_k = -180.0 - lat_k;   //-- fix for South Pole
            else
               lat_k = 180.0 - lat_k;    //-- fix for North Pole

            if( dx == 0 )
            {
               double dtemp = x1_ + 180.0;   //-- straight over the pole
               if( dtemp > 360.0 )           //-- ensure inside normal range
                  dtemp = dtemp - 360.0;

               lon_.push_back(dtemp);
            }

            overThePole = true;
         }
         else
         {
            lon_.push_back(lon_[k-1] + dx);             
         }

         lat_.push_back(lat_k);
         lat_k_prev += dy;
      }

      // the above code computes the real co-ordinates of the points, but Magics will not be happy
      // with them (e.g. 0, -1, -2, ..., -89, -90, -89, ..., -2, -1, 0) so we will create a list of 
      // latitudes that go in one direction only and will be passed to Magics for plotting.
      // Also, Magics seems to be happier if the longitude does not change, so we just plot
      // with an array of constant longitudes
      latForPlotting_.clear();
      latForPlotting_.reserve(nrPoints_);
      lonForPlotting_.clear();
      lonForPlotting_.reserve(nrPoints_);
      for (int k = 0;  k < nrPoints_; k++)
      {
         latForPlotting_.push_back(y1_ + (original_dy*k));
         lonForPlotting_.push_back(x1_);
      }
      haveLatsForPlotting(true);
      haveLonsForPlotting(true);
   }
   // Compute coordinates
   else
   {
      for (int k = 1;  k < nrPoints_; k++) //-- no pole
      {
         lon_.push_back(lon_[k-1] + dx);
         lat_.push_back(lat_[k-1] + dy);
      }
   }

   return;
}

// Compute geographical points according to the Horizontal Point Mode parameter
void ApplicationInfo::computeLine(int& npoints, MvField* field )
{
   // Compute initial geographical points
   this->computeLine(npoints);

   // Use the default geographical points computed by function computeLine
   if ( hor_point_ == "INTERPOLATE" )
      return;
   
   // Update lat/long values
   // Given the default lat/lon positions (computed by function computeLine)
   // select only the nearest points to the original grid points
   MvLocation loc(lat_[0],lon_[0]);
   MvLocation loc2 = field->nearestGridPointLocation( loc );
   double distmin = loc.distanceInMeters(loc2);
   vector<double> lat2;
   vector<double> lon2;
   double distc;          // current distance in meters
   int index = 0;         // index of the closest location
   for ( int k = 1; k < nrPoints_; k++)
   {
      MvLocation loc(lat_[k],lon_[k]);
      MvLocation loc1 = field->nearestGridPointLocation( loc );
      distc = loc.distanceInMeters(loc1);
      if ( loc1.longitude() != loc2.longitude() ||
           loc1.latitude()  != loc2.latitude() )
      {
         lat2.push_back(lat_[index]);
         lon2.push_back(lon_[index]);
         loc2 = loc1;
         index = k;
         distmin = distc;
      }
      else
      {
         // Save the smallest distance
         if ( distc < distmin )
         {
            distmin = distc;
            index = k;
         }
      }
   }

   // Process the last point
   lat2.push_back(lat_[index]);
   lon2.push_back(lon_[index]);

   // Update coordinates and total number of points
   nrPoints_ = npoints = lat2.size();
   lon_.clear();
   lat_.clear();
   lon_ = lon2;
   lat_ = lat2;
}

// Generate data values.
// Only ever called if we use pressure levels or UKMO model levels
void ApplicationInfo::InterpolateVerticala( vector<double>& cp, ParamInfo *param, vector<double>& yvals )
{
   if ( ! interpolate_ )
   {
      this->getLevelValues(param,cp);
      return;
   }

   int i;
   double lpa,pk;
   LevelInfo *firstlev, *nextlev;
   int nrLevels = this->outputLevels();
   cp.clear();
   cp.reserve(nrLevels*nrPoints_);
   for ( int k = 0; k < nrLevels; k++ )
   {
      // Invert Model level data, except for UKMO
      //kmodel = (levType_ == cML_UKMO_ND) ? k : (nrLevels)-k-1;

      //pk = GetInterpolatedYValue(k);
      pk = yvals[k];

      if ( findPressure(param->Levels(),pk,firstlev, nextlev) )
      {
         if ( levType_ )
            lpa = ( pk - firstlev->YValue()) / (nextlev->YValue() - firstlev->YValue());
         else
            //if ( logax_ )
            //   lpa = ( pk - firstlev->YValue()) / (nextlev->YValue() - firstlev->YValue());
            //else
               lpa = log( pk/firstlev->YValue()) / log(nextlev->YValue()/firstlev->YValue());

         double *xvals = firstlev->XValues();
         double *xnext = nextlev->XValues();

         for (i = 0; i < nrPoints_; i++ )
         {
            if ( xvals[i] < XMISSING_VALUE && xnext[i] < XMISSING_VALUE )
               cp.push_back(xvals[i] + ( xnext[i] - xvals[i]) *lpa); //replace k with kmodel
            else
               cp.push_back(XMISSING_VALUE); // replace k with kmodel
         }
      }
      else
         for (i = 0;i < nrPoints_; i++)
            cp.push_back(XMISSING_VALUE);
   }
}

// Generate data values.
// Only ever called if levType_ == XS_ML_LNSP ie if we use non-UKMO model levels
void ApplicationInfo::InterpolateVerticalb( MvField &field, vector<double>& cp, ParamInfo *param, double *splin, vector<double>& yvals )
{
   if ( ! interpolate_ )
   {
      this->getLevelValues(param,cp);
      return;
   }

   // Convert surface pressure
   vector<double> sp;
   sp.reserve(nrPoints_);
   for (int i = 0; i < nrPoints_; i++)
      sp.push_back(exp(splin[i]));

   // Allocate memory for the output data
   int nrLevels = outputLevels();
   cp.clear();
   cp.reserve(nrLevels*nrPoints_);

   // Main loop
   double lpa;
   LevelInfo *firstlev,*nextlev;
   double  firstval, nextval;
   for ( int k = 0; k < nrLevels; k++ )
   {
      double pk = yvals[k];

      // Invert Model level data
      for ( int i = 0; i < nrPoints_; i++)
      {
         if ( sp[i] >= XMISSING_VALUE )
         {
            cp.push_back(XMISSING_VALUE);
            continue;
         }

         if ( this->findModelPressure( param->Levels(), pk, sp[i], field, firstlev, nextlev, firstval, nextval) )
         {
            if ( firstlev->XValues()[i] < XMISSING_VALUE && nextlev->XValues()[i] < XMISSING_VALUE )
            {
               lpa = log(pk/firstval)/log(nextval/firstval);
               cp.push_back(firstlev->XValues()[i] + ( nextlev->XValues()[i] - firstlev->XValues()[i] ) * lpa);
            }
            else
               cp.push_back(XMISSING_VALUE);
         }
         // Create more 'valid' points near the orography to avoid
         // having empty areas in the plot
         else
         {
            bool useValue = false;
            if (firstval < THRESHOLD_INTERPOLATION) // upper atmosphere?
                useValue = fabs(pk - firstval) < (firstval*0.5); // close to 'real' level?
            else
                useValue = fabs(pk - firstval) < THRESHOLD_INTERPOLATION;
            
            if (useValue)
               cp.push_back(firstlev->XValues()[i]);
            else
               cp.push_back(XMISSING_VALUE); 
         }
      }
   }

   return;
}

// Generate data values.
// Only ever called if levType_ == XS_ML_GHBC
void ApplicationInfo::InterpolateVerticalGHBC( vector<double>& cp, ParamInfo *param, ParamInfo *paramGHBC, vector<double>& yvals )
{
   if ( ! interpolate_ )
   {
      this->getLevelValues(param,cp);
      return;
   }

   int i;
   double lpa;
   double firstval, nextval;
   LevelInfo *firstlev,*nextlev;

   LevelIterator ii = param->Levels().begin();
   LevelIterator jj = paramGHBC->Levels().begin();
   int nrLevels = this->outputLevels();
   cp.clear();
   cp.reserve(nrLevels*nrPoints_);
   for ( int k = 0; k < nrLevels; k++, ii++, jj++ )
   {
      double pk = yvals[k];                    // pressure level index
      double* pval = (*ii).second->XValues();  // param X values

      // Invert Model level data
      for ( i = 0; i < nrPoints_; i++)
      {
         // Ignore missing values
         if ( pval[i] >= XMISSING_VALUE )
         {
            cp.push_back(XMISSING_VALUE);
            continue;
         }

         if ( findModelPressureGHBC( param->Levels(), paramGHBC->Levels(), pk, i, firstlev, nextlev, firstval, nextval) )
         {
            if ( firstlev->XValues()[i] < XMISSING_VALUE && nextlev->XValues()[i] < XMISSING_VALUE )
            {
               if ( firstval == 0. && nextval == 0. )
                  cp.push_back(firstlev->XValues()[i]);
               else
               {
                  //lpa = log(pk/firstval)/log(nextval/firstval);
                  lpa = (pk - firstval) / (nextval - firstval);
                  cp.push_back(firstlev->XValues()[i] + ( nextlev->XValues()[i] - firstlev->XValues()[i] ) * lpa);
               }
            }
            else
               cp.push_back(XMISSING_VALUE);
         }
         else
            cp.push_back(XMISSING_VALUE);
      }
   }

   return;
}

bool ApplicationInfo::findPressure( LevelMap &lmap, double value, LevelInfo *&l1, LevelInfo *&l2)
{
   LevelIterator ii = lmap.begin();
   LevelInfo *tmp1 = (*ii).second, *tmp2;
   ii++;

   for (; ii != lmap.end(); ii++ )
   {
      tmp2 = (*ii).second;
      if ( isWithinRange( tmp1->YValue(), tmp2->YValue(), value ) )
      {
         l1 = tmp1;
         l2 = tmp2;
         return true;
      }

      tmp1 = tmp2;
   }

   return false;
}

bool ApplicationInfo::findModelPressure( LevelMap& lmap, double value, double splin,
                                         MvField& field, LevelInfo*& l1, LevelInfo*& l2,
                                         double& val1,double& val2 )
{
   LevelIterator ii = lmap.begin();
   double tmpval1, tmpval2;
   LevelInfo *tmpl1, *tmpl2;

   tmpl1 = (*ii).second;
   ii++;
   tmpval1 = field.meanML_to_Pressure_bySP(splin,(int)(tmpl1->YValue()));

   // desired pressure above (in the atmosphere) the top (first) level in the data?
   if (value < tmpval1)
   {
      val1 = tmpval1;
      l1 = tmpl1;
      return false;
   }


   for (; ii != lmap.end(); ii++ )
   {
      tmpl2 = (*ii).second;
      tmpval2 = field.meanML_to_Pressure_bySP(splin,(int)(tmpl2->YValue()));

      if ( isWithinRange( tmpval1, tmpval2, value ) )
      {
         l1 = tmpl1; l2 = tmpl2;
         val1 = tmpval1; val2 = tmpval2;
         return true;
      }

      tmpl1 = tmpl2;
      tmpval1 = tmpval2;
   }

   l1 = tmpl1;
   return false;
}

bool ApplicationInfo::findModelPressureGHBC( LevelMap& lmap, LevelMap& gmap,
											 double value, int indexIn,
											 LevelInfo*& l1, LevelInfo*& l2,
											 double& val1, double& val2 )
{
   LevelIterator ii = lmap.begin();
   LevelIterator jj = gmap.begin();
   double tmpval1, tmpval2;
   LevelInfo *tmpl1, *tmpl2;
   LevelInfo *ll1, *ll2;

   ll1 = (*ii).second;
   tmpl1 = (*jj).second;
   tmpval1 = tmpl1->XValues()[indexIn]; //field.meanML_to_Pressure_bySP(splin,(int)(tmpl1->YValue()));
   ii++;
   jj++;

   for (; jj != gmap.end(); jj++, ii++ )
   {
	  ll2 = (*ii).second;
      tmpl2 = (*jj).second;
      tmpval2 = tmpl2->XValues()[indexIn]; //field.meanML_to_Pressure_bySP(splin,(int)(tmpl2->YValue()));

      if ( isWithinRange( tmpval1, tmpval2, value ) )
      {
         l1 = ll1; l2 = ll2;
         val1 = tmpval1; val2 = tmpval2;
         return true;
      }

      ll1 = ll2;
      tmpl1 = tmpl2;
      tmpval1 = tmpval2;
   }

   return false;
}

void ApplicationInfo::scaleVelocity(ParamInfo *par)
{
   LevelMap lmap = par->Levels();
   LevelIterator ii = lmap.begin();

   // Compute distance
   double dellat = ABS(y2_-y1_), dellon = ABS(x2_-x1_);
   double latm = (y1_+y2_)/2.;
   double dellona = dellon*cos( cCDR*latm );
   double dist = 110442.3*sqrt (dellat*dellat + dellona*dellona);

   double hscale = 21./dist;
   double vscale = 12./(bottomLevel_ - topLevel_);

   if (levType_ == XS_ML )
      vscale = 12./(100 * 1000);

   double factor = -vscale/hscale;
   double *values;
   for ( ; ii  != lmap.end(); ii++ )
   {
      values = (*ii).second->XValues();

      for (int i=0; i<nrPoints_; i++)
      {
         if (values[i] < XMISSING_VALUE)
            values[i] *= factor;
         else
            values[i] = XMISSING_VALUE;
      }
   }

   return;
}

void ApplicationInfo::scaleVelocityPB(ParamInfo *par)
{
   LevelMap lmap = par->Levels();
   LevelIterator ii = lmap.begin();

   double *values;
   for ( ; ii  != lmap.end(); ii++ )
   {
      values = (*ii).second->XValues();
      double P = (*ii).second->YValue();
      for (int i=0; i<nrPoints_; i++)
      {
         if (values[i] < XMISSING_VALUE)
            values[i] = values[i] / (-9.8 * P/(286.*273.));
         else
            values[i] = XMISSING_VALUE;
      }
   }

   return;
}

void ApplicationInfo::scaleVelocity1(ParamInfo *par, double factor)
{
   LevelMap lmap = par->Levels();
   LevelIterator ii = lmap.begin();

   double *values;
   for ( ; ii  != lmap.end(); ii++ )
   {
      values = (*ii).second->XValues();
      for (int i=0; i<nrPoints_; i++)
      {
         if (values[i] < XMISSING_VALUE)
            values[i] *= factor;
         else
            values[i] = XMISSING_VALUE;
      }
   }

   return;
}

void ApplicationInfo::setMinMaxLevels( double P1, double P2, int nlevel )
{
    bottomLevel_ = P1;
    topLevel_ = P2;

    nInputLevels_ = nlevel;
}

void ApplicationInfo::getMinMaxLevels( double& P1, double& P2, int& nlevel )
{
    P1 = bottomLevel_;
    P2 = topLevel_;
    nlevel = nInputLevels_;
}

#if 0
void ApplicationInfo::setYValues(MvNcVar *yvar, ParamInfo *par)
{
   int nrY = NrYValues(par);
   double *yvalues = new double[nrY];

   // Retrieve y values
   if ( interpolate_ )
   {
      if ( levType_ == XS_ML )
      {
         yvalues[0] = PresTop_;   //ncx2->setCurrent(ntime_);
   //ncx2->put(x_values,1,nrPoints);

         yvalues[1] = PresBot_;
      }
      else if ( levType_ == cML_UKMO_ND )
      {
         yvalues[1] = PresTop_;
         yvalues[0] = PresBot_;
      }
      else
      {
         yvalues[0] = PresTop_/100;
         yvalues[1] = PresBot_/100;
      }
   }
   else
   {
      LevelMap lmap = par->Levels();
      int i = 0;   // the index of the first axis value to set
      int inc = 1; // the amount to increment the axis value index by

      if ( levType_ == cML_UKMO_ND )  // UK Met Office model levels are 'upside-down'
      {
         i = nrY - 1;
         inc = -1;
      }   //ncx2->setCurrent(ntime_);
   //ncx2->put(x_values,1,nrPoints);


      for ( LevelIterator ii = lmap.begin(); ii != lmap.end(); ii++, i+=inc )
      {
         yvalues[i] = (*ii).second->YValue();
         if ( levType_ == XS_PL )
            yvalues[i] /= 100;
      }
   }

   // Insert Y values as an attribute of the input variable
   yvar->addAttribute("_Y_VALUES",nrY,yvalues);
   //ncx2->setCurrent(ntime_);
   //ncx2->put(x_values,1,nrPoints);

   delete [] yvalues;
}
#endif

bool ApplicationInfo::computeLevelMatrixValues ( ParamMap& params, const string& key,
                                                   vector<double>& y_values,
                                                   vector<double>& cp,
                                                   MvField& field )
{
   // Get parameter info
   ParamIterator ii = params.find(key);
   ParamInfo *par = (*ii).second;

   // Compute values
   double* splin = 0;
   if( levType_ == XS_ML_LNSP )
   {
      // Get LNSP values
      ParamInfo* parLnsp = this->getParamInfo( params,LNSP_FIELD,key );
      if ( ! parLnsp )
      {
         cout << "ERROR: Could not find parameter LNSP" << endl;
         return false;
      }

      splin = parLnsp->getOneLevelValues("1");
      if ( !splin )  // LNSP not found
      {
         cout << "ERROR: Could not find LNSP Level values" << endl;
         return false;
      }

      // Compute level values
      this->computeLevelInfoUI(par,y_values,splin,field);

      // Interpolate matrix
      this->InterpolateVerticalb(field,cp,par,splin,y_values);
   }
   else if (levType_ == XS_ML_GHBC ) // handle height-based coordinates
   {
      // Get extra values
      ParamInfo* parGHBC = this->getParamInfo( params,paramGHBC_,key );
      if ( ! parGHBC )
      {
         cout << "ERROR: Could not find parameter GHBC" << endl;
         return false;
      }

      // Compute level values
      this->computeLevelInfoGHBC(parGHBC,y_values);

      // Interpolate matrix
      this->InterpolateVerticalGHBC(cp,par,parGHBC,y_values);
   }
   else
   {
      // Compute level values
      this->computeLevelInfoUI(par,y_values,splin,field);

      // Interpolate matrix
      this->InterpolateVerticala(cp,par,y_values);
   }

   return true;
}

int ApplicationInfo::computeLevelInfoUI( ParamInfo* par, vector<double>& vals, double* splin, MvField& field )
{
   // Compute the levels according to the parameters given in the user interface
   if ( uiLSType_ == "FROM_DATA" )
   {
      if ( splin == 0 )
         return this->computeLevelInfo( par,vals );
      else
         return this->computeLevelInfo( par,vals,splin,field );
   }
   else if ( uiLSType_ == "COUNT" )
   {
      LevelMap lmap = par->Levels();
      nInputLevels_ = lmap.size();
      return this->computeLevelInfo( vals );
   }
   else
   {
      vals = this->levelList();
      nOutputLevels_ = vals.size();
      nInputLevels_  = vals.size();
      return nOutputLevels_;
   }
}

int ApplicationInfo::computeLevelInfo( ParamInfo* par, vector<double>& vals )
{
   // Get levels
   LevelMap lmap = par->Levels();
   int nrLevels = lmap.size();
   vals.clear();
   vals.reserve(nrLevels);
   for ( LevelIterator jj = lmap.begin(); jj != lmap.end(); jj++ )
      vals.push_back( (*jj).second->YValue() );

   nInputLevels_ = nOutputLevels_ = nrLevels;

   return nrLevels;
}

int ApplicationInfo::computeLevelInfo( ParamInfo* par, vector<double>& vals, double* splin, MvField& field )
{
   // Convert ML&LNSP to PL using the following algorithm:
   // a) for each ML value (1,2,...N) get all the correspondent values
   //    and compute the average
   // b) compute extra levels by adding an offset (10 hPa) until reaches the highest
   //    pressure value (bottomLevel_), which was computed previously.
   if ( levType_ != XS_ML_LNSP )
   {
      return this->computeLevelInfo( par,vals );
   }

   // Part A: compute "average" levels 
   LevelMap lmap = par->Levels();
   int nrLevels = lmap.size();
   vals.clear();
   vals.reserve(nrLevels);  // initial allocation, it will increase in part B
   for ( LevelIterator jj = lmap.begin(); jj != lmap.end(); jj++ )
   {
      LevelInfo *lInfo = (*jj).second;
      double dlev = 0.;
      int count = 0;
      for (int i = 0; i < nrPoints_; i++ )
      {
         if ( splin[i] <  XMISSING_VALUE )
         {
            dlev += field.meanML_to_Pressure_byLNSP(splin[i],(int)(lInfo->YValue()));
            count++;
         }
      }

      double mean = count ? dlev/count : 0.;
      vals.push_back(mean);
   }

   // Part B: add extra levels
   double level1 = vals[nrLevels-1] + OFFSET_LEVELINFO;
   while ( level1 <= bottomLevel_ )
   {
      vals.push_back(level1);
      level1 += OFFSET_LEVELINFO;
   }

   // Assume that the visualisation will be in the Log space too
   uiVLinearScaling_ = false;

   nInputLevels_ = nrLevels;
   nOutputLevels_ = vals.size();

   return nOutputLevels_;
}

int ApplicationInfo::computeLevelInfo( vector<double>& vals )
{
   // Initialize array
   vals.clear();
   vals.reserve(uiLSCount_);

   if ( uiVLinearScaling_ )
   {
      // Compute equal separated levels
      double zdp = ( bottomLevel_ - topLevel_) / double (uiLSCount_ - 1);
      for (int i = 0; i < uiLSCount_; i++)
         vals.push_back(topLevel_ + i*zdp);
   }
   else
   {
      // Compute levels using a log scaling 
      double logBottomLevel = log(bottomLevel_);
      double logTopLevel = log(topLevel_);
      double zdp = ( logBottomLevel - logTopLevel) / double (uiLSCount_ - 1);
      for (int i = 0; i < uiLSCount_; i++)
         vals.push_back(exp(logTopLevel + i*zdp));
   }

   nOutputLevels_ = uiLSCount_;

   return nOutputLevels_;
}

int ApplicationInfo::computeLevelInfoGHBC( ParamInfo* par, vector<double>& vals )
{
   // Convert ML&GHBC to PL using the following algorithm: for each ML value
   // (1,2,...N) get all the correspondent values and compute the average
   LevelMap lmap = par->Levels();
   int nrLevels = lmap.size();
   vals.clear();
   vals.reserve(nrLevels);
   double xval;
   for ( LevelIterator jj = lmap.begin(); jj != lmap.end(); jj++ )
   {
      LevelInfo *lInfo = (*jj).second;
      double dlev = 0.;
      int count = 0;
      for (int i = 0; i < nrPoints_; i++ )
      {
         xval = lInfo->XValues()[i];
         if ( xval <  XMISSING_VALUE )
         {
            dlev += xval;
            count++;
         }
      }

      double mean = count ? dlev/count : 0.;
      vals.push_back(mean);
   }

   nInputLevels_ = nOutputLevels_ = nrLevels;

   return nrLevels;
}

void ApplicationInfo::getLevelValues( ParamInfo *par, vector<double>& cp )
{
   LevelMap lmap = par->Levels();
   LevelIterator ii;

#if 1
   cp.clear();
   cp.reserve(lmap.size() * nrPoints_);
   for (ii = lmap.begin(); ii != lmap.end(); ii++ )

#else
   int nrY = NrYValues(par);
   int k = nrY - 1;      // the index of the first level to set
   int kinc = -1;        // the amount to increment the level index by

   if ( levType_ == cML_UKMO_ND )  // UK Met Office model levels are 'upside-down'
   {
      k = 0;
      kinc = 1;
   }

   for (ii = lmap.begin(); ii != lmap.end(); ii++, k += kinc )
#endif

   {
      LevelInfo *lInfo = (*ii).second;
      for (int i = 0; i < nrPoints_; i++ )
         cp.push_back(lInfo->XValues()[i]);
   }
}

#if 0
double ApplicationInfo::GetInterpolatedYValue(int index)
{
   double pk;

   // Interpolation
   if ( logax_ )
   {
      double deltalog = (log10(PresBot_) - log10(PresTop_))/(nrLevels_ - 1);
      pk = exp((log10(PresTop_) + (double)index*deltalog)*log(10.));
   }
   else
   {
      double zdp = ( PresBot_ - PresTop_)/ ( nrLevels_ - 1 );
      pk = (levType_ == cML_UKMO_ND) ? PresTop_ + index : PresTop_+ index * zdp;
   }

   return pk;
}
#endif

void ApplicationInfo::setAreaLine( double L1, double L2, double R1, double R2 )
{
   // Initialize area or line coordinates
   x1_ = L1;
   x2_ = L2;
   y1_ = R1;
   y2_ = R2;

   // Check values
   if ( y1_ > 90. || y1_ < -90. || y2_ > 90. || y2_ < -90.)
      viaPole_ = true;

   return;
}

void ApplicationInfo::getAreaLine( double& L1, double& L2, double& R1, double& R2 )
{
   L1 = x1_;
   L2 = x2_;
   R1 = y1_;
   R2 = y2_;
}

void ApplicationInfo::getAreaLine( double* area )
{
   area[0] = y1_;
   area[1] = x1_;
   area[2] = y2_;
   area[3] = x2_;
}

double ApplicationInfo::Ancos()
{
      double angle = atan2 (y2_ - y1_,x2_ - x1_);
      return cos(angle);
}

double ApplicationInfo::Ansin()
{
      double angle = atan2 (y2_ - y1_,x2_ - x1_);
      return sin(angle);
}

bool ApplicationInfo::levelTypeFlag()
{
    if ( levType_ == XS_PL )
        return false;
    else
        return true;
}

void ApplicationInfo::setVerticalInterpolationFlag()
{
   if (levType_ == XS_ML_LNSP || levType_ == XS_ML_GHBC)
      interpolate_ = true;
   else if (uiLSType_ == "COUNT" || uiLSType_ == "LEVEL_LIST")
      interpolate_ = true;
   else
      interpolate_ = false;

   return;
}

void ApplicationInfo::setVerticalInterpolationFlag( bool flag )
{
   interpolate_ = flag;
}

ParamInfo* ApplicationInfo::getParamInfo( ParamMap &params, const string& key )
{
   // Check if field is valid
   ParamIterator ii = params.find(key);
   if ( ii == params.end() )
   {
      cout << "ERROR: Could not find the requested field (getParamInfo)" << endl;
      return 0;
   }

   // Get parameter info
   return (*ii).second;
}

ParamInfo* ApplicationInfo::getParamInfo( ParamMap &params, int key, const string& keyBase )
{
   // Build the new key based on keyBase
   char newKey[60];
   sprintf(newKey,"p%06d%s",key,keyBase.substr(7,50).c_str() );

   // Get paraminfo
   ParamInfo* par = this->getParamInfo( params, newKey );
   if ( par )
      return par;

   // Special case: if paramInfo not found and key is "extra" (LNSP or GHBC)
   // then try to find any other "extra" paramInfo (do not use the keyBase info)
   if ( this->isLNSP(key) || this->isGHBC(key) )
   {
      for ( ParamIterator ii = params.begin(); ii != params.end(); ii++ )
      {
         par = (*ii).second;
         if ( par->Parameter() == key )
            return par;
      }
   }

   return par;
}

void ApplicationInfo::levelList( vector<double>& list )
{
   uiLSList_ = list;
   topLevel_ = list[0];
   bottomLevel_ = list[list.size()-1];
}
