// ------------------------------------------------------------------------
// kvu_utils.cpp: Miscellaneous helper routines
// Copyright (C) 1999-2004,2009 Kai Vehmanen
//
// Attributes:
//     eca-style-version: 2
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
// ------------------------------------------------------------------------

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <algorithm> /* find() */
#include <cstdlib> /* atoi() */
#include <iostream>
#include <string>
#include <vector>

#if HAVE_LOCALE_H
#include <locale.h> /* setlocale */
#endif

#include <time.h> /* nanosleep() */
#include <sys/time.h> /* gettimeofday() */
#include <unistd.h> /* usleep() */
#include <ctype.h> /* isspace(), toupper() */

#include "kvu_dbc.h"
#include "kvu_utils.h"

using namespace std;

static string::const_iterator kvu_priv_find_next_instance(const string& arg, const string::const_iterator& curpos, const string::value_type value);
static void kvu_priv_strip_escapes(string* const input, const string& escaped_char);

/**
 * Returns a string where all regex metachars in 'arg'
 * have been quoted using a backslash.
 *
 * Reference: man regex(7)
 */
string kvu_string_regex_meta_escape(const string& arg)
{
  string result;

  /* regex metachar set: ^.[]$()|*+?{}\ */

  string::const_iterator p = arg.begin();
  while(p != arg.end()) {
    if (*p == '^') result += "\\^";
    else if (*p == '.') result += "\\.";
    else if (*p == '[') result += "\\[";
    else if (*p == ']') result += "\\]";
    else if (*p == '$') result += "\\$";
    else if (*p == '(') result += "\\(";
    else if (*p == ')') result += "\\)";
    else if (*p == '|') result += "\\|";
    else if (*p == '*') result += "\\*";
    else if (*p == '+') result += "\\+";
    else if (*p == '?') result += "\\?";
    else if (*p == '{') result += "\\{";
    else if (*p == '}') result += "\\}";
    else if (*p == '\\') result += "\\\\";
    else 
      result += *p;

    ++p;
  }

  return result;
}

/**
 * Returns a string where all meta characters in 'arg'
 * have been quoted using a backslash.
 *
 * Reference: man sh(1), man bash(1)
 */
string kvu_string_shell_meta_escape(const string& arg)
{
  string result;

  string::const_iterator p = arg.begin();
  while(p != arg.end()) {
    if (*p == '"') result += "\\\"";
    else if (*p == '\'') result += "\\\'";
    else if (*p == ' ') result += "\\ ";
    else if (*p == '|') result += "\\|";
    else if (*p == '&') result += "\\&";
    else if (*p == ';') result += "\\;";
    else if (*p == '(') result += "\\(";
    else if (*p == ')') result += "\\)";
    else if (*p == '<') result += "\\<";
    else if (*p == '>') result += "\\>";
    else 
      result += *p;

    ++p;
  }

  return result;
}

/**
 * Converts a string to a vector of strings (words).
 * Whitespace is used as the separator.
 *
 * Note! This function is obsolete, use @see string_to_tokens() 
 *       instead.
 */
vector<string> kvu_string_to_words(const string& s)
{
  return kvu_string_to_tokens(s);
}

/**
 * Converts a string to a vector of token strings.
 * Whitespace is used as the separator.
 */
vector<string> kvu_string_to_tokens(const string& s)
{
  vector<string> vec;
  string stmp = "";

  for(string::const_iterator p = s.begin(); p != s.end(); p++) {
    if (isspace(*p) == 0)
      stmp += *p;
    else {
      if (stmp == "") continue;
      vec.push_back(stmp);
      stmp = "";
    }
  }
  if (stmp.size() > 0)
    vec.push_back(stmp);

  return vec;
}

/**
 * Converts a string to a vector of token strings.
 * Whitespace is used as the token separator. 
 *
 * Unlike string_to_tokens(), quotes can be used to mark 
 * groups of words as tokens (e.g. "this is one token").
 * The tokens are not removed from the string. 
 * Single-quotes (') are not supported.
 *
 * It's also possible to add individual whitespace
 * characted by escaping them with a backclash (e.g.
 * 'this\ is\ one\ token\ '). Escaped characters are
 * not considered as possible separators.
 */
vector<string> kvu_string_to_tokens_quoted(const string& s)
{
  vector<string> vec;
  string stmp;
  bool quoteflag = false;

  for(string::const_iterator p = s.begin(); p != s.end(); p++) {
    if (*p == '\"') {
      quoteflag = !quoteflag;
      stmp += *p;
    }
    else if (*p == '\\') {
      p++;
      if (p == s.end()) break;
      stmp += *p;
    }
    else if (isspace(*p) == 0 || quoteflag == true) {
      stmp += *p;
    }
    else {
      /* note: token ready, add to vector if length is non-zero */
      if (stmp.size() == 0) continue;

      vec.push_back(stmp);
      stmp = "";
      DBC_CHECK(stmp.size() == 0);
    }
  }
  if (stmp.size() > 0)
    vec.push_back(stmp);

  return vec;
}

/**
 * Converts a string to a vector of strings.
 *
 * @param str string to be converted
 * @param separator character to be used for separating items
 */
vector<string> kvu_string_to_vector(const string& str, 
				    const string::value_type separator)
{
  vector<string> vec;
  string stmp = "";

  for(string::const_iterator p = str.begin(); p != str.end(); p++) {
    if (*p != separator)
      stmp += *p;
    else {
      if (stmp == "") continue;
      vec.push_back(stmp);
      stmp = "";
    }
  }
  if (stmp.size() > 0)
    vec.push_back(stmp);

  return vec;
}

/**
 * Converts a string to a vector of integers.
 *
 * @param str string to be converted
 * @param separator character to be used for separating items
 */
vector<int> kvu_string_to_int_vector(const string& str, 
				     const string::value_type separator)
{
  vector<int> vec;
  string stmp = "";

  for(string::const_iterator p = str.begin(); p != str.end(); p++) {
    if (*p != separator)
      stmp += *p;
    else {
      if (stmp == "") continue;
      vec.push_back(atoi(stmp.c_str()));
      stmp = "";
    }
  }
  if (stmp.size() > 0)
    vec.push_back(atoi(stmp.c_str()));

  return vec;
}

/**
 * Return a modified copy of vector 'str_vector' where 'from' has
 * been replaced with 'to' in all items.
 */
vector<string> kvu_vector_search_and_replace(const vector<string>& str_vector, 
					     const string& from,
					     const string& to)
{
  vector<string> vstmp;
  vector<string>::const_iterator p = str_vector.begin();
  while(p != str_vector.end()) {
    vstmp.push_back(kvu_string_search_and_replace(*p, from, to));
    ++p;
  }
  return vstmp;
}

/**
 * Converts a vector of strings to a single string.
 *
 * @param str vector of strings to be converted
 * @param separator string that is inserted between items
 */
string kvu_vector_to_string(const vector<string>& str, 
			    const string& separator)
{

  string stmp;

  vector<string>::const_iterator p = str.begin();
  while(p != str.end()) {
    stmp += *p;
    ++p;
    if (p != str.end()) 
      stmp += separator;
  }

  return stmp;
}

/**
 * Return a new string, where all 'from' characters are
 * replaced with 'to' characters.
 */
string kvu_string_search_and_replace(const string& str, 
				     const string::value_type from,
				     const string::value_type to)
{
  string stmp (str);
  for(vector<string>::size_type p = 0; p < str.size(); p++) {
    if (str[p] == from) stmp[p] = to;
    else stmp[p] = str[p];
  }

  return stmp;
}

/**
 * Return a new string, where all 'from' characters are
 * replaced with 'to' characters.
 */
string kvu_string_search_and_replace(const string& str, 
				     const string& from,
				     const string& to)
{
  string tmp (str);
  size_t pos = 0;
  while((pos = tmp.find(from, pos)) != string::npos) {
    tmp.replace(pos, from.size(), to);
    pos += (to.size() > from.size() ? to.size() : from.size());
  }
  return tmp;
}

/**
 * Case-insensitive string compare. Ignores preceding and 
 * trailing white space.
 */
bool kvu_string_icmp(const string& first, const string& second)
{
  string a = first;
  string b = second;

  a = kvu_remove_trailing_spaces(a);
  a = kvu_remove_preceding_spaces(a);
  a = kvu_convert_to_uppercase(a);

  b = kvu_remove_trailing_spaces(b);
  b = kvu_remove_preceding_spaces(b);
  b = kvu_convert_to_uppercase(b);

  return a == b;
}

/**
 * Removes all trailing white space
 */
string kvu_remove_trailing_spaces(const string& a)
{
  string r = "";
  string::const_reverse_iterator p;
  for(p = a.rbegin(); p != a.rend(); p++) {
    if (*p != ' ') break;
  }
  for(; p != a.rend(); p++) {
    r = *p + r;
  }
  return  r;
}

/**
 * Removes all preciding white space
 */
string kvu_remove_preceding_spaces(const string& a)
{
  string r = "";
  string::const_iterator p;
  for(p = a.begin(); p != a.end(); p++) {
    if (*p != ' ') break;
  }
  for(; p != a.end(); p++) {
    r += *p;
  }
  return r;
}

/**
 * Removes all surrounding white spaces
 */
string kvu_remove_surrounding_spaces(const string& a)
{
  string::const_iterator p,q;
  for(p = a.begin(); p != a.end() && *p == ' '; p++);
  for(q = (a.end() - 1); q != a.begin() && *q == ' '; q--);
  return string(p,q + 1);
}

/**
 * Converts string to uppercase using toupper(int)
 */
string kvu_convert_to_uppercase(const string& a)
{
  string r = a;
  for(string::iterator p = r.begin(); p != r.end(); p++)
    *p = toupper(*p);
  return r;
}

/**
 * Converts string to lowercase using tolower(int)
 */
string kvu_convert_to_lowercase(const string& a)
{ 
  string r = a;
  for(string::iterator p = r.begin(); p != r.end(); p++)
    *p = tolower(*p);
  return r;
}

/**
 * Converts string to uppercase using toupper(int)
 * Modifies the parameter object.
 */
void kvu_to_uppercase(string& a)
{
  string::iterator p = a.begin();
  while(p != a.end()) {
    *p = toupper(*p);
    ++p;
  }
}

/**
 * Converts string to lowercase using tolower(int)
 * Modifies the parameter object.
 */
void kvu_to_lowercase(string& a)
{
  string::iterator p = a.begin();
  while(p != a.end()) {
    *p = tolower(*p);
    ++p;
  }
}

/**
 * Finds the next instance of character 'value' and returns its
 * position.
 * 
 * All backslash escaped instances of 'value' ("\<value>")
 * are ignored in the search. Also instance of 'value' within 
 * a pair of double quotes are ignored.
 *
 * Note that general backlash escaping is not supported, i.e. 
 * "\<character>" is not interpreted as "<character>".
 *
 * @return position of next 'value' or arg.end() if not found
 */
static string::const_iterator kvu_priv_find_next_instance(const string& arg, const string::const_iterator& start, const string::value_type value)
{
  string::const_iterator curpos = start, ret = arg.end(), nextquote = arg.end();

  while(curpos != arg.end()) {
    ret = find(curpos, arg.end(), value);

    nextquote = find(curpos, arg.end(), '"'); 
    if (nextquote < ret) {

      /* step: ignore quoted part of the input string */
      nextquote = find(nextquote + 1, arg.end(), '"');
      if (nextquote != arg.end()) {
	curpos = nextquote + 1;
	ret = find(curpos, arg.end(), value);
      }
    }

    if (ret != arg.end() && ret != arg.begin()) {
      string::const_iterator prev = ret - 1;
      if ((*prev) == '\\') {
	curpos = ret + 1;
	continue;
      }
    }

    break;
  }

  return ret;
}

/**
 * Returns the nth argument from a formatted string
 *
 * @param number the argument number 
 * @param argu a formatted string: "something:arg1,arg2,...,argn"
 *
 * require:
 *  number >= 1
 */
string kvu_get_argument_number(int number, const string& arg)
{
  string result;
  vector<string> temp = kvu_get_arguments(arg);
  if (static_cast<int>(temp.size()) >= number) {
    result = temp[number - 1];
  }
  return result;
}

/**
 * Converts all backslash-commas into commas and returns
 * the result.
 *
 * @pre escaped_char.size() == 1
 */
static void kvu_priv_strip_escapes(string* const input, const string& escaped_char)
{
  size_t pos; 
  while((pos = input->find(string("\\") + escaped_char)) != string::npos) {
    input->replace(pos, 2, escaped_char);
  }
}

/**
 * Strips the outer quotes of type 'quote_char' from the string.
 * The string is only modified if the the string starts with,
 * and ends to, a character matching 'quote_char'.
 *
 * @pre escaped_char.size() == 1
 */
void kvu_string_strip_outer_quotes(string* input, const std::string::value_type quote_char)
{
  if (input->size() >= 2 &&
      *input->begin() == quote_char &&
      *(input->end() - 1) == quote_char)
    *input = std::string(input->begin() + 1, input->end() - 1);
}

/** 
 * Returns number of arguments in formatted string 'arg'.
 */
int kvu_get_number_of_arguments(const string& arg)
{
  return kvu_get_arguments(arg).size();
}

/**
 * Returns a vector of all arguments from a formatted string
 *
 * Note: forces locale to "POSIX" as decimal separate must be
 *       be period (.) in order to argument separation to work
 *
 * @param argu a formatted string: "something:arg1,arg2,...,argn"
 */
vector<string> kvu_get_arguments(const string& argu)
{
  vector<string> resvec;
  char* locale_save;
  string::const_iterator b = kvu_priv_find_next_instance(argu, argu.begin(), ':');
  string::const_iterator e;

  if (b == argu.end()) {
    if (argu.size() > 0) b = argu.begin();
    else return resvec;
  }
  else 
    ++b;

#if HAVE_SETLOCALE
  locale_save = setlocale (LC_NUMERIC, "POSIX");
#else
  locale_save = NULL;
#endif

  for(; b != argu.end();) {
    e = kvu_priv_find_next_instance(argu, b, ',');
    string target = string(b, e);

    /* strip backslash-commas (and leave the commas in place) */
    kvu_priv_strip_escapes(&target, ",");
    kvu_priv_strip_escapes(&target, ":");
    kvu_string_strip_outer_quotes(&target, '"');
    resvec.push_back(target);

    if (e == argu.end()) 
      break;

    b = e;
    ++b;
    
    /* special case in which last argument is empty */
    if (b == argu.end() &&
	*e == ',')
      resvec.push_back("");
  }

#if HAVE_SETLOCALE
  /* restore original locale */
  if (locale_save)
    setlocale (LC_NUMERIC, locale_save);
#endif

  return resvec;
}

/**
 * Get the prefix part of a string argument
 * @param argument format used is -prefix:arg1, arg2, ..., argN
 *
 * require:
 *   argu.find('-') != string::npos
 *
 * ensure:
 *   result.size() >= 0
 */
string kvu_get_argument_prefix(const string& argu)
{
  // --------
  DBC_REQUIRE(argu.find('-') != string::npos);
  // --------

  string::const_iterator b = find(argu.begin(), argu.end(), '-');
  string::const_iterator e = find(argu.begin(), argu.end(), ':');

  if (b != argu.end()) {
    ++b;
    if (b !=  argu.end()) {
      return string(b,e);
    }
  }

  return "";
}

/**
 * Prints a time stamp to stderr
 */
void kvu_print_time_stamp(void)
{
  // --
  // not thread-safe!
  // --
  static bool first = true;
  static struct timeval last;
  struct timeval current;

  if (first) {
    ::gettimeofday(&last, 0);
    first = false;
  }

  ::gettimeofday(&current, 0);

  cerr << "(timestamp) " << current.tv_sec << "sec, " <<
    current.tv_usec << "msec.";
  
  long delta = current.tv_usec;
  delta -= last.tv_usec;
  delta += (current.tv_sec - last.tv_sec) * 1000000;
  cerr << " Delta " << delta << "msec." << endl;

  last.tv_sec = current.tv_sec;
  last.tv_usec = current.tv_usec;
}

/**
 * Put the calling execution context to sleeps for 
 * 'seconds.nanosecods'.
 *
 * Note! If available, implemented using nanosleep().
 *
 * @return 0 on success, non-zero if sleep was 
 *         interrupted for some reason
 */
int kvu_sleep(long int seconds, long int nanoseconds)
{
  int ret = 0;
#if defined(HAVE_NANOSLEEP) && !defined(__CYGWIN__)
  struct timespec len;
  len.tv_sec = static_cast<time_t>(seconds);
  len.tv_nsec = nanoseconds;
  ret = ::nanosleep(&len, NULL);

#elif HAVE_USLEEP
  ret = ::usleep(seconds * 1000000 + nanoseconds / 1000);

#else
  cerr << "(libkvutils) kvutils:: warning! neither nanosleep() or usleep() found!" << endl;
  return -1;
#endif

  return ret;
}
