/***************************** 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 "MvMiscelaneous.h"
#include "MvTmpFile.h"

#ifdef ECCODES_UI
#include "CodesDirHandler.h"
#endif

#ifdef METVIEW
#include "mars.h"
#endif

#include "Tokenizer.h"

#include <algorithm>
#include <fstream>
#include <cerrno>
#include <cstring>

#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <math.h>
#include <ctype.h>

#if defined(METVIEW_AIX_XLC)
inline double exp10(const double r) { return exp(r*2.302585093);} // exp(r*ln(10))
#endif

namespace metview {

const double cTMELT = 273.16;
const double cR3LES =  17.502;
const double cR3IES =  22.587;
const double cR4LES =  32.19;
const double cR4IES =  -0.7;
const double cRD    =  287.05;
const double cRV    =  461.51;
const double cRESTT =  611.21;
const double cR2ES  = cRESTT*cRD/cRV;
const double cVTMPC1= cRV/cRD-1.;
const double RAD    = 180./3.14159;


std::string shareDirFile(const std::string& fName)
{
    std::string resPath;
#ifdef ECCODES_UI
    resPath=CodesDirHandler::instance()->shareDirFile(fName);
#else
    static const char* linkDir=getenv("METVIEW_LINK_DIR");
    resPath=(linkDir)?(std::string(linkDir) + "/" + fName):std::string("");
#endif
    return resPath;
}

std::string preferenceDirFile(const std::string& fName)
{
    std::string resPath;
#ifdef ECCODES_UI
    resPath=CodesDirHandler::instance()->confDirFile(fName);
#else
    static const char* userDir=getenv("METVIEW_USER_DIRECTORY");
    resPath=(userDir)?(std::string(userDir) + "/System/Preferences/" + fName):std::string("");
#endif
    return resPath;
}

// Call a shell command
// Returns the result and an error message
void shellCommand(const std::string &command, std::stringstream& out,std::stringstream& err)
{
	FILE *in;
	char cbuf[512];

	// Create a temporary file
    MvTmpFile ftmp;
    std::string cmd = command + " 2>" + ftmp.path();

	if (!(in = popen(cmd.c_str() ,"r")) )
		return;

	while(fgets(cbuf, sizeof(cbuf), in) != NULL)
		out << cbuf;

	pclose(in);

    if (!(in = fopen(ftmp.path().c_str() ,"r")) )
		return;

	while(fgets(cbuf, sizeof(cbuf), in) != NULL)
		err << cbuf;

	fclose(in);
}

bool createWorkDir(const std::string& prefix,std::string& tmpPath,std::string& errTxt)
{
    std::string tmpRoot;
	
	char *mvtmp=getenv("METVIEW_TMPDIR");
	if (mvtmp == 0)  
	{	
		errTxt="No env variable METVIEW_TMPDIR is not defined!";
		return false;
	}
	else
	{
        tmpRoot=std::string(mvtmp);
	}

	time_t sec=time(NULL);
    pid_t pid = getpid();

 	std::stringstream out;
  	out << tmpRoot << "/" + prefix + "_" << sec << "_" << pid; 
	tmpPath=out.str();
	
	if(mkdir(tmpPath.c_str(),0777) != 0)
	{
		errTxt="Could not genarate work directory: " + tmpPath;
		return false;
	}
	
	return true;
}  

bool checkGrid(const std::vector<std::string>& areaStr,const std::vector<std::string>& gridStr,
                        std::vector<std::string>& numXy,std::string& errStr)
{
    if(areaStr.size() != 4)
        return false;

    if(gridStr.size() != 2)
        return false;


    std::vector<float> area; // S/W/N/E
    for(std::size_t i=0; i < 4; i++)
    {
        area.push_back(fromString<float>(areaStr[i]));
    }

    if(area[1] > area[3])
    {
        if(area[3] < 0)
            area[3]+=360.;
        else
        {
            errStr="W should be smaller than E! W=" + toString<float>(area[1]) +
                    " E=" +  toString<float>(area[3]);
            return false;
        }
    }

    if(area[0] > area[2])
    {
        errStr="S should be smaller than N! S=" + toString<float>(area[0]) +
                    " N=" +  toString<float>(area[2]);
        return false;
    }

    std::vector<float> grid;
    for(unsigned int i=0; i < 2; i++)
    {
        grid.push_back(fromString<float>(gridStr[i]));
    }

    if(grid[0] < 0.)
    {
        errStr="The W-E grid increment must be greater than 0! The specified value=" + toString<float>(grid[0]);
        return false;
    }
    if(grid[1] < 0.)
    {
        errStr="The N-S grid increment must be greater than 0! The specified value=" + toString<float>(grid[1]);
        return false;
    }

    double eps=0.00000001;

    double nx=(area[3]-area[1])/grid[0];
    if(fabs(nx-round(nx) > eps))
    {
        errStr="W-E grid resolution= " + gridStr[0] + " does not match the area!";
        return false;
    }

    numXy.push_back(toString<int>(static_cast<int>(nx)));

    double ny=(area[2]-area[0])/grid[1];
    if(fabs(ny-round(ny) > eps))
    {
        errStr="N-S grid resolution= " + gridStr[1] + " does not match the area!";
        return false;
    }

    numXy.push_back(toString<int>(static_cast<int>(ny)));

    return true;
}



bool checkGridFitToArea(const std::string& areaStr,const std::string& gridStr)
{
    std::vector<std::string> vArea;
    Tokenizer parseA("/");
    parseA(areaStr,vArea);

    std::vector<std::string> vGrid;
    Tokenizer parseG("/");
    parseG(gridStr,vGrid);

    if(vArea.size() != 4 || vGrid.size() != 2)
        return false;

    std::vector<float> area;
    for(unsigned int i=0; i < 4; i++)
    {
        std::istringstream iss(vArea[i]);
        float d;
        iss >> d;
        area.push_back(d);
    }

    std::vector<float> grid;
    for(unsigned int i=0; i < 2; i++)
    {
        std::istringstream iss(vGrid[i]);
        float d;
        iss >> d;
        if(d <= 0.)
            return false;

        grid.push_back(d);
    }

    double trVal;

    float nx=(area[2]-area[0])/grid[1];
    trVal=trunc(nx);
    if(nx != trVal  ||  static_cast<float>(static_cast<int>(trVal)) != trVal)
        return false;

    float ny=(area[3]-area[1])/grid[0];
    trVal=trunc(ny);
    if(ny != trVal  ||  static_cast<float>(static_cast<int>(trVal)) != trVal)
        return false;

    return true;
}

std::string simplified(const std::string& str)
{
    std::size_t pos1=str.find_first_not_of(" ");
    std::size_t pos2=str.find_last_not_of(" ");

    if(pos1 != std::string::npos && pos2 != std::string::npos && pos2 >= pos1)
    {
        return str.substr(pos1,pos2-pos1+1);
    }
    return std::string();
}


bool isNumber(const std::string& v)
{
    for(size_t i=0; i < v.size(); i++)
    {
        if(isdigit(v[i]) == 0)
            return false;
    }
    return true;
}

std::string toBold(int v)
{
    std::string s;
    s="<b>" + metview::toString(v) + "</b>";
    return s;
}

std::string toBold(float v)
{
    std::string s;
    s="<b>" + metview::toString(v) + "</b>";
    return s;
}

std::string toBold(const std::string& v)
{
    std::string s;
    s="<b>" + v + "</b>";
    return s;
}




/*! 
  \brief computes the saturation  mixing ratio

  This method computes the saturation mixing ratio.

  \param t  temperature in K 
  \param p  pressure in Pa
  \return   saturation mixing ratio in kg/kg 
*/

double saturationMixingRatio(double t, double p)
{
	double e=saturationVapourPressure(t); 
  	return 0.621981*e/(p-e); 
}

/*! 
  \brief computes the relative himidity

  This method computes the relative humidity.

  \param t  temperature in K 
  \param p  pressure in Pa
  \param q  specific humidity in kg/kg
  \return   relative humidity (0-1) 
*/

double relativeHumidity(double t, double p,double q)
{
	double es=saturationVapourPressure(t);
	double e=vapourPressure(p,q);
	
  	return e/es; 
}

/*! 
  \brief computes the relative himidity from dewpoint temperature

  This method computes the relative humidity from dewpoint temperature.

  \param t  temperature in K 
  \param td dewpoint temperature in K 
  \return   relative humidity (0-1) 
*/

double relativeHumidityFromTd(double t, double td)
{
	double es=saturationVapourPressure(td)/saturationVapourPressure(t);	
  	return es; 
}

/*! 
  \brief computes the specific humidity from relative humidity

  This method computes specific humidity from relative humidity

  \param t  temperature in K 
  \param p  pressure in Pa
  \param r  relative humidity (0-1) 
  \return   specific humidity in kg/kg 
*/

double specificHumidity(double t, double p,double r)
{
  	double ws=saturationMixingRatio(t,p);
	double w=r*ws;
	
	return w/(1.+w);
}

/*! 
  \brief computes the specific humidity from dewpoint temperature

  This method computes specific humidity from dewpoint temperature

  \param t  temperature in K 
  \param p  pressure in Pa
  \param td  dewpoint temperature in K 
  \return   specific humidity in kg/kg 
*/

double specificHumidityFromTd(double t, double p,double td)
{
  	double r=relativeHumidityFromTd(t,td);
  	return specificHumidity(t,p,r);

}

/*! 
  \brief computes the saturation vapour pressure over water from temperature
 
  This method computes the saturation vapour pressure
  over water and ice from temperature.

  \param t temperature in K 
  \return pressure in Pa   
*/   

double saturationVapourPressure(double t)
{
	double c1    = 611.21;
	double c3l   = 17.502;
	double c4l   = 32.19;
	double c3i   = 22.587;
	double c4i   = -0.7;
	double t0      = 273.16;
	double ti= 250.16;
	
	//Saturation vapour pressure over water
	double es_water = c1*exp(c3l*(t-t0)/(t-c4l)); 
	
	//Saturation vapour pressure over ice
	double es_ice  = c1*exp( c3i*(t-t0)/(t-c4i)); 

	//fraction of liquid water	
	double alpha;
	if(t <= ti)
	  	alpha=0.;
	else if (t > ti && t <t0)
	  	alpha=(t-ti)/(t0-ti);
	else
	  	alpha=1.;
		
	return alpha*es_water+(1.-alpha)*es_ice;
}	

/*! 
  \brief computes the vapour pressure over from pressure and spec humidity
 
  This method computes the vapour pressure
  from pressure and specific humidity.

  \param p pressure in Pa 
  \param q specific humidity in kg/kg
  \return vapour pressure in Pa   
*/   
double vapourPressure(double p,double q)
{
	double w=q/(1-q);
	return p*w/(0.621981+w);
}  
  
/*! 
  \brief computes the water vapour ppmv from pressure and specific humidity
 
  This method computes the water vapour ppmv
  from pressure and specific humidity.

  \param p pressure in Pa 
  \param q specific humidity in kg/kg
  \return humidity in ppm   
*/     
  
double vapourPPMV(double p,double q)
{
	double pw=vapourPressure(p,q);
	return pw/(p-pw)*1000000;
}  
  
/*! 
  \brief computes the ozone ppmv from mixing ratio
 
  This method computes the ozone ppmv
  from from mixing ratio.

  \param r ozone mixing ratio in kg/kg 
  \return ozone in ppmv  
*/     
  
double ozonePPMV(double r)
{
	double mmr2vmr = 28.97/48.0;
	return 1000000*r*mmr2vmr;
}

double dewPointFromQ(double p, double t, double q, const std::string& formula)
{
   const std::string cMixedFormula( "MIXED_PHASE_0_TO_-23" );
   const std::string cSaturationIce( "SATURATION_OVER_ICE" );
   double ZCVM3, ZCVM4;

   if( formula == cMixedFormula )
   {
      double rtwat   = cTMELT;
      double rtice   = cTMELT - 23.0;
      double foealfa = std::min(1.,pow(((std::max(rtice,std::min(rtwat,t))-rtice)/(rtwat-rtice)),2.0) );

      ZCVM3 = foealfa*cR3LES + (1.-foealfa)*cR3IES;
      ZCVM4 = foealfa*cR4LES + (1.-foealfa)*cR4IES;
   }
   else if ( formula == cSaturationIce )
   {
      if( t > cTMELT )
      {
         ZCVM3=cR3LES;
         ZCVM4=cR4LES;
      }
      else
      {
         ZCVM3=cR3IES;
         ZCVM4=cR4IES;
      }
   }
   else  // SATURATION OVER WATER
   {
      ZCVM3=cR3LES;
      ZCVM4=cR4LES;
   }

   double ZFRAC = log(p*q/(cR2ES*(1.+cVTMPC1*q)))/ZCVM3;

   return (cTMELT-ZFRAC*ZCVM4)/(1.-ZFRAC);
}

double speed( double u, double v )
{
   return sqrt(pow(u,2) + pow(v,2));
}

double direction( double u, double v )
{
   return (-90.0 - atan2(v,u)*RAD);
}

double truncate(double d,int decimals)
{
    double p=pow(10,decimals);
    return round(d*p)/p;
}

#ifdef METVIEW
void writeFileToLogInfo(const std::string& fileName,int maxLineNum)
{
    writeFileToLog(LOG_INFO,fileName,maxLineNum);
}

void writeFileToLogWarn(const std::string& fileName,int maxLineNum)
{
    writeFileToLog(LOG_WARN,fileName,maxLineNum);
}

void writeFileToLogErr(const std::string& fileName,int maxLineNum)
{
    writeFileToLog(LOG_EROR,fileName,maxLineNum);
}

void writeFileToLog(int logId,const std::string& fileName,int maxLineNum)
{
    if(maxLineNum == -1)
    {
        std::ifstream in(fileName.c_str());
        std::string line;
        while(getline(in,line))
        {
            marslog(logId,"%s",line.c_str());
        }
    }
    else
    {
        std::string err;
        std::string txt=getLastLines(fileName,maxLineNum,err);
        std::istringstream in(txt);
        std::string line;
        while(getline(in,line))
        {
            marslog(logId,"%s",line.c_str());
        }
    }
}
#endif


std::string getLastLines(const std::string& fileName,int lastLineNum,std::string& error_msg)
{
    if(lastLineNum <= 0 )
        return string();

    std::ifstream source( fileName.c_str(), std::ios_base::in );
    if (!source) {
          error_msg = "File::get_last_n_lines: Could not open file " + fileName;
          error_msg += " (";
          error_msg += strerror(errno);
          error_msg += ")";
          return string();
     }

    size_t const granularity = 100 * lastLineNum;
    source.seekg( 0, std::ios_base::end );
    size_t fileSize = static_cast<size_t>(source.tellg());
    std::vector<char> buffer;
    int newlineCount = 0;
    while(source
                && buffer.size() != fileSize
                && newlineCount < lastLineNum)
        {
          buffer.resize( std::min( buffer.size() + granularity, fileSize ) );
          source.seekg( -static_cast<std::streamoff>( buffer.size() ), std::ios_base::end );
          source.read( &(buffer.front()), buffer.size() );
          newlineCount = std::count(buffer.begin(), buffer.end(), '\n');
       }

    std::vector<char>::iterator start = buffer.begin();
    while ( newlineCount > lastLineNum ) {
          start = std::find( start, buffer.end(), '\n' ) + 1;
          -- newlineCount;
    }

    return std::string( start, buffer.end() );
}

} //namespace metview
