/*
	PIP - Platform Independent Primitives
	Process
	Ivan Pelipenko peri4ko@yandex.ru

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU Lesser General Public License as published by
	the Free Software Foundation, either version 3 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 Lesser General Public License for more details.

	You should have received a copy of the GNU Lesser General Public License
	along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef MICRO_PIP

#include "piincludes_p.h"
#include "piprocess.h"
#ifndef WINDOWS
#  include <sys/wait.h>
#  include <csignal>
#endif
#ifdef MAC_OS
#  include <crt_externs.h>
#endif


//! \class PIProcess piprocess.h
//! \~\details
//! \~english
//! This class able to start external executables, watch for them,
//! grab output and exit code.
//!
//! \~russian
//!
//!
//! \~english \section PIProcess_sec0 Synopsis
//! \~russian \section PIProcess_sec0 Краткий обзор
//! \~english
//! External executable can be started with control or fully independent
//! from application.\n
//! Start with control allow you to wait for finish, grab output
//! and terminate it at any time.\n
//! You can change working directory and environment of executable.\n
//!
//! \~russian
//!
//!
//! \~english \section PIProcess_sec1 Usage
//! \~russian \section PIProcess_sec1 Использование
//! \~english
//!
//!
//! \~russian
//!


PRIVATE_DEFINITION_START(PIProcess)
#ifdef WINDOWS
	STARTUPINFOA si;
	PROCESS_INFORMATION pi;
#else
	pid_t pid;
#endif
	FILE * tf_in, * tf_out, * tf_err;
PRIVATE_DEFINITION_END(PIProcess)


PIProcess::PIProcess(): PIThread() {
	exit_code = -1;
#ifdef WINDOWS
	PRIVATE->pi.dwProcessId = 0;
#else
	PRIVATE->pid = 0;
#endif
	is_exec = false;
	g_in = g_out = g_err = false;
	t_in = t_out = t_err = false;
	PRIVATE->tf_in = PRIVATE->tf_out = PRIVATE->tf_err = 0;
	env = PIProcess::currentEnvironment();
}


PIProcess::~PIProcess() {
	if (t_in)  f_in.remove();
	if (t_out) f_out.remove();
	if (t_err) f_err.remove();
}


void PIProcess::exec_() {
	is_exec = false;
	startOnce();
	//cout << "exec wait" << endl;
	while (!is_exec)
		piMinSleep();
	//cout << "exec end" << endl;
}


void PIProcess::startProc(bool detached) {
	//cout << "run" << endl;
	PIString str;
	/// arguments convertion
	int as = 0;
	const char * argscc[args.size()+1];
	int argsl[args.size()];
	for (int i = 0; i < args.size_s(); ++i) {
		argscc[i] = args[i].data();
		argsl[i] = strlen(argscc[i]);
		as += argsl[i] + 3;
	}
	argscc[args.size()] = 0;
#ifdef WINDOWS
	char * a = new char[as];
	memset(a, ' ', as - 1);
	as = 0;
	for (int i = 0; i < args.size_s(); ++i) {
		str = args[i];
		a[as] = '"';
		memcpy(&(a[as + 1]), argscc[i], argsl[i]);
		a[as + argsl[i] + 1] = '"';
		as += argsl[i] + 3;
	}
	a[as - 1] = 0;
	//piCout << a;
#endif
#ifndef WINDOWS
	/// environment convertion
	const char * envcc[env.size()+1];
	envcc[env.size_s()] = 0;
	for (int i = 0; i < env.size_s(); ++i) {
		envcc[i] = env[i].data();
	}
#endif
	/// files for stdin/out/err
	t_in = t_out = t_err = false;
	if (f_in.path().isEmpty()) {
		f_in.openTemporary(PIIODevice::ReadWrite);
		t_in = true;
	}
	if (f_out.path().isEmpty()) {
		f_out.openTemporary(PIIODevice::ReadWrite);
		t_out = true;
	}
	if (f_err.path().isEmpty()) {
		f_err.openTemporary(PIIODevice::ReadWrite);
		t_err = true;
	}

	str = args.front();
	is_exec = true;
	if (!detached) execStarted(str);
#ifndef WINDOWS
	int pid_ = fork();
	if (!detached) PRIVATE->pid = pid_;
	if (pid_ == 0) {
#endif
		PRIVATE->tf_in = PRIVATE->tf_out = PRIVATE->tf_err = 0;
		//cout << "exec " << tf_in << ", " << tf_out << ", " << tf_err << endl;
		//cout << f_out.path() << endl;
		if (g_in)  PRIVATE->tf_in = freopen(f_in.path().data(), "r", stdin);
		if (g_out) PRIVATE->tf_out = freopen(f_out.path().data(), "w", stdout);
		if (g_err) PRIVATE->tf_err = freopen(f_err.path().data(), "w", stderr);
#ifndef WINDOWS
		if (!wd.isEmpty())
			if (!chdir(wd.data()))
				piCoutObj << "Error while set working directory";
#endif
#ifdef WINDOWS
	GetStartupInfoA(&(PRIVATE->si));
	memset(&(PRIVATE->pi), 0, sizeof(PRIVATE->pi));
	if(CreateProcessA(0,   // No module name (use command line)
		a,         // Command line
		0,         // Process handle not inheritable
		0,         // Thread handle not inheritable
		false,     // Set handle inheritance to FALSE
		detached ? DETACHED_PROCESS/*CREATE_NEW_CONSOLE*/ : 0, // Creation flags
		0,//envcc,         // Use environment
		wd.isEmpty() ? 0 : wd.data(), // Use working directory
		&(PRIVATE->si),       // Pointer to STARTUPINFO structure
		&(PRIVATE->pi)))      // Pointer to PROCESS_INFORMATION structure
	{
		if (!detached) WaitForSingleObject(PRIVATE->pi.hProcess, INFINITE);
		CloseHandle(PRIVATE->pi.hThread);
		CloseHandle(PRIVATE->pi.hProcess);
	} else
		piCoutObj << "\"CreateProcess\" error, " << errorString();
#else

		//cout << "exec " << tf_in << ", " << tf_out << ", " << tf_err << endl;
		if (execve(str.data(), (char * const *)argscc, (char * const *)envcc) < 0)
			piCoutObj << "\"execve" << str << args << "\" error :" << errorString();
	} else {
		piMinSleep();
		//cout << "wait" << endl;
		if (!detached) {
			wait(&exit_code);
			pid_ = 0;
			if (!detached) PRIVATE->pid = pid_;
			//cout << "wait done" << endl;
		}
	}
#endif
	if (!detached) execFinished(str, exit_code);
	is_exec = false;
#ifdef WINDOWS
	delete[] a;
#endif
}


void PIProcess::terminate() {
#ifdef WINDOWS
	if (is_exec)
		if (!TerminateProcess(PRIVATE->pi.hProcess, 0))
			return;
	PRIVATE->pi.dwProcessId = 0;
#else
	if (is_exec)
		kill(PRIVATE->pid, SIGKILL);
	PRIVATE->pid = 0;
#endif
}


void PIProcess::execIndependent(const PIString & program, const PIStringList & args_) {
	PIProcess p;
	p.args << program << args_;
	p.startProc(true);
}


int PIProcess::pID() const {
#ifdef WINDOWS
	return PRIVATE->pi.dwProcessId;
#else
	return PRIVATE->pid;
#endif
}


int PIProcess::currentPID() {
#ifdef WINDOWS
	return GetCurrentProcessId();
#else
	return getpid();
#endif
}


PIStringList PIProcess::currentEnvironment() {
	PIStringList l;
	int i = 0;
	while (environ[i] != 0) {
		l << environ[i];
		++i;
	}
	return l;
}


void PIProcess::run() {
	startProc(false);
}


void PIProcess::removeEnvironmentVariable(const PIString & variable) {
	PIString s;
	for (int i = 0; i < env.size_s(); ++i) {
		s = env[i];
		if (s.left(s.find("=")).trimmed() == variable) {
			env.remove(i);
			--i;
		}
	}
}


void PIProcess::setEnvironmentVariable(const PIString & variable, const PIString & value) {
	PIString s, v;
	for (int i = 0; i < env.size_s(); ++i) {
		s = env[i];
		v = s.left(s.find("=")).trimmed();
		if (v == variable) {
			env[i] = v + "=" + value;
			return;
		}
	}
	env << variable + "=" + value;
}


PIString PIProcess::getEnvironmentVariable(const PIString & variable) {
	PIStringList env_ = currentEnvironment();
	PIString s, v;
	for (int i = 0; i < env_.size_s(); ++i) {
		s = env_[i];
		v = s.left(s.find("=")).trimmed();
		if (v == variable) {
			return s.right(s.size() - 1 - s.find("=")).trimmed();
		}
	}
	return PIString();
}

#endif // MICRO_PIP

