/*
	SuperCollider real time audio synthesis system
    Copyright (c) 2002 James McCartney. All rights reserved.
	http://www.audiosynth.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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
*/
/*

Primitives for Unix.

*/

#include <cstring>
#include <errno.h>
#include <signal.h>

#include "PyrPrimitive.h"
#include "PyrObject.h"
#include "PyrKernel.h"
#include "PyrSched.h"
#include "VMGlobals.h"
#include "GC.h"
#include "SC_RGen.h"
#include "SC_Filesystem.hpp"
#include "sc_popen.h"
#include "SCBase.h"

#include "SC_Lock.h"

#include <vector>
#include <boost/filesystem.hpp>

#ifdef _WIN32
#include "SC_Win32Utils.h"
#else
#include <libgen.h>
#endif

namespace bfs = boost::filesystem;

extern bool compiledOK;
PyrSymbol* s_unixCmdAction;

int prString_System(struct VMGlobals *g, int numArgsPushed);
int prString_System(struct VMGlobals *g, int numArgsPushed)
{
	PyrSlot *a = g->sp;

	char cmdline[1024];
	int err = slotStrVal(a, cmdline, 1023);
	if (err) return err;

	int res = system(cmdline);
	SetInt(a, res);

	return errNone;
}

int prString_Basename(struct VMGlobals *g, int numArgsPushed);
int prString_Basename(struct VMGlobals *g, int numArgsPushed)
{
	PyrSlot *a = g->sp;

	char path[PATH_MAX];
	int err = slotStrVal(a, path, PATH_MAX);
	if (err) return err;

	char *basename0 = basename(path);

	int size = strlen(basename0);
	PyrString *strobj = newPyrStringN(g->gc, size, 0, true);
	memcpy(strobj->s, basename0, size);

	SetObject(a, strobj);

	return errNone;
}

int prString_Dirname(struct VMGlobals *g, int numArgsPushed);
int prString_Dirname(struct VMGlobals *g, int numArgsPushed)
{
	PyrSlot *a = g->sp;

	char path[PATH_MAX];
	int err = slotStrVal(a, path, PATH_MAX);
	if (err) return err;

	char *dirname0 = dirname(path);

	int size = strlen(dirname0);
	PyrString *strobj = newPyrStringN(g->gc, size, 0, true);
	memcpy(strobj->s, dirname0, size);

	SetObject(a, strobj);

	return errNone;
}

struct sc_process {
	pid_t pid;
	FILE *stream;
	bool postOutput;
};

static void string_popen_thread_func(struct sc_process *process)
{
	FILE *stream = process->stream;
	pid_t pid = process->pid;
	char buf[1024];

	while (process->postOutput) {
		char *string = fgets(buf, 1024, stream);
		if (!string) break;
		postText(string, strlen(string));
	}

	int res;
	res = sc_pclose(stream, pid);
	res = WEXITSTATUS(res);

	delete process;

	gLangMutex.lock();
	if(compiledOK) {
		VMGlobals *g = gMainVMGlobals;
		++g->sp;  SetObject(g->sp, class_string);
		++g->sp; SetInt(g->sp, res);
		++g->sp; SetInt(g->sp, pid);
		runInterpreter(g, s_unixCmdAction, 3);
	}
	gLangMutex.unlock();
}

int prString_POpen(struct VMGlobals *g, int numArgsPushed);
int prString_POpen(struct VMGlobals *g, int numArgsPushed)
{
	PyrSlot *a = g->sp - 1;
	PyrSlot *b = g->sp;

	if (!isKindOfSlot(a, class_string)) return errWrongType;

	char *cmdline = new char[slotRawObject(a)->size + 1];
	slotStrVal(a, cmdline, slotRawObject(a)->size + 1);

#ifdef SC_IPHONE
	SetInt(a, 0);
	return errNone;
#endif

	sc_process *process = new sc_process;
	process->stream = sc_popen(cmdline, &process->pid, "r");
	setvbuf(process->stream, 0, _IONBF, 0);
	pid_t pid = process->pid;

	process->postOutput = IsTrue(b);

	delete [] cmdline;

	if(process->stream == NULL) {
		delete process;
		return errFailed;
	}

	SC_Thread thread(std::bind(string_popen_thread_func, process));
	thread.detach();

	SetInt(a, pid);
	return errNone;
}

int prArrayPOpen(struct VMGlobals *g, int numArgsPushed);
int prArrayPOpen(struct VMGlobals *g, int numArgsPushed)
{
	PyrObject *obj;

	PyrSlot *a = g->sp - 1;
	PyrSlot *b = g->sp;

#ifdef SC_IPHONE
	SetInt(a, 0);
	return errNone;
#endif

	if (NotObj(a)) return errWrongType;

	obj = slotRawObject(a);
	if (!(slotRawInt(&obj->classptr->classFlags) & classHasIndexableInstances))
		return errNotAnIndexableObject;

	if( obj->size < 1)
		return errFailed;

	PyrSlot filenameSlot;
	getIndexedSlot(obj, &filenameSlot, 0);
	if (!isKindOfSlot(&filenameSlot, class_string)) return errWrongType;
	char filename[PATH_MAX];
	if (slotRawObject(&filenameSlot)->size > PATH_MAX - 1) return errFailed;
	slotStrVal(&filenameSlot, filename, slotRawObject(&filenameSlot)->size + 1);

	std::vector<char *> argv (obj->size + 1);

	bfs::path p;
	p /= filename;
	std::string filenameOnly = p.filename().string();
	std::vector<char> vfilenameOnly(filenameOnly.begin(), filenameOnly.end());
	vfilenameOnly.push_back('\0');

	argv[0] = vfilenameOnly.data();
	argv[obj->size] = NULL;

	if(obj->size > 1) {
		for (int i=1; i<obj->size; ++i) {
			PyrSlot argSlot;
			getIndexedSlot(obj, &argSlot, i);
			if (!isKindOfSlot(&argSlot, class_string)) return errWrongType;
			char *arg = new char[slotRawObject(&argSlot)->size + 1];
			slotStrVal(&argSlot, arg, slotRawObject(&argSlot)->size + 1);
			argv[i] = arg;
		}
	}

	sc_process *process = new sc_process;
	process->stream = sc_popen_argv(filename, argv.data(), &process->pid, "r");
	setvbuf(process->stream, 0, _IONBF, 0);
	pid_t pid = process->pid;

	process->postOutput = IsTrue(b);

	if(process->stream == NULL) {
		delete process;
		return errFailed;
	}

	SC_Thread thread(std::bind(string_popen_thread_func, process));
	thread.detach();

	for (int i=1; i<obj->size; ++i) {
		delete [] argv[i];
	}

	SetInt(a, pid);
	return errNone;

}

int prPidRunning(VMGlobals *g, int numArgsPushed);
int prPidRunning(VMGlobals *g, int numArgsPushed)
{
	PyrSlot *a;

	a = g->sp;

#ifdef _WIN32
	HANDLE handle;

	handle = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, slotRawInt(a));
	if(handle) {
		unsigned long exitCode;

		if(GetExitCodeProcess(handle, &exitCode) == 0)
			SetFalse(a);
		else if(exitCode == STILL_ACTIVE)
			SetTrue(a);

		CloseHandle(handle);
	}
	else
		SetFalse(a);
#else
	if(kill(slotRawInt(a), 0) == 0)
		SetTrue(a);
	else
		SetFalse(a);
#endif

	return errNone;
}

int prUnix_Errno(struct VMGlobals *g, int numArgsPushed);
int prUnix_Errno(struct VMGlobals *g, int numArgsPushed)
{
	PyrSlot *a = g->sp;

	SetInt(a, errno);

	return errNone;
}

#include <time.h>

static void fillSlotsFromTime(PyrSlot * result, struct tm* tm, std::chrono::system_clock::time_point const & now)
{
	PyrSlot *slots = slotRawObject(result)->slots;

	SetInt(slots+0, tm->tm_year + 1900);
	SetInt(slots+1, tm->tm_mon + 1); // 0 based month ??
	SetInt(slots+2, tm->tm_mday);
	SetInt(slots+3, tm->tm_hour);
	SetInt(slots+4, tm->tm_min);
	SetInt(slots+5, tm->tm_sec);
	SetInt(slots+6, tm->tm_wday);
	SetFloat(slots+7, std::chrono::duration_cast<std::chrono::nanoseconds>(now.time_since_epoch()).count() * 1.0e-9);
}

int prLocalTime(struct VMGlobals *g, int numArgsPushed)
{
	using namespace std::chrono;
	system_clock::time_point now = system_clock::now();
	time_t now_time_t = system_clock::to_time_t(now);
	struct tm* tm = localtime(&now_time_t);

	fillSlotsFromTime(g->sp, tm,  now);

	return errNone;
}

int prGMTime(struct VMGlobals *g, int numArgsPushed)
{
	using namespace std::chrono;
	system_clock::time_point now = system_clock::now();
	time_t now_time_t = system_clock::to_time_t(now);
	struct tm* tm = gmtime(&now_time_t);

	fillSlotsFromTime(g->sp, tm, now);
	return errNone;
}

int prAscTime(struct VMGlobals *g, int numArgsPushed);
int prAscTime(struct VMGlobals *g, int numArgsPushed)
{
	PyrSlot *a = g->sp;
	PyrSlot *slots = slotRawObject(a)->slots;

	if (IsNil(slots + 0)) {
		SetNil(a);
		return errNone;
	}

	struct tm tm0;

	if (slotIntVal(slots+0, &tm0.tm_year)) return errWrongType;
	tm0.tm_year -= 1900;
	if (slotIntVal(slots+1, &tm0.tm_mon)) return errWrongType;
	tm0.tm_mon -- ;
	if (slotIntVal(slots+2, &tm0.tm_mday)) return errWrongType;
	if (slotIntVal(slots+3, &tm0.tm_hour)) return errWrongType;
	if (slotIntVal(slots+4, &tm0.tm_min)) return errWrongType;
	if (slotIntVal(slots+5, &tm0.tm_sec)) return errWrongType;
	if (slotIntVal(slots+6, &tm0.tm_wday)) return errWrongType;

	const char *text = asctime(&tm0);

	int size = strlen(text) - 1; // Discard trailing newline
	PyrString *strobj = newPyrStringN(g->gc, size, 0, true);
	memcpy(strobj->s, text, size);

	SetObject(a, strobj);

	return errNone;
}

int prStrFTime(struct VMGlobals *g, int numArgsPushed);
int prStrFTime(struct VMGlobals *g, int numArgsPushed)
{
	PyrSlot *a = g->sp - 1;
	PyrSlot *b = g->sp;

	PyrSlot *slots = slotRawObject(a)->slots;

	if (IsNil(slots + 0)) {
		SetNil(a);
		return errNone;
	}

	struct tm tm0;

	if (slotIntVal(slots+0, &tm0.tm_year)) return errWrongType;
	tm0.tm_year -= 1900;
	if (slotIntVal(slots+1, &tm0.tm_mon)) return errWrongType;
	tm0.tm_mon --;
	if (slotIntVal(slots+2, &tm0.tm_mday)) return errWrongType;
	if (slotIntVal(slots+3, &tm0.tm_hour)) return errWrongType;
	if (slotIntVal(slots+4, &tm0.tm_min)) return errWrongType;
	if (slotIntVal(slots+5, &tm0.tm_sec)) return errWrongType;
	if (slotIntVal(slots+6, &tm0.tm_wday)) return errWrongType;

	char format[1024];
	if (slotStrVal(b, format, 1024)) return errWrongType;

	char buffer[1024];
	if (strftime(buffer, 1024, format, &tm0) != 0) {
		int size = strlen(buffer);
		PyrString *strobj = newPyrStringN(g->gc, size, 0, true);
		memcpy(strobj->s, buffer, size);

		SetObject(a, strobj);
	} else {
		error("could not convert the date to string with the give format");
		return errFailed;
	}
	return errNone;
}

int32 timeseed();

int prTimeSeed(struct VMGlobals *g, int numArgsPushed);
int prTimeSeed(struct VMGlobals *g, int numArgsPushed)
{
	PyrSlot *a = g->sp;
	SetInt(a, timeseed());
	return errNone;
}

int prGetPid(VMGlobals *g, int numArgsPushed);
int prGetPid(VMGlobals *g, int numArgsPushed)
{
	PyrSlot *a = g->sp;
	SetInt(a,
#ifndef _WIN32
		getpid()
#else
		GetCurrentProcessId()
#endif
		);
	return errNone;
}


void initUnixPrimitives();
void initUnixPrimitives()
{
	int base, index = 0;

	base = nextPrimitiveIndex();

	s_unixCmdAction = getsym("doUnixCmdAction");

	definePrimitive(base, index++, "_String_System", prString_System, 1, 0);
	definePrimitive(base, index++, "_String_Basename", prString_Basename, 1, 0);
	definePrimitive(base, index++, "_String_Dirname", prString_Dirname, 1, 0);
	definePrimitive(base, index++, "_String_POpen", prString_POpen, 2, 0);
	definePrimitive(base, index++, "_Unix_Errno", prUnix_Errno, 1, 0);
	definePrimitive(base, index++, "_LocalTime", prLocalTime, 1, 0);
	definePrimitive(base, index++, "_GMTime", prGMTime, 1, 0);
	definePrimitive(base, index++, "_AscTime", prAscTime, 1, 0);
	definePrimitive(base, index++, "_prStrFTime", prStrFTime, 2, 0);
	definePrimitive(base, index++, "_TimeSeed", prTimeSeed, 1, 0);
	definePrimitive(base, index++, "_PidRunning", prPidRunning, 1, 0);
	definePrimitive(base, index++, "_GetPid", prGetPid, 1, 0);
	definePrimitive(base, index++, "_ArrayPOpen", prArrayPOpen, 2, 0);
}
