/*
 *  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;
		}
		PString  *result = pmm.newPString( pobjs);
		result->value =  ctime(&now_t); // ctimeが返す文字列は26文字 
		result->value.resize( result->value.size() - 1);
		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) { 
		if ( get_gc()->issandbox() ) return RERR_Sandbox(excp);
		try {
			size_t	siz = pred->size();
			if ( siz < 2 ) return RERR_argment_number(excp, 2);
			const PString	*cmd = get<PString>(0);
			if ( cmd == 0 || !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]);
			if ( ret == -1 ) return RERR(excp," spanwnvp ");
#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 ) return RERR(excp," wait ");
			} 
			if ( ret == -1 ) return RERR(excp," fork/exec ");
#endif
			const PInteger *result = pmm.newPInteger( pobjs, ret);
			const PObject *out = (*pred)[siz-1];
			return out->unify(*result, this);
		} catch (exception &) {
			return RERR(excp," cause exception ");
		}
		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 ( get_gc()->issandbox() ) return RERR_Sandbox(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 ( get_gc()->issandbox() ) return RERR_Sandbox(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 ( get_gc()->issandbox() ) return RERR_Sandbox(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 ( get_gc()->issandbox() ) return RERR_Sandbox(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 ( get_gc()->issandbox() ) return RERR_Sandbox(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 RERR(excp," exit ");
	}
};

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 ( get_gc()->issandbox() ) return RERR_Sandbox(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;
		c.sbuffing();	// 標準出力と標準エラーをバッファリングする 

		bool awp = isawp(path.c_str());
		if ( !c.compile( path, awp) ) return RERR_Compile( excp, c.serr);
		
		get_gc()->hbase.addPHornBase( c.hbase );
		get_gc()->topgoals.erase( get_gc()->goalidx );
		get_gc()->topgoals.addPHorns( c.topgoals );
		get_gc()->factory.ponews.insert( get_gc()->factory.ponews.end(), c.factory.ponews.begin(), c.factory.ponews.end());
		c.factory.ponews.clear();

		return true;
	}
};

struct ExecContext_Eval : public ExecContextBase {
	GlobalContext	*bc;
	GlobalContext	*c;
	ExecContext	*ec;
	bool		sandbox;

	ExecContext_Eval(ExecContext *p_,  void *f_, const PPredicate *pred_, VLocal *l) : bc(0), c(0), ec(0), sandbox(false), ExecContextBase(p_, f_, pred_, l) {}
	virtual bool first(PException &excp) { 
		if ( get_gc()->issandbox() ) return RERR_Sandbox(excp);
		if ( pred->size() < 1 ) return RERR_argment_number(excp, 1);

	 	c = new GlobalContext();	
		if ( sandbox ) c->setsandbox();

		c->sbuffing();	// 標準出力と標準エラーをバッファリングする 

		const PObject *item = get<PObject>(0);

		string src;
		if ( !item->cnv_string(src) )  return RERR_argment_type(excp, 0);
		if ( !c->compile( src.c_str(), false) ) return RERR_Compile(excp, c->serr);

		if ( pred->size() == 1 ) {
			c->unsbuffing();
		}

		bc = get_gc();
		set_gc(c);	// グローバルコンテクストをセット 
		ec = new ExecContext( c->topgoals.upHorn(0).getGoal(), c->topgoals.upHorn(0).getVcnt());
		bool ret = ec->execute(excp, true);

		set_gc(bc);	// グローバルコンテクストを元に戻す 
		if ( pred->size() >= 2 ) {
			const PString *p = pmm.newPString( pobjs, c->sout);
			c->sout.clear();
			if ( !unify<PString>( pred->size() - 1, p, excp) ) return false;
		}
		if ( pred->size() == 3 ) {
			const PString *p = pmm.newPString( pobjs, c->serr);
			c->serr.clear();
			if ( !unify<PString>( pred->size() - 2, p, excp) ) return false;
		}

		return ret;
	}


	virtual bool backtrack(PException &excp) { 
		if ( get_gc()->issandbox() ) return RERR_Sandbox(excp);
		if ( ec == 0 ) return false;
		bc = get_gc();
		set_gc(c);	// グローバルコンテクストをセット 
		bool ret = ec->execute(excp, false);
		set_gc(bc);	// グローバルコンテクストを元に戻す 
		if ( pred->size() >= 2 ) {
			const PString *p = pmm.newPString( pobjs, c->sout);
			c->sout.clear();
			if ( !unify<PString>( pred->size() - 1, p, excp) ) return false;
		}
		if ( pred->size() == 3 ) {
			const PString *p = pmm.newPString( pobjs, c->serr);
			c->serr.clear();
			if ( !unify<PString>( pred->size() - 2, p, excp) ) return false;
		}
		return ret;
	}

	virtual void cleanup() { 
		if ( c ) { delete c; c = 0; }
		if ( ec ) { ec->cleanup(); delete ec; ec = 0; }
		ExecContextBase::cleanup(); 
	}

	virtual ~ExecContext_Eval() {
		cleanup();
	}
};

struct ExecContext_Sandbox : public ExecContext_Eval {
	ExecContext_Sandbox(ExecContext *p_,  void *f_, const PPredicate *pred_, VLocal *l) : ExecContext_Eval(p_, f_, pred_, l) { sandbox = true; }

	virtual ~ExecContext_Sandbox() {
		cleanup();
	}
};

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

		PString  *result = pmm.newPString(pobjs);
#if _WIN32
	result->value = "Windows";
#else 
 #if unix
	result->value = "Unix";
 #else
  #if defined(__APPLE__)
	result->value = "MAC OS X";
  #else	
	result->value = "Other";
  #endif
 #endif
#endif
		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);
		PString  *result = pmm.newPString(pobjs);
		result->value.push_back(pseparator);
		return (*pred)[0]->unify( *result, this);
	}
};

#endif
