#ifndef __JSYSTEMTOOLKIT__
#define __JSYSTEMTOOLKIT__

#include <iostream>
#include <string>
#include <vector>
#include <unistd.h>

#include "JLang/JException.hh"
#include "JSystem/JShell.hh"
#include "JSystem/JSysinfo.hh"


/**
 * \file
 * System auxiliaries.
 * \author mdejong
 */

/**
 * Auxiliary classes and methods for operating system calls.
 */
namespace JSYSTEM {}
namespace JPP { using namespace JSYSTEM; }

namespace JSYSTEM {

  using JLANG::JSystemException;

  /**
   * Shell names.
   */
  static const char* const  SH    =  "sh";
  static const char* const  ZSH   =  "zsh";
  static const char* const  KSH   =  "ksh";
  static const char* const  BASH  =  "bash";
  static const char* const  CSH   =  "csh";
  static const char* const  TCSH  =  "tcsh";

  static const std::string  SPECIAL_CHARACTERS = "\"' <>[](){};$#";

  
  
  /**
   * Get memory usage in percent of given process identifier.
   *
   * \param  shell              shell interface
   * \param  pid                process identifier
   * \return                    memory usage [%]
   */
  inline float getMemoryUsage(JShell& shell, const pid_t pid)
  {
    using namespace std;

    float value = 0.0;

    shell << "ps -o %mem= -p " << pid << endl;

    if (shell.get(value))
      return value;
    else
      THROW(JSystemException, "No process data for PID " << pid);
  }


  /**
   * Get memory usage in percent of this process.
   *
   * \param  shell              shell interface
   * \return                    memory usage [%]
   */
  inline float getMemoryUsage(JShell& shell)
  {
    return getMemoryUsage(shell, getpid());
  }


  /**
   * Get memory usage in percent of this process.
   *
   * \return                    memory usage [%]
   */
  inline float getMemoryUsage()
  {
    return getMemoryUsage(JShell::getInstance(), getpid());
  }
  
  
  /**
   * Get cpu usage in percent of given process identifier.
   *
   * \param  shell              shell interface
   * \param  pid                process identifier
   * \return                    cpu usage [%]
   */
  inline float getCpuUsage(JShell& shell, const pid_t pid)
  {
    using namespace std;

    float value = 0.0;

    shell << "ps -o %cpu= -p " << pid << endl;

    if (shell.get(value))
      return value;
    else
      THROW(JSystemException, "No process data for PID " << pid);
  }


  /**
   * Get cpu usage in percent of this process.
   *
   * \param  shell              shell interface
   * \return                    cpu usage [%]
   */
  inline float getCpuUsage(JShell& shell)
  {
    return getCpuUsage(shell, getpid());
  }


  /**
   * Get cpu usage in percent of this process.
   *
   * \return                    cpu usage [%]
   */
  inline float getCpuUsage()
  {
    return getCpuUsage(JShell::getInstance(), getpid());
  }


  /**
   * Get process identifier.
   *
   * \param  shell              shell interface
   * \param  process            process name
   * \return                    process identifier
   */
  inline pid_t getPID(JShell& shell, const char* process)
  {
    using namespace std;

    pid_t pid = -1;

    shell << "ps -o pid= -C " << process << endl;

    if (shell.get(pid))
      return pid;
    else
      THROW(JSystemException, "No process " << process);
  }


  /**
   * Get process identifier.
   *
   * \param  process            process name
   * \return                    process identifier
   */
  inline pid_t getPID(const char* process)
  {
    return getPID(JShell::getInstance(), process);
  }


  /**
   * Get parent process identifier.
   *
   * \param  shell              shell interface
   * \param  pid                process identifier
   * \return                    parent  identifier
   */
  inline pid_t getParentID(JShell& shell, pid_t pid)
  {
    using namespace std;

    shell << "ps -o ppid= -p " << pid << endl;

    if (shell.get(pid))
      return pid;
    else
      THROW(JSystemException, "No parent identifier " << pid);
  }


  /**
   * Get parent process identifier.
   *
   * \param  pid                process identifier
   * \return                    parent  identifier
   */
  inline pid_t getParentID(const pid_t pid)
  {
    return getParentID(JShell::getInstance(), getpid());
  }


  /**
   * Get shell name.
   *
   * \param  shell              shell interface
   * \return                    shell name
   */
  inline std::string getShell(JShell& shell)
  {
    using namespace std;

    static string value = "";

    if (value == "") {

      pid_t pid = getppid();
	
      shell << "ps -o ppid= -o args= -p " << pid << endl;

      if (shell >> pid >> value) {
      
	shell.flush();
      
	if (!value.empty() && value[0] == '-') {
	  value = value.substr(1);
	}

      } else {

	static_cast<istream&>(shell).clear();
	shell.flush();
	
	THROW(JSystemException, "No shell");
      }
    }

    return value;
  }


  /**
   * Get shell name of this process.
   *
   * \return                    shell name
   */
  inline std::string getShell()
  {
    return getShell(JShell::getInstance());
  }

  
  /**
   * Get RAM of this CPU.
   *
   * \return                    number of bytes
   */
  inline unsigned long long int getRAM()
  {
    const JSysinfo info;

    return info.getTotalRAM();
  }


  /**
   * Get process path.
   *
   * \param  shell              shell interface
   * \param  process            process name
   * \return                    path
   */
  inline const std::string which(JShell& shell, const char* process)
  {
    using namespace std;

    string buffer;
    
    shell << "which " << process << endl;

    shell.getline(buffer);

    shell.flush();

    return buffer;
  }


  /**
   * Get process path.
   *
   * \param  process            process name
   * \return                    path
   */
  inline const std::string which(const char* process)
  {
    return which(JShell::getInstance(), process);
  }

  
  /**
   * Print method.
   *
   * \param  message            message
   */
  inline void gprint(const std::string& message)
  {
    using namespace std;

    istringstream is(message);

    for (string buffer; getline(is, buffer); ) {

      if (!buffer.empty()) {

	for (string::size_type i = 0; i != buffer.size(); ++i) {
	  if (SPECIAL_CHARACTERS.find(buffer[i]) != string::npos) {
	    buffer.insert(i++, "\\");
	  }
	}

	cout << "echo " << buffer << ";" << endl;
      }
    }
  }

  
  /**
   * Exit method.
   *
   * \param  status             exit status
   * \param  message            optional message
   */
  inline int gexit(int status, const std::string& message = "")
  {
    using namespace std;

    gprint(message);
    
    cout << "exit " << status << ";" << endl;
    
    return status;
  }


  /**
   * Set environment variable.
   *
   * This method prints the shell command to set the variable with the given name to the specified value.
   *
   * \param  name                 variable name
   * \param  value                variable value
   */
  inline void set_variable(const std::string& name, const std::string& value)
  {
    using namespace std;
    using namespace JSYSTEM;
    
    static const string shell = getShell();

    string buffer(value);
    
    if (buffer.find(' ') != string::npos) {
      buffer = "\"" + buffer + "\"";
    }
    
    if        (shell.find(ZSH)  != string::npos ||
	       shell.find(KSH)  != string::npos ||
	       shell.find(BASH) != string::npos) {
      
      cout << "export " << name << "=" << buffer << ";" << endl;
    
    } else if (shell.find(CSH)  != string::npos ||
	       shell.find(TCSH) != string::npos) {
      
      cout << "setenv " << name << " " << buffer << ";" << endl;

    } else {

      THROW(JSystemException, "unknown shell " << shell);
    }
  }
};

#endif