
//============================================================================
// Name        :
// Author      : Avi
// Revision    : $Revision: #49 $ 
//
// Copyright 2009-2020 ECMWF.
// This software is licensed under the terms of the Apache Licence version 2.0 
// which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. 
// In applying this licence, ECMWF does not waive the privileges and immunities 
// granted to it by virtue of its status as an intergovernmental organisation 
// nor does it submit to any jurisdiction. 
//
// Description : This class is used as a helper class
//============================================================================
#include <boost/lexical_cast.hpp>

#include "Str.hpp"

using namespace std;

namespace ecf {

const char* VALID_NODE_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.";

const char* Str::CHILD_CMD() { static const char* CHILD_CMD = "chd:"; return CHILD_CMD; }
const char* Str::USER_CMD() { static const char* USER_CMD = "--"; return USER_CMD; }
const char* Str::SVR_CMD() { static const char* SVR_CMD = "svr:"; return SVR_CMD; } // Only for automatic check_pt

const std::string& Str::EMPTY() { static std::string empty = std::string(); return empty;}
const std::string& Str::ROOT_PATH() { static std::string root_path  = "/"; return root_path;}
const std::string& Str::PATH_SEPERATOR() { static std::string  path_sep = "/"; return path_sep;}
const std::string& Str::COLON() { static std::string colon = ":"; return colon;}


const std::string& Str::STATE_CHANGE() { static std::string state_change = "   state change "; return state_change;}

const std::string& Str::TASK()   { static std::string task = "TASK"; return task; }
const std::string& Str::FAMILY() { static std::string family = "FAMILY"; return family;}
const std::string& Str::SUITE()  { static std::string suite = "SUITE"; return suite;}
const std::string& Str::ALIAS()  { static std::string alias = "ALIAS"; return alias; }

const std::string& Str::DEFAULT_PORT_NUMBER() { static std::string port_number = "3141"; return port_number;}
const std::string& Str::LOCALHOST()           { static std::string localhost = "localhost"; return localhost;}

const std::string& Str::ECF_PORT()       { static std::string ECF_PORT = "ECF_PORT"; return ECF_PORT;}
const std::string& Str::ECF_RID()        { static std::string ECF_RID = "ECF_RID"; return ECF_RID;}
const std::string& Str::ECF_TRYNO()      { static std::string ECF_TRYNO = "ECF_TRYNO"; return ECF_TRYNO;}
const std::string& Str::ECF_TRIES()      { static std::string ECF_TRIES = "ECF_TRIES"; return ECF_TRIES;}
const std::string& Str::ECF_NAME()       { static std::string ECF_NAME = "ECF_NAME"; return ECF_NAME; }
const std::string& Str::ECF_HOST()       { static std::string ECF_HOST = "ECF_HOST"; return ECF_HOST;}
const std::string& Str::ECF_PASS()       { static std::string ECF_PASS = "ECF_PASS";return ECF_PASS;}
const std::string& Str::ECF_JOB()        { static std::string ECF_JOB = "ECF_JOB"; return ECF_JOB;}
const std::string& Str::ECF_JOBOUT()     { static std::string ECF_JOBOUT = "ECF_JOBOUT"; return ECF_JOBOUT;}
const std::string& Str::ECF_SCRIPT()     { static std::string ECF_SCRIPT = "ECF_SCRIPT"; return ECF_SCRIPT;}
const std::string& Str::ECF_DUMMY_TASK() { static std::string ECF_DUMMY_TASK = "ECF_DUMMY_TASK"; return ECF_DUMMY_TASK;}
const std::string& Str::ECF_NO_SCRIPT()  { static std::string ECF_NO_SCRIPT = "ECF_NO_SCRIPT"; return ECF_NO_SCRIPT;}
const std::string& Str::ECF_MICRO()      { static std::string ECF_MICRO = "ECF_MICRO"; return ECF_MICRO;}
const std::string& Str::ECF_FILES()      { static std::string ECF_FILES = "ECF_FILES"; return ECF_FILES;}
const std::string& Str::ECF_FETCH()      { static std::string ECF_FETCH = "ECF_FETCH"; return ECF_FETCH;}
const std::string& Str::ECF_KILL_CMD()   { static std::string ECF_KILL_CMD = "ECF_KILL_CMD"; return ECF_KILL_CMD;}
const std::string& Str::ECF_STATUS_CMD() { static std::string ECF_STATUS_CMD = "ECF_STATUS_CMD"; return ECF_STATUS_CMD;}

const std::string& Str::ECF_HOME()    { static std::string ECF_HOME = "ECF_HOME"; return ECF_HOME;}
const std::string& Str::ECF_INCLUDE() { static std::string ECF_INCLUDE = "ECF_INCLUDE"; return ECF_INCLUDE;}
const std::string& Str::ECF_JOB_CMD() { static std::string ECF_JOB_CMD = "ECF_JOB_CMD"; return ECF_JOB_CMD;}
const std::string& Str::ECF_OUT()     { static std::string ECF_OUT = "ECF_OUT"; return ECF_OUT;}
const std::string& Str::ECF_EXTN()    { static std::string ECF_EXTN = "ECF_EXTN"; return ECF_EXTN;}
const std::string& Str::ECF_LOG()     { static std::string ECF_LOG = "ECF_LOG"; return ECF_LOG;}


const std::string& Str::WHITE_LIST_FILE() { static std::string WHITE_LIST_FILE = "ecf.lists"; return WHITE_LIST_FILE;}
const std::string& Str::ECF_PASSWD() { static std::string ECF_PASSWD = "ecf.passwd"; return ECF_PASSWD;}
const std::string& Str::ECF_CUSTOM_PASSWD() { static std::string ECF_CUSTOM_PASSWD = "ecf.custom_passwd"; return ECF_CUSTOM_PASSWD;}

const std::string& Str::ALPHANUMERIC_UNDERSCORE()
{
   static string ALPHANUMERIC_UNDERSCORE = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
   return ALPHANUMERIC_UNDERSCORE;
}

const std::string& Str::NUMERIC() { static string NUMERIC = "0123456789"; return NUMERIC; }

void Str::removeQuotes(std::string& s)
{
	if (!s.empty()) {
		if (s[0] == '"' && s[s.size()-1] == '"') {
			s.erase(s.begin());
			s.erase(s.begin() + s.size()-1);
		}
	}
}
// 047 is octal for '
void Str::removeSingleQuotes(std::string& s)
{
	if (!s.empty()) {
		if (s[0] == 047 && s[s.size()-1] == 047) {
			s.erase(s.begin());
			s.erase(s.begin() + s.size()-1);
		}
	}
}

bool Str::replace(std::string& jobLine, const std::string& stringToFind, const std::string& stringToRplace)
{
	size_t pos = jobLine.find(stringToFind);
	if (pos != string::npos) {
		jobLine.replace(pos,stringToFind.length(),stringToRplace);
		return true;
	}
	return false;
}

bool Str::replace_all(std::string& subject, const std::string& stringToFind, const std::string& stringToReplace)
{
   bool replaced = false ;
   size_t pos = 0;
   while ((pos = subject.find(stringToFind, pos)) != std::string::npos) {
      subject.replace(pos, stringToFind.length(), stringToReplace);
      pos += stringToReplace.length();
      replaced = true;
   }
   return replaced;
}

bool Str::extract_data_member_value(const std::string& str, const std::string& data_member_name, std::string& data_member_value)
{
   //        012345678901234567,
   //    str=cmd 1 user:mao
   //    data_member_name=user:
   //    data_member_value=ma0
   std::string::size_type start = str.find(data_member_name);
   if (start != std::string::npos) {
      start += data_member_name.size();
      data_member_value.clear();
      for(size_t i = start; i < str.size(); i++) {
         if (str[i] == ' ') break;
         data_member_value += str[i];
      }
      return true;
   }
   return false;
}


void Str::replaceall(std::string& subject, const std::string& search, const std::string& replace)
{
   boost::replace_all(subject, search, replace);
}

#define USE_STRINGSPLITTER 1
void Str::split(const std::string& line, std::vector< std::string >& tokens,const std::string& delimiters )
{
#ifdef USE_STRINGSPLITTER
   Str::split_using_string_view(line,tokens,delimiters);
#else
   Str::split_orig(line,tokens,delimiters);
#endif
//   This test will split a line 1000000 times: 'This is a long string that is going to be used to test the performance of splitting with different Implementations the fastest times wins '
//   Time for istreamstream 1000000                 times = 2.131s wall, (2.140s user + 0.000s system = 2.140s) CPU (100.4%)
//   Time for std::getline 1000000                  times = 1.490s wall, (1.490s user + 0.000s system = 1.490s) CPU (100.0%)
//   Time for boost::split 1000000                  times = 0.930s wall, (0.920s user + 0.000s system = 0.920s) CPU (99.0%)
//   Time for Str::split_orig 1000000               times = 1.065s wall, (1.070s user + 0.000s system = 1.070s) CPU (100.5%)
//   Time for Str::split_orig1 1000000              times = 0.561s wall, (0.560s user + 0.000s system = 0.560s) CPU (99.9%)
//   Time for Str::split_using_string_view2 1000000 times = 0.686s wall, (0.690s user + 0.000s system = 0.690s) CPU (100.6%)
//   Time for Str::split_using_string_view 1000000  times = 0.482s wall, (0.480s user + 0.000s system = 0.480s) CPU (99.6%)
//   Time for make_split_iterator::split 1000000    times = 3.611s wall, (3.610s user + 0.000s system = 3.610s) CPU (100.0%)
//   Time for boost::string_view 1000000            times = 0.769s wall, (0.770s user + 0.000s system = 0.770s) CPU (100.1%)
//   Time for boost::string_view(2) 1000000         times = 0.688s wall, (0.690s user + 0.000s system = 0.690s) CPU (100.3%)
//  ACore:: ...test_str_split_perf_with_file
//   This test will split each line in file ${ECF_TEST_DEFS_DIR}vsms2.31415.def
//   Time for istreamstream 2001774                 times = 1.567s wall, (1.570s user + 0.000s system = 1.570s) CPU (100.2%)
//   Time for std::getline 2001774                  times = 2.456s wall, (2.460s user + 0.000s system = 2.460s) CPU (100.2%)
//   Time for boost::split 2001774                  times = 1.698s wall, (1.690s user + 0.000s system = 1.690s) CPU (99.5%)
//   Time for Str::split_orig 2001774               times = 0.822s wall, (0.830s user + 0.000s system = 0.830s) CPU (101.0%)
//   Time for Str::split_orig1 2001774              times = 0.502s wall, (0.500s user + 0.000s system = 0.500s) CPU (99.7%)
//   Time for Str::split_using_string_view 2001774  times = 0.489s wall, (0.490s user + 0.000s system = 0.490s) CPU (100.3%)
//   Time for Str::split_using_string_view2 2001774 times = 0.639s wall, (0.630s user + 0.000s system = 0.630s) CPU (98.6%)
//   Time for boost::make_split_iterator 2001774    times = 3.338s wall, (3.340s user + 0.000s system = 3.340s) CPU (100.1%)
//   Time for boost::string_view 2001774            times = 0.599s wall, (0.600s user + 0.000s system = 0.600s) CPU (100.1%)
//   Time for boost::string_view(2) 2001774         times = 0.696s wall, (0.700s user + 0.000s system = 0.700s) CPU (100.6%)
}

void Str::split_orig(const std::string& line, std::vector< std::string >& tokens,const std::string& delimiters )
{
   // Skip delimiters at beginning.
   string::size_type lastPos = line.find_first_not_of( delimiters, 0 );

   // Find first "non-delimiter".
   string::size_type pos = line.find_first_of( delimiters, lastPos );

   while ( string::npos != pos || string::npos != lastPos ) {
      tokens.push_back( line.substr( lastPos, pos - lastPos ) ); // Found a token, add it to the vector.
      lastPos = line.find_first_not_of( delimiters, pos );       // Skip delimiters.  Note the "not_of"
      pos = line.find_first_of( delimiters, lastPos );           // Find next "non-delimiter"
   }
}

void Str::split_orig1(const std::string& line, std::vector< std::string >& tokens,const std::string& delims )
{
   auto first = std::cbegin(line);
   auto end = std::cend(line);

   while (first != end)
   {
       const auto second = std::find_first_of(first, end, std::cbegin(delims), std::cend(delims));

       if (first != second)
           tokens.emplace_back(first, second);

       if (second == end)
           break;

       first = std::next(second);
   }
}

void Str::split_using_string_view(boost::string_view strv, std::vector< std::string >& output, boost::string_view delims )
{
   // Uses pointers
   for (auto first = strv.data(), second = strv.data(), last = first + strv.size(); second != last && first != last; first = second + 1) {
      second = std::find_first_of(first, last, std::cbegin(delims), std::cend(delims));

      if (first != second)
         output.emplace_back(first, second - first);
   }
}

void Str::split_using_string_view2(boost::string_view strv, std::vector< std::string >& output, boost::string_view delims )
{
   size_t first = 0;

   size_t strv_size = strv.size();
   while (first < strv_size)
   {
      const auto second = strv.find_first_of(delims, first);

      if (first != second) {
         boost::string_view ref = strv.substr(first, second-first);
         output.emplace_back(ref.begin(),ref.end());
      }

      if (second == boost::string_view::npos)
         break;

      first = second + 1;
   }
}

bool Str::get_token(boost::string_view str,size_t pos,std::string& token,boost::string_view delims)
{
//   Time for StringSplitter::get_token 250000 times = 1.457s wall, (1.460s user + 0.000s system = 1.460s) CPU (100.2%)
//   Time for Str::get_token            250000 times = 0.566s wall, (0.560s user + 0.000s system = 0.560s) CPU (99.0%)
//   Time for Str::get_token2           250000 times = 0.668s wall, (0.670s user + 0.000s system = 0.670s) CPU (100.3%)
//   Time for Str::get_token3           250000 times = 0.620s wall, (0.620s user + 0.000s system = 0.620s) CPU (100.0%)

   size_t current_pos = 0;
   auto first = std::cbegin(str);
   auto end = std::cend(str);

   while (first != end) {
       const auto second = std::find_first_of(first, end, std::cbegin(delims), std::cend(delims));

       if (first != second) {
          if (current_pos == pos) {
             token = std::string(first, second);
             return true;
          }
          current_pos++;
       }

       if (second == end) break;

       first = std::next(second);
   }
   return false;

}

bool Str::get_token2(boost::string_view strv,size_t pos,std::string& token,boost::string_view delims)
{
   size_t current_pos = 0;
   size_t first = 0;
   while (first < strv.size()) {
      const auto second = strv.find_first_of(delims, first);

      if (first != second) {
         if (current_pos == pos) {
            boost::string_view ref = strv.substr(first, second-first);
            token = std::string(ref.begin(),ref.end());
            return true;
         }
         current_pos++;
      }

      if (second == boost::string_view::npos)
         break;

      first = second + 1;
   }
   return false;
}

bool Str::get_token3(boost::string_view str,size_t pos,std::string& token,boost::string_view delims)
{
   size_t current_pos = 0;
   for (auto first = str.data(), second = str.data(), last = first + str.size(); second != last && first != last; first = second + 1) {

      second = std::find_first_of(first, last, std::cbegin(delims), std::cend(delims));
      if (first != second) {
         if (current_pos == pos) {
            token = std::string(first, second - first);
            return true;
         }
         current_pos++;
      }
   }
   return false;
}

boost::split_iterator<std::string::const_iterator> Str::make_split_iterator(const std::string& line,const std::string& delimiters)
{
   return boost::make_split_iterator(line, boost::algorithm::token_finder(boost::is_any_of(delimiters),boost::algorithm::token_compress_on));
}

static bool caseInsCharCompare(char a, char b) { return  (toupper(a) == toupper(b)); }

bool Str::caseInsCompare( const std::string& s1, const std::string& s2)
{
	return ( (s1.size() == s2.size()) && equal(s1.begin(),s1.end(), s2.begin(), caseInsCharCompare));
}


struct case_insensitive_less : public std::binary_function< char,char,bool >
{
    bool operator () (char x, char y) const {
        if (toupper(x) == toupper(y)) {
           return x > y;
        }
        return toupper( static_cast< unsigned char >(x)) <
               toupper( static_cast< unsigned char >(y));
    }
};
bool Str::caseInsLess( const std::string& a, const std::string& b)
{
   return std::lexicographical_compare( a.begin(),a.end(),
           b.begin(),b.end(), case_insensitive_less() );
}


struct case_insensitive_greater : public std::binary_function< char,char,bool >
{
    bool operator () (char x, char y) const {
        if (toupper(x) == toupper(y)) {
           return x < y;
        }
        return toupper( static_cast< unsigned char >(x)) >
               toupper( static_cast< unsigned char >(y));
    }
};
bool Str::caseInsGreater( const std::string& a, const std::string& b)
{
   return std::lexicographical_compare( a.begin(),a.end(),
           b.begin(),b.end(), case_insensitive_greater() );
}


bool Str::valid_name(const std::string& name, std::string &msg)
{
   // valid names are alphabetic (alphanumeric | underscore | .)
   // however we can't have a leading '.' as that can interfere with trigger expressions

   // verify that the string is not empty
   if ( name.empty() ) {
	   msg = "Invalid name. Empty string.";
	   return false;
   }

   // verify that the first character is alphabetic or has underscore
 	bool result = Str::ALPHANUMERIC_UNDERSCORE().find( name[0], 0 ) != string::npos;
	if ( !result ) {
      msg = "Valid names can only consist of alphanumeric characters "
            ",underscores and dots. The first character cannot be a dot: ";
      msg += name;
		return false;
	}

   // verify that any other characters are alphanumeric or underscore
   if (name.size() > 1) {
      result = name.find_first_not_of(VALID_NODE_CHARS, 1) == string::npos;
      if ( !result ) {
         msg = "Valid names can only consist of alphanumeric characters "
               ",underscores and dots. The first character cannot be a dot. ";
         if (name.find('\r') != string::npos)  msg += "Window's line ending ? ";
         msg += "'";
         msg += name;
         msg += "'"; // use '<name>' to show if PC format. i.e carriage return
      }
   }

   return result;
}

bool Str::valid_name(const std::string& name)
{
   // valid names are alphabetic (alphanumeric | underscore | .)
   // however we can't have a leading '.' as that can interfere with trigger expressions

   // verify that the string is not empty
   if ( name.empty() ) {
      return false;
   }

   // verify that the first character is alphabetic or has underscore
   bool result = Str::ALPHANUMERIC_UNDERSCORE().find( name[0], 0 ) != string::npos;
   if ( !result ) {
      return false;
   }

   // verify that any other characters are alphanumeric or underscore
   if (name.size() > 1) {
      result = name.find_first_not_of(VALID_NODE_CHARS, 1) == string::npos;
   }

   return result;
}

int Str::to_int( const std::string& the_str, int error_return)
{
	if ( the_str.find_first_of( Str::NUMERIC(), 0 ) != std::string::npos ) {
		try {
			return boost::lexical_cast< int >( the_str );
		}
		catch ( boost::bad_lexical_cast&) {}
	}
	return error_return;
}

bool Str::truncate_at_start(  std::string& fileContents, size_t max_lines)
{
	if (fileContents.empty()) return false;

	/// Truncate from the front
	size_t no_of_new_lines = 0;
	for(size_t i =fileContents.size()-1; i >0; --i) {
		if (fileContents[i] == '\n') no_of_new_lines++;
		if ( no_of_new_lines >=  max_lines) {
			fileContents.erase(fileContents.begin(),fileContents.begin() + i +1); //skip new line at start of file
			return true;
		}
	}
	return false;
}

bool Str::truncate_at_end(  std::string& fileContents, size_t max_lines)
{
   if (fileContents.empty()) return false;

   /// Truncate from the back
   size_t no_of_new_lines = 0;
   size_t the_size = fileContents.size();
   for(size_t i =0; i < the_size; ++i) {
      if (fileContents[i] == '\n') no_of_new_lines++;
      if ( no_of_new_lines >=  max_lines) {
         fileContents.erase(fileContents.begin()+i+1,fileContents.end()); //skip new line at end of file
         return true;
      }
   }
   return false;
}

std::string Str::dump_string_vec(const std::vector<std::string>& vec)
{
   std::string str;
   for(const auto& s: vec) {
      str += s;
      str += "\n";
   }
   return str;
}

}
