//-----------------------------------------------------------------------------
// AScript path module
//-----------------------------------------------------------------------------
#include <ascript.h>

AScript_BeginModule(path)

//-----------------------------------------------------------------------------
// AScript module functions: path
//-----------------------------------------------------------------------------
// path.stat(pathname:string):map
AScript_DeclareFunction(stat)
{
	SetMode(RSLTMODE_Normal, FLAG_Map);
	DeclareArg(env, "pathname", VTYPE_String);
	SetHelp(
	"Returns a stat object associated with the specified item.");
}

AScript_ImplementFunction(stat)
{
	Directory *pDirectory = Directory::OpenDirectory(env, sig,
									args.GetString(0), Directory::NF_Signal);
	if (sig.IsSignalled()) return Value::Null;
	Object *pObj = pDirectory->GetStatObj(sig);
	Directory::Delete(pDirectory);
	if (sig.IsSignalled()) return Value::Null;
	return Value(pObj);
}

// path.exists(pathname:string):map
AScript_DeclareFunction(exists)
{
	SetMode(RSLTMODE_Normal, FLAG_Map);
	DeclareArg(env, "pathname", VTYPE_String);
	SetHelp("Returns true if the specified file exists in a file system.");
}

AScript_ImplementFunction(exists)
{
	bool existFlag = Directory::IsExist(env, sig, args.GetString(0));
	if (sig.IsSignalled()) return Value::Null;
	return Value(existFlag);
}

// path.dir(pathname?:string, pattern*:string):map:flat:[stat,icase,file,dir] {block?}
AScript_DeclareFunction(dir)
{
	SetMode(RSLTMODE_Normal, FLAG_Map | FLAG_Flat);
	DeclareArg(env, "pathname", VTYPE_String, OCCUR_ZeroOrOnce);
	DeclareArg(env, "pattern", VTYPE_String, OCCUR_ZeroOrMore);
	DeclareBlock(OCCUR_ZeroOrOnce);
	DeclareAttr(AScript_Symbol(stat));
	DeclareAttr(AScript_Symbol(icase));
	DeclareAttr(AScript_Symbol(file));
	DeclareAttr(AScript_Symbol(dir));
	SetHelp(
	"Creates an iterator that lists item names in the specified directory.\n"
	"If pathname is omitted, the current directory shall be listed.\n"
	ITERATOR_HELP);
}

AScript_ImplementFunction(dir)
{
	bool addSepFlag = true;
	bool statFlag = args.IsSet(AScript_Symbol(stat));
	bool ignoreCaseFlag = args.IsSet(AScript_Symbol(icase));
	bool fileFlag = args.IsSet(AScript_Symbol(file)) || !args.IsSet(AScript_Symbol(dir));
	bool dirFlag = args.IsSet(AScript_Symbol(dir)) || !args.IsSet(AScript_Symbol(file));
	const char *pathName = args.IsString(0)? args.GetString(0) : "";
	int depthMax = 0;
	StringList patterns;
	if (!args.GetList(1).ToStringList(sig, patterns)) return Value::Null;
	Directory::Iterator_Walk *pIterator = new Directory::Iterator_Walk(
					addSepFlag, statFlag, ignoreCaseFlag, fileFlag, dirFlag);
	if (!pIterator->Init(env, sig, pathName, depthMax, patterns)) {
		Iterator::Delete(pIterator);
		return Value::Null;
	}
	return ReturnIterator(env, sig, args, pIterator);
}

// path.walk(pathname?:string, maxdepth?:number, pattern*:string):map:flat:[stat,icase,file,dir] {block?}
AScript_DeclareFunction(walk)
{
	SetMode(RSLTMODE_Normal, FLAG_Map | FLAG_Flat);
	DeclareArg(env, "pathname", VTYPE_String, OCCUR_ZeroOrOnce);
	DeclareArg(env, "maxdepth", VTYPE_Number, OCCUR_ZeroOrOnce);
	DeclareArg(env, "pattern", VTYPE_String, OCCUR_ZeroOrMore);
	DeclareBlock(OCCUR_ZeroOrOnce);
	DeclareAttr(AScript_Symbol(stat));
	DeclareAttr(AScript_Symbol(icase));
	DeclareAttr(AScript_Symbol(file));
	DeclareAttr(AScript_Symbol(dir));
	SetHelp(
	"Creates an iterator that recursively lists item names under the specified\n"
	"directory. If pathname is omitted, search starts at the current directory\n"
	ITERATOR_HELP);
}

AScript_ImplementFunction(walk)
{
	bool addSepFlag = true;
	bool statFlag = args.IsSet(AScript_Symbol(stat));
	bool ignoreCaseFlag = args.IsSet(AScript_Symbol(icase));
	bool fileFlag = args.IsSet(AScript_Symbol(file)) || !args.IsSet(AScript_Symbol(dir));
	bool dirFlag = args.IsSet(AScript_Symbol(dir)) || !args.IsSet(AScript_Symbol(file));
	const char *pathName = args.IsString(0)? args.GetString(0) : "";
	int depthMax = args.IsNumber(1)? args.GetInt(1) : -1;
	StringList patterns;
	if (!args.GetList(2).ToStringList(sig, patterns)) return Value::Null;
	Directory::Iterator_Walk *pIterator = new Directory::Iterator_Walk(
					addSepFlag, statFlag, ignoreCaseFlag, fileFlag, dirFlag);
	if (!pIterator->Init(env, sig, pathName, depthMax, patterns)) {
		Iterator::Delete(pIterator);
		return Value::Null;
	}
	return ReturnIterator(env, sig, args, pIterator);
}

// path.glob(pattern:string):map:flat:[stat,icase,file,dir] {block?}
AScript_DeclareFunction(glob)
{
	SetMode(RSLTMODE_Normal, FLAG_Map | FLAG_Flat);
	DeclareArg(env, "pattern", VTYPE_String);
	DeclareBlock(OCCUR_ZeroOrOnce);
	DeclareAttr(AScript_Symbol(stat));
	DeclareAttr(AScript_Symbol(icase));
	DeclareAttr(AScript_Symbol(file));
	DeclareAttr(AScript_Symbol(dir));
	SetHelp(
	"Creates an iterator for item names that match with a pattern supporting\n"
	"UNIX shell-style wild cards. In default, case of characters is distinguished.\n"
	"To ignore cases, specify :icase attribute.\n"
	ITERATOR_HELP);
}

AScript_ImplementFunction(glob)
{
	bool addSepFlag = true;
	bool statFlag = args.IsSet(AScript_Symbol(stat));
	bool ignoreCaseFlag = args.IsSet(AScript_Symbol(icase));
	bool fileFlag = args.IsSet(AScript_Symbol(file)) || !args.IsSet(AScript_Symbol(dir));
	bool dirFlag = args.IsSet(AScript_Symbol(dir)) || !args.IsSet(AScript_Symbol(file));
	Directory::Iterator_Glob *pIterator = new Directory::Iterator_Glob(
					addSepFlag, statFlag, ignoreCaseFlag, fileFlag, dirFlag);
	if (!pIterator->Init(env, sig, args.GetString(0))) {
		Iterator::Delete(pIterator);
		return Value::Null;
	}
	return ReturnIterator(env, sig, args, pIterator);
}

// path.match(pattern:string, name:string):map
AScript_DeclareFunction(match)
{
	SetMode(RSLTMODE_Normal, FLAG_Map);
	DeclareArg(env, "pattern", VTYPE_String);
	DeclareArg(env, "name", VTYPE_String);
	DeclareAttr(AScript_Symbol(icase));
	SetHelp(
	"Returns true if a name matches with a pattern that supports UNIX\n"
	"shell-style wild cards. In default, case of characters is distinguished.\n"
	"To ignore cases, specify :icase attribute.");
}

AScript_ImplementFunction(match)
{
	const char *pattern = args.GetString(0);
	const char *name = args.GetString(1);
	bool ignoreCaseFlag = args.IsSet(AScript_Symbol(icase));
	return Value(Directory::IsMatchName(pattern, name, ignoreCaseFlag));
}

// path.split(pathname:string):map
AScript_DeclareFunction(split)
{
	SetMode(RSLTMODE_Normal, FLAG_Map);
	DeclareArg(env, "pathname", VTYPE_String);
	SetHelp(
	"Splits a pathname by a directory separator and returns a list containing\n"
	"a directory name as the first element and a base name as the second one.\n"
	"This has the same result as calling path.dirname() and path.basename().");
}

AScript_ImplementFunction(split)
{
	Value result;
	ValueList &valList = result.InitAsList(env);
	valList.push_back(Value(env, Directory::ExtractDirName(args.GetString(0)).c_str()));
	valList.push_back(Value(env, Directory::ExtractBaseName(args.GetString(0)).c_str()));
	return result;
}

// path.dirname():map
AScript_DeclareFunction(dirname)
{
	SetMode(RSLTMODE_Normal, FLAG_Map);
	DeclareArg(env, "pathname", VTYPE_String);
	SetHelp(
	"Splits a pathname by a directory separator and returns a directory name part.");
}

AScript_ImplementFunction(dirname)
{
	return Value(env, Directory::ExtractDirName(args.GetString(0)).c_str());
}

// path.basename(pathname:string):map
AScript_DeclareFunction(basename)
{
	SetMode(RSLTMODE_Normal, FLAG_Map);
	DeclareArg(env, "pathname", VTYPE_String);
	SetHelp(
	"Splits a pathname by a directory separator and returns a base name part.");
}

AScript_ImplementFunction(basename)
{
	return Value(env, Directory::ExtractBaseName(args.GetString(0)).c_str());
}

// path.abspath(name:string):map:[http]
AScript_DeclareFunction(absname)
{
	SetMode(RSLTMODE_Normal, FLAG_Map);
	DeclareArg(env, "name", VTYPE_String);
	DeclareAttr(AScript_Symbol(http));
	SetHelp("Returns an absolute path name of the given name.");
}

AScript_ImplementFunction(absname)
{
	return Value(env, OAL::MakeAbsPath(OAL::FileSeparator, args.GetString(0)).c_str());
}

// path.join(paths+:string):map:[http]
AScript_DeclareFunction(join)
{
	SetMode(RSLTMODE_Normal, FLAG_Map);
	DeclareArg(env, "paths", VTYPE_String, OCCUR_OnceOrMore);
	DeclareAttr(AScript_Symbol(http));
	SetHelp("Returns a path name that joins given strings with directory separators.");
}

AScript_ImplementFunction(join)
{
	String str;
	const ValueList &valList = args.GetList(0);
	char chSep = OAL::FileSeparator;
	if (args.IsSet(AScript_Symbol(http))) chSep = '/';
	foreach_const (ValueList, pValue, valList) {
		if (pValue != valList.begin()) str += chSep;
		str += pValue->GetString();
	}
	return Value(env, str.c_str());
}

// path.splitext(pathname:string):map
AScript_DeclareFunction(splitext)
{
	SetMode(RSLTMODE_Normal, FLAG_Map);
	DeclareArg(env, "pathname", VTYPE_String);
	SetHelp(
	"Splits a pathname by a dot character indicating a beginning of an extension\n"
	"and returns a list containing a path name without an extention\n"
	"and an extention part.");
}

AScript_ImplementFunction(splitext)
{
	const char *pathName = args.GetString(0);
	const char *p = Directory::ExtractExtName(pathName);
	Value result;
	ValueList &valList = result.InitAsList(env);
	size_t lenLeft = p - pathName;
	if (p > pathName && *(p - 1) == '.') lenLeft--;
	valList.push_back(Value(env, pathName, lenLeft));
	valList.push_back(Value(env, p));
	return result;
}

// Module entry
AScript_ModuleEntry()
{
	// value assignment
	do {
		char str[2];
		str[0] = OAL::FileSeparator;
		str[1] = '\0';
		AScript_AssignValue(sep_file, Value(env, str));
	} while (0);
	// function assignment
	AScript_AssignFunction(stat);
	AScript_AssignFunction(exists);
	AScript_AssignFunction(dir);
	AScript_AssignFunction(walk);
	AScript_AssignFunction(glob);
	AScript_AssignFunction(match);
	AScript_AssignFunction(split);
	AScript_AssignFunction(dirname);
	AScript_AssignFunction(basename);
	AScript_AssignFunction(absname);
	AScript_AssignFunction(join);
	AScript_AssignFunction(splitext);
}

AScript_ModuleTerminate()
{
}

AScript_EndModule(path, path)

AScript_RegisterModule(path)
