/*
 *  ADP (Another Data Processor) www.adp.la
 *  Copyright (C) 2010 Katsuhisa Ohfuji <katsuhisa@ohfuji.name>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
 *
 *  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.
 */

#ifndef ADP_BUILTIN_SYSTEM_H
#define ADP_BUILTIN_SYSTEM_H

#ifdef _WIN32
#include <direct.h>
#include <process.h>
#else
#include <sys/stat.h>
#include <sys/wait.h>
#endif

struct ExecContext_Now : public ExecContextRoot {
	ExecContext_Now(ExecContext *p_,  void *f_, const PPredicate *pred_, VLocal *l) : ExecContextRoot(p_, f_, pred_, l) {}
	virtual bool first(PException &excp) { 
		if ( pred->size() != 1 && pred->size() != 2 ) return RERR_argment_number(excp, 1);
		const PObject *out = (*pred)[pred->size()-1];
		time_t	now_t;
		time(&now_t);
		if ( pred->size() == 2 ) {
			const PINTEGER *dif = (*pred)[0]->c_integer(this);
			if ( !dif ) return RERR_argment_type(excp, 0);
			now_t += *dif;
		}
		char	buf[64]; // ctimeが返す文字列は26文字
		strcpy( buf, ctime(&now_t));
		buf[strlen(buf) - 1] = '\0'; // \nを取る
		const PString  *result = pmm.newPString( pobjs, buf);
		return out->unify(*result, this);
	}
};


struct ExecContext_Spawnp : public ExecContextRoot {
	ExecContext_Spawnp(ExecContext *p_,  void *f_, const PPredicate *pred_, VLocal *l) : ExecContextRoot(p_, f_, pred_, l) {}
	virtual bool first(PException &excp) { 
		try {
			size_t	siz = pred->size();
			if ( siz < 2 ) return RERR_argment_number(excp, 2);
			const PString	*cmd = get<PString>(0);
			if ( !cmd->c_str() ) return RERR_argment_type(excp, 0);

			vector<char const *> argv;
			argv.push_back(cmd->c_str());
			vector<string>		argsvector;
			string arg;
			for ( size_t vidx = 1; vidx < siz - 1; vidx++ ) {
				arg.clear();
				const PObject	*argobj = get<PString>(vidx);
				if ( !argobj || !argobj->cnv_string(arg) ) return RERR_argment_type(excp, vidx);
				argsvector.push_back(arg);
				argv.push_back(argsvector.back().c_str());
			}
			argv.push_back(0);
#ifdef _WIN32
			intptr_t	ret = _spawnvp( _P_WAIT, cmd->c_str(), &argv[0]);
#else
			int ret = -1;	// vforkエラーは-1
			pid_t cpid = fork();
			if ( cpid == 0 ) {
				if ( execvp( cmd->c_str(), (char**)&argv[0]) == -1 ) {
					exit(-1);
				}
			} else if ( cpid != -1 ) {
				if ( wait(&ret) == -1 ) {
					ret = -1;	// waitエラーも-1（本当は例外が良いかも）
				}
			} 
#endif
			if ( ret == -1 ) return false;
			const PInteger *result = pmm.newPInteger( pobjs, ret);
			const PObject *out = (*pred)[siz-1];
			return out->unify(*result, this);
		} catch (exception &) {
			return false;
		}
		return true;
	}
};

struct ExecContext_Env : public ExecContextBase {
	ExecContext_Env(ExecContext *p_,  void *f_, const PPredicate *pred_, VLocal *l) : ExecContextBase(p_, f, pred_,l) {}

	ssmap::iterator	i;
	ssmap::iterator iend;
	const PObject *okey;

	virtual bool first(PException &excp) { 
		if ( pred->size() != 2 ) return RERR_argment_number(excp, 2);
		okey = get<PObject>(0);

		string key;
		if ( okey->cnv_string(key) ) {
			i = envmap.find(key);
			if ( i == envmap.end() ) return false;
			iend = i;
			iend++;
		} else if ( okey->isveriable() ) {
			i = envmap.begin();
			iend = envmap.end();
		}
		return backtrack(excp);
	}

	virtual bool backtrack(PException &excp) { 
		if ( i != iend ) {
			const PString *result_2 = pmm.newPString( pobjs, i->second);
			if ( !unify<PString>( 1, result_2, excp) ) return false;
			if ( okey->isveriable() ) {
				const PString *result_1 = pmm.newPString( pobjs, i->first);
				if ( !unify<PString>( 0, result_1, excp) ) return false;
			}
		} else {
			return false;
		}
		i++;
		return true;
	}

};

struct ExecContext_Arg : public ExecContextBase {
	PINTEGER	pos;
	PINTEGER	epos;
	const PObject *okey;

	ExecContext_Arg(ExecContext *p_,  void *f_, const PPredicate *pred_, VLocal *l) : ExecContextBase(p_, f_, pred_, l) {}

	virtual bool first(PException &excp) { 
		if ( pred->size() != 2 ) return RERR_argment_number(excp, 2);
		okey = get<PObject>(0);

		if ( okey->cnv_integer(pos) ) {
			if ( pos >= argvector.size() ) return false;
			epos = pos + 1;
		} else if ( okey->isveriable() ) {
			pos = 0;
			epos = argvector.size();
		}
		return backtrack(excp);
	}

	virtual bool backtrack(PException &excp) { 
		if ( pos < epos ) {
			const PString *p = pmm.newPString( pobjs, argvector[static_cast<size_t>(pos)]);
			if ( !unify<PString>( 1, p, excp) ) return false;
			if ( okey->isveriable() ) {
				const PInteger *p = pmm.newPInteger( pobjs, pos);
				if ( !unify<PInteger>( 0, p, excp) ) return false;
			}
		} else {
			return false;
		}
		pos++;
		return true;
	}

};

struct ExecContext_Sleep : public ExecContextRoot {
	ExecContext_Sleep(ExecContext *p_,  void *f_, const PPredicate *pred_, VLocal *l) : ExecContextRoot(p_, f_, pred_, l) {}
	virtual bool first(PException &excp) { 
		if ( pred->size() != 1 ) return RERR_argment_number(excp, 1);
		const PObject *item = get<PObject>(0);

		PINTEGER msec;
		if ( !item->cnv_integer(msec) )  return RERR_argment_type(excp, 0);

		mySleep((long long)msec*1000*1000);

		return true;
	}
};

struct ExecContext_Exit : public ExecContextRoot {
	ExecContext_Exit(ExecContext *p_,  void *f_, const PPredicate *pred_, VLocal *l) : ExecContextRoot(p_, f_, pred_, l) {}
	virtual bool first(PException &excp) { 
		if ( pred->size() != 1 ) return RERR_argment_number(excp, 1);
		const PObject *item = get<PObject>(0);

		PINTEGER exit_code;
		if ( !item->cnv_integer(exit_code) )  return RERR_argment_type(excp, 0);

		exit(static_cast<int>(exit_code));

		return false;
	}
};

struct ExecContext_Transfer : public ExecContextRoot {
	ExecContext_Transfer(ExecContext *p_,  void *f_, const PPredicate *pred_, VLocal *l) : ExecContextRoot(p_, f_, pred_, l) {}
	virtual bool first(PException &excp) { 
		if ( pred->size() != 1 ) return RERR_argment_number(excp, 1);
		const PObject *item = get<PObject>(0);

		string path;
		if ( !item->cnv_string(path) )  return RERR_argment_type(excp, 0);

		GlobalContext	c;
		if ( !c.compile( path, true) ) return RERR_Compile( excp);
		
		gc->hbase.addPHornBase( c.hbase );
		gc->topgoals.erase( gc->goalidx );
		gc->topgoals.addPHorns( c.topgoals );

		return true;
	}
};

struct ExecContext_Platform : public ExecContextRoot {
	ExecContext_Platform(ExecContext *p_,  void *f_, const PPredicate *pred_, VLocal *l) : ExecContextRoot(p_, f_, pred_, l) {}
	virtual bool first(PException &excp) { 
		if ( pred->size() != 1 ) return RERR_argment_number(excp, 1);
#if _WIN32
	string result_ = "Windows";
#else 
 #if unix
	string result_ = "Unix";
 #else
	string result_ = "Other";
 #endif
#endif
		const PString  *result = pmm.newPString(pobjs,result_);
		return (*pred)[0]->unify( *result, this);
	}
};


struct ExecContext_Pathsep : public ExecContextRoot {
	ExecContext_Pathsep(ExecContext *p_,  void *f_, const PPredicate *pred_, VLocal *l) : ExecContextRoot(p_, f_, pred_, l) {}
	virtual bool first(PException &excp) { 
		if ( pred->size() != 1 ) return RERR_argment_number(excp, 1);
		string result_;
		result_.push_back(pseparator);
		const PString  *result = pmm.newPString(pobjs,result_);
		return (*pred)[0]->unify( *result, this);
	}
};

#endif
