/*
    PIP - Platform Independent Primitives
    Process
    Copyright (C) 2016  Ivan Pelipenko peri4ko@gmail.com

    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 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 General Public License for more details.

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

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


PIProcess::PIProcess(): PIThread() {
	exit_code = -1;
#ifdef WINDOWS
	pi.dwProcessId = 0;
#else
	pid = 0;
#endif
	is_exec = false;
	g_in = g_out = g_err = false;
	t_in = t_out = t_err = false;
	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)
		msleep(1);
	//cout << "exec end" << endl;
}


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


int PIProcess::pID() const {
#ifdef WINDOWS
	return pi.dwProcessId;
#else
	return 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() {
	//cout << "run" << endl;
	PIString str;
	/// arguments convertion
	as = 0;
#ifdef WINDOWS
	//args.pop_front();
	piForeachC (PIString & i, args)
		as += i.lengthAscii() + 1;
	char * a = new char[as];
	memset(a, ' ', as - 1);
	as = 0;
	for (int i = 0; i < args.size_s(); ++i) {
		str = args[i];
		memcpy(&a[as], str.data(), str.lengthAscii());
		as += str.length() + 1;
	}
	a[as - 1] = 0;
#else
	char * a[args.size_s() + 1];
	for (int i = 0; i < args.size_s(); ++i) {
		str = args[i];
		a[i] = new char[str.size() + 1];
		memcpy(a[i], str.data(), str.lengthAscii());
		a[i][str.size()] = 0;
		//cout << a[i] << endl;
	}
	a[args.size_s()] = 0;
#endif
	/// environment convertion
	char ** e = new char*[env.size_s() + 1];
	for (int i = 0; i < env.size_s(); ++i) {
		str = env[i];
		e[i] = new char[str.size() + 1];
		memcpy(e[i], str.data(), str.lengthAscii());
		e[i][str.size()] = 0;
		//cout << e[i] << endl;
	}
	e[env.size_s()] = 0;
	/// files for stdin/out/err
	t_in = t_out = t_err = false;
	if (f_in.path().isEmpty()) {
		f_in = PIFile::openTemporary(PIIODevice::ReadWrite);
		t_in = true;
	}
	//f_in.open(PIIODevice::ReadWrite); f_in.close();
	if (f_out.path().isEmpty()) {
		f_out = PIFile::openTemporary(PIIODevice::ReadWrite);
		t_out = true;
	}
	//f_out.open(PIIODevice::WriteOnly); f_out.close();
	if (f_err.path().isEmpty()) {
		f_err = PIFile::openTemporary(PIIODevice::ReadWrite);
		t_err = true;
	}
	//f_err.open(PIIODevice::WriteOnly); f_err.close();

	str = args.front();
	is_exec = true;
	execStarted(str);
#ifndef WINDOWS
	pid = fork();
	if (pid == 0) {
#endif
		tf_in = tf_out = tf_err = 0;
		//cout << "exec " << tf_in << ", " << tf_out << ", " << tf_err << endl;
		//cout << f_out.path() << endl;
		if (g_in)  tf_in = freopen(f_in.path().data(), "r", stdin);
		if (g_out) tf_out = freopen(f_out.path().data(), "w", stdout);
		if (g_err) tf_err = freopen(f_err.path().data(), "w", stderr);
#ifndef WINDOWS
		if (!wd.isEmpty()) as = chdir(wd.data());
#endif
#ifdef WINDOWS
	GetStartupInfoA(&si);
	memset(&pi, 0, sizeof(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
		0,         // No creation flags
		0,//e,         // Use environment
		wd.isEmpty() ? 0 : wd.data(), // Use working directory
		&si,       // Pointer to STARTUPINFO structure
		&pi))      // Pointer to PROCESS_INFORMATION structure
	{
		WaitForSingleObject(pi.hProcess, INFINITE);
		CloseHandle(pi.hProcess);
	} else
		piCoutObj << "\"CreateProcess\" error, " << errorString();
#else

		//cout << "exec " << tf_in << ", " << tf_out << ", " << tf_err << endl;
		if (execve(str.data(), a, e) < 0)
			piCoutObj << "\"execve\" error, " << errorString();
	} else {
		msleep(1);
		//cout << "wait" << endl;
		wait(&exit_code);
		/*if (tf_in != 0) fclose(tf_in);
		if (tf_out != 0) fclose(tf_out);
		if (tf_err != 0) fclose(tf_err);*/
		pid = 0;
		//cout << "wait done" << endl;
	}
#endif
	execFinished(str, exit_code);
	is_exec = false;
	for (int i = 0; i < env.size_s(); ++i)
		delete e[i];
	delete e;
#ifdef WINDOWS
	delete a;
#else
	for (int i = 0; i < args.size_s(); ++i)
		delete a[i];
#endif
	//cout << "end " << tf_in << ", " << tf_out << ", " << tf_err << endl;
}


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;
}
