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

#define HOV_SP  1013.25     // stardard pressure

void HovHeightToolkit::GenerateKey(string &str, MvRequest& rq)
{
   // The level value is ignored to build the key
   int par = rq("PARAM");
   int level = 0;
   const char* expver = rq("EXPVER");
   char key[16];

   ostrstream oss( key, sizeof( key ) );
   oss << setfill( '0' )
       << setw( 1 ) << 'p'
       << setw( 3 ) << par
       << setw( 4 ) << level
       << setw( 4 ) << ( expver ? expver : "_" )
       << ends;

   str = key;

   return;
}

void HovHeightToolkit::UpdateLevelList(MvField& field, double ilevel)
{
   // Check if conversion from model level to pressure level is needed
   double level = (vertAxisType_ == HOV_PRESSURE && field.isModelLevel()) ? field.meanML_to_Pressure_bySP(double(HOV_SP*100.),(int)ilevel)/100. : ilevel;

   vector<double>::iterator iter = find(coord1_.begin(),coord1_.end(),level);
   if ( iter == coord1_.end() )
      coord1_.push_back(level);
}

bool HovHeightToolkit::ComputeSecondCoord()
{
   // The variable coord1_ was already initialized during
   // the initialisation of the parameters

   // Initialise number of points
   this->NPoints(coord1_.size());

   return true;
}

bool HovHeightToolkit::ComputeValues(MvField &field, int index)
{
   MvFieldExpander x(field);
   xint_[index] = field.integrate( y1_,x1_,y2_,x2_);

   return true;
}

bool HovHeightToolkit::GenerateData(MvRequest& data)
{
   // Write initial info and variables to the netCDF
   if ( !this->Initialiser() )
      return false;

   // Allocate internal variables
   if ( xint_ )
      delete xint_;
   xint_ = new double[nrPoints_];

   // Sort data by level, expver and param
   MvFieldSet fs(data);
   MvFieldSetIterator iter(fs);
   iter.rewind();
   iter.sort("LEVELIST", '>');
   iter.sort("STEP");
   iter.sort("TIME");
   iter.sort("DATE");
   iter.sort("EXPVER");
   iter.sort("PARAM");

   // Generate data
   int currentGenerated = 0, lastNrGenerated = -1;
   int ind = 0;
   string sfirst = "FIRSTFIELD";
   string timeKey,keystr;
   string lastTimeKey = sfirst;
   string lastKey = sfirst;
   MvField field, lastField;
   ParamIterator paramIter;
   while( field = iter() )
   {
      currentGenerated++;

      // Create keys
      MvRequest rq = field.getRequest();
      GenerateKey(keystr,rq);
      GenerateTimeKey(timeKey,rq);

      // Compute data
      if ( !ComputeValues(field,ind++) )
         return false;

      // Get all the computed level values for each time
      if ( ind < nrPoints_ )
      {
         // Extra check
         if ( lastTimeKey != timeKey && lastTimeKey != sfirst )
         {
            marslog(LOG_EROR, "Input data not consistent. Probably number of levels is not the same for all time stamps");
            return false;
         }
         lastTimeKey = timeKey;
         continue;
      }

      // Make sure the time stamps are the same
      if ( lastTimeKey != timeKey)
      {
         marslog(LOG_EROR, "Input data not consistent. Probably number of levels is not the same for all time stamps");
         return false;
      }

      // Save data in the ParamInfo structure
      ParamInfo* par = this->GetParamInfo( keystr );
      if ( !par )
         return false;

      par->FillIndex(xint_,timeKey,nrPoints_);

      // Write out data, if it can be grouped together
      if ( lastKey != keystr && lastKey != sfirst )
      {
         if ( !NcWriteData(lastField,lastKey) )
            return false;
         lastNrGenerated = currentGenerated;
      }

      lastKey = keystr;
      lastField = field;
      lastTimeKey = sfirst;
      ind = 0;
   }

   // Write out the data in the netcdf file
   if ( lastNrGenerated <=  currentGenerated )
   {
      if ( ! NcWriteData(lastField,lastKey) )
         return false;
   }
   netcdf_->close();

   return true;
}

string HovHeightToolkit::GetTitle(ParamInfo *par)
{
   char title[256];
   sprintf(title,"Hovmoeller of %s %s (%.1f%s/%.1f%s/%.1f%s/%.1f%s)",
           par->ParamLongName().c_str(),par->ExpVerTitle().c_str(),
           (y1_< 0) ? -y1_ : y1_, (y1_<0) ? "S" : "N",
           (x1_< 0) ? -x1_ : x1_, (x1_<0) ? "W" : "E",
           (y2_< 0) ? -y2_ : y2_, (y2_<0) ? "S" : "N",
           (x2_< 0) ? -x2_ : x2_, (x2_<0) ? "W" : "E" );

   return string(title);
}

bool HovHeightToolkit::GetInputInfo(MvRequest& in)
{
   // Get area
   y1_ = in("AREA",0);
   x1_ = in("AREA",1);
   y2_ = in("AREA",2);
   x2_ = in("AREA",3);

   // Check if coordinates follow Mars rules (n/w/s/e)
   bool swap = false;
   if ( x1_ > x2_ )
   {
      double W = x1_;
      x1_ = x2_;
      x2_ = W;
      swap = true;
   }
   if( y2_ > y1_ )
   {
      double W = y1_;
      y1_ = y2_;
      y2_ = W;
      swap = true;
   }

   // Send a warning message
   if ( swap )
//      marslog(LOG_WARN,"WARNING: Input geographical coordinates do not follow MARS rules (n/w/s/e). Values have been swapped.");
      cout << "WARNING: Input geographical coordinates do not follow MARS rules (n/w/s/e). Values have been swapped." << endl;

   // Get vertical axis type
   vertAxisType_ = ( (const char*)in("VERTICAL_LEVEL_TYPE") && strcmp(in("VERTICAL_LEVEL_TYPE"),"PRESSURE") == 0 ) ? HOV_PRESSURE : HOV_ASINDATA;

   // Set axis orientation
   verticalAxis_ = HOV_GEO;

   // Initialize x/y resolution  ??? Do we need this ???
   gridEW_= gridNS_= 1;   //in("RESOLUTION");

   return true;
}

bool HovHeightToolkit::GetInputInfo( MvNetCDF* netcdf )
{
   // Get common information from the netCDF file
   if ( !GetInputCommonInfo(netcdf) )
      return false;

   // Retrieve attributes values
   MvRequest req = netcdf->getRequest();

   // Retrieve vertical type axes flag
   const char* cp = (const char*)req("vertical_type");
   if ( cp == NULL )
   {
      marslog(LOG_EROR, "Netcdf input file error: attribute vertical_type not found");
      return false;
   }
   vertAxisType_ = ( string(cp) == "PRESSURE" ) ? HOV_PRESSURE : HOV_ASINDATA;

   // Set axis orientation
   verticalAxis_ = HOV_GEO;

   return true;
}

void HovHeightToolkit::NcWriteGlobalAttributesApp()
{
   // Add average direction
   const char* adir =  ( vertAxisType_ == HOV_PRESSURE ) ? "PRESSURE" : "AS_IN_DATA";
   netcdf_->addAttribute("vertical_type",adir);
}

bool HovHeightToolkit::NcWriteSecondCoord()
{
   // Add second coordinate to the netCDF file
   vector<string> v_sdim(1,GetSecondCoordName());
   vector<long> v_ndim(1,nrPoints_);
   MvNcVar *ncx = netcdf_->addVariable(v_sdim[0],ncDouble,v_ndim,v_sdim);
   ncx->addAttribute("long_name", "Atmospheric Levels");
   ncx->addAttribute("units", "hPa");
   ncx->put(coord1_,(long)nrPoints_);

   return true;
}

bool HovHeightToolkit::consistencyCheck( MvRequest& req1, MvRequest& req2)
{
   // Check input TYPE
   if ( (const char*)req1("TYPE") != (const char*)req2.getVerb() )
   {
      marslog(LOG_EROR,"TYPE parameter is not consistent");
      return false;
   }

   // Check coordinates
   if ( (double)req1("AREA",0) != (double)req2("AREA",0) ||
        (double)req1("AREA",1) != (double)req2("AREA",1) ||
        (double)req1("AREA",2) != (double)req2("AREA",2) ||
        (double)req1("AREA",3) != (double)req2("AREA",3)
      )
   {
      marslog(LOG_EROR,"AREA coordinates are not consistent");
      return false;
   }

   // Check VERTICAL_LEVEL_TYPE
   if ( (const char*)req1("VERTICAL_LEVEL_TYPE") != (const char*)req2("VERTICAL_LEVEL_TYPE") )
   {
      marslog(LOG_EROR,"VERTICAL_LEVEL_TYPE parameter is not consistent");
      return false;
   }

   return true;
}

MvRequest HovHeightToolkit::CreateViewRequest()
{
   MvRequest viewReq("MHOVMOELLERVIEW");

   viewReq("TYPE") = this->ApplicationType();
   viewReq.addValue("AREA",y1_);
   viewReq.addValue("AREA",x1_);
   viewReq.addValue("AREA",y2_);
   viewReq.addValue("AREA",x2_);

   viewReq("VERTICAL_LEVEL_TYPE") = ( vertAxisType_ == HOV_PRESSURE ) ? "PRESSURE" : "AS_IN_DATA";

   viewReq("_CLASS") = "MHOVMOELLERVIEW";
   viewReq("_DEFAULT") = 1;
   viewReq("_DATAATTACHED") = "YES";

   return viewReq;
}

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

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

   // Compute Hovmoeller diagram
   origReq_ = in;
   if (!Compute(in,out))
      setError(1, "Failed to generate Hovmoeller Vertical.");

cout << "Request OUT:" << endl;
out.print();
}

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

// Translate Metview 3 HeightHovmoeller Data to Metview 4 definition. Call Metview 4
// server to process the job.
void HeightHovmoellerM3::serve(MvRequest& in,MvRequest& out)
{
   // Send a general warning message
   setError(0, "The Metview 3 Vertical-Hovmoeller DATA icon is deprecated. An automatic translation to the correspondent Metview 4 icon will be performed internally, but may not work for all cases. It is recommended to manually replace the old icons with their new equivalents.");

   // There are input parameters that are no longer available in Metview 4.
   // Remove them and send a warning message.
   setError(0,"The Metview 3 Height-Hovmoeller DATA icon is deprecated. Parameters TIME_AXIS_DIRECTION, HEIGHT_AXIS_DIRECTION, TIME_AXIS and HEIGHT_AXIS will not be translated internally.");
   MvRequest req = in;
   req.unsetParam("TIME_AXIS_DIRECTION");
   req.unsetParam("HEIGHT_AXIS_DIRECTION");
   req.unsetParam("TIME_AXIS");
   req.unsetParam("HEIGHT_AXIS");

   // Parameter HEIGHT_AXIS_TYPE is replaced by VERTICAL_LEVEL_TYPE in Metview 4
   if ( (const char*)req("HEIGHT_AXIS_TYPE") )
   {
      req("VERTICAL_LEVEL_TYPE") = req("HEIGHT_AXIS_TYPE");
      req.unsetParam("HEIGHT_AXIS_TYPE");
   }

   // Keep the remaining parameters and update the VERB
   req.setVerb("VERTICAL_HOVM");

   // Call the server to process the job
   HeightHovmoeller::serve(req,out);
}
