//
// Object_URI
// referred to RFC1738 (Uniform Resource Locators)

#include "ascript/Object_URI.h"
#include "ascript/Expr.h"

namespace AScript {

//-----------------------------------------------------------------------------
// Object_URI
//-----------------------------------------------------------------------------
Object_URI::Object_URI(const Object_URI &obj) : Object(obj),
	_userValidFlag(obj._userValidFlag), _scheme(obj._scheme),
	_user(obj._user), _password(obj._password),
	_host(obj._host), _port(obj._port), _urlpath(obj._urlpath), _misc(obj._misc)
{
	// ftp, http, gopher, mailto, news, nntp, telnet, wais, file, prospro
}

Object_URI::~Object_URI()
{
}

Object *Object_URI::Clone() const
{
	return new Object_URI(*this);
}

bool Object_URI::Parse(Signal sig, const char *str)
{
	enum {
		STAT_Scheme,
		STAT_Slash1, STAT_Slash2, STAT_Host, STAT_Port,
		STAT_UrlPath,
		STAT_MailTo,
		STAT_News,
	} stat = STAT_Scheme;
	_userValidFlag = false;
	_scheme.clear();
	_user.clear();
	_password.clear();
	_host.clear();
	_port.clear();
	_urlpath.clear();
	_misc.clear();
	for (const char *p = str; *p != '\0'; p++) {
		char ch = *p;
		if (stat == STAT_Scheme) {
			if (ch == ':') {
				if (_scheme == "mailto") {
					stat = STAT_MailTo;
				} else if (_scheme == "news") {
					stat = STAT_News;
				} else {
					stat = STAT_Slash1;
				}
			} else if ('a' <= ch && ch <= 'z' || ch == '+' || ch == '.' || ch == '-') {
				_scheme += ch;
			} else if ('A' <= ch && ch <= 'Z') {
				_scheme += ch - 'A' + 'a';
			} else {
				sig.SetError(ERR_ValueError, "invalid scheme name");
				return false;
			}
		} else if (stat == STAT_Slash1) {
			if (ch == '/') {
				stat = STAT_Slash2;
			} else {
				SetError_InvalidURIFormat(sig);
				return false;
			}
		} else if (stat == STAT_Slash2) {
			if (ch == '/') {
				stat = STAT_Host;
			} else {
				SetError_InvalidURIFormat(sig);
				return false;
			}
		} else if (stat == STAT_Host) {
			if (ch == ':') {
				stat = STAT_Port;
			} else if (ch == '/') {
				stat = STAT_UrlPath;
			} else if (ch == '@') {
				if (_userValidFlag) {
					SetError_InvalidURIFormat(sig);
					return false;
				}
				_userValidFlag = true;
				_user = _host, _password = _port;
				_host.clear(), _port.clear();
			} else if (0x20 <= ch && ch < 0x7f) {
				_host += ch;
			} else {
				SetError_InvalidURIFormat(sig);
				return false;
			}
		} else if (stat == STAT_Port) {
			if (ch == '/') {
				stat = STAT_UrlPath;
			} else if (ch == '@') {
				if (_userValidFlag) {
					SetError_InvalidURIFormat(sig);
					return false;
				}
				_userValidFlag = true;
				_user = _host, _password = _port;
				_host.clear(), _port.clear();
				stat = STAT_Host;
			} else if (0x20 <= ch && ch < 0x7f) {
				_port += ch;
			} else {
				SetError_InvalidURIFormat(sig);
				return false;
			}
		} else if (stat == STAT_UrlPath) {
			_urlpath += ch;
		} else if (stat == STAT_MailTo) {
			_misc += ch;
		} else if (stat == STAT_News) {
			_misc += ch;
		}
	}
	return true;
}

String Object_URI::ToString(Signal sig, bool exprFlag)
{
	String rtn;
	if (!_scheme.empty()) {
		rtn += _scheme;
		rtn += ":";
		if (_scheme != "mailto" && _scheme != "news") {
			rtn += "//";
		}
	}
	if (_userValidFlag) {
		if (!_user.empty()) {
			rtn += _user;
			if (!_password.empty()) {
				rtn += ":";
				rtn += _password;
			}
		}
		rtn += "@";
	}
	rtn += _host;
	if (!_port.empty()) {
		rtn += ":";
		rtn += _port;
	}
	if (!_urlpath.empty()) {
		rtn += "/";
		rtn += _urlpath;
	}
	if (!_misc.empty()) {
		rtn += _misc;
	}
	return rtn;
}

void Object_URI::SetError_InvalidURIFormat(Signal sig)
{
	sig.SetError(ERR_ValueError, "invalid URI format");
}

//-----------------------------------------------------------------------------
// AScript interfaces for Object_URI
//-----------------------------------------------------------------------------
// uri#scheme(str?:string)
AScript_DeclareMethod(URI, scheme)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	DeclareArg(env, "str", VTYPE_String, OCCUR_ZeroOrOnce);
	SetHelp("Sets or gets scheme part of the URI.");
}

AScript_ImplementMethod(URI, scheme)
{
	Object_URI *pSelf = Object_URI::GetSelfObj(args);
	if (args.IsString(0)) {
		pSelf->SetScheme(args.GetString(0));
		return args.GetSelf();
	} else {
		return Value(env, pSelf->GetScheme());
	}
}

// uri#user(str?:string)
AScript_DeclareMethod(URI, user)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	DeclareArg(env, "str", VTYPE_String, OCCUR_ZeroOrOnce);
	SetHelp("Sets or gets scheme user of the URI.");
}

AScript_ImplementMethod(URI, user)
{
	Object_URI *pSelf = Object_URI::GetSelfObj(args);
	if (args.IsString(0)) {
		pSelf->SetUser(args.GetString(0));
		return args.GetSelf();
	} else {
		if (!pSelf->IsUserValid()) return Value::Null;
		return Value(env, pSelf->GetUser());
	}
}

// uri#password(str?:string)
AScript_DeclareMethod(URI, password)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	DeclareArg(env, "str", VTYPE_String, OCCUR_ZeroOrOnce);
	SetHelp("Sets or gets password part of the URI.");
}

AScript_ImplementMethod(URI, password)
{
	Object_URI *pSelf = Object_URI::GetSelfObj(args);
	if (args.IsString(0)) {
		pSelf->SetPassword(args.GetString(0));
		return args.GetSelf();
	} else {
		return Value(env, pSelf->GetPassword());
	}
}

// uri#host(str?:string)
AScript_DeclareMethod(URI, host)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	DeclareArg(env, "str", VTYPE_String, OCCUR_ZeroOrOnce);
	SetHelp("Sets or gets host part of the URI.");
}

AScript_ImplementMethod(URI, host)
{
	Object_URI *pSelf = Object_URI::GetSelfObj(args);
	if (args.IsString(0)) {
		pSelf->SetHost(args.GetString(0));
		return args.GetSelf();
	} else {
		return Value(env, pSelf->GetHost());
	}
}

// uri#port(str?:string)
AScript_DeclareMethod(URI, port)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	DeclareArg(env, "str", VTYPE_String, OCCUR_ZeroOrOnce);
	SetHelp("Sets or gets port part of the URI.");
}

AScript_ImplementMethod(URI, port)
{
	Object_URI *pSelf = Object_URI::GetSelfObj(args);
	if (args.IsString(0)) {
		pSelf->SetPort(args.GetString(0));
		return args.GetSelf();
	} else {
		return Value(env, pSelf->GetPort());
	}
}

// uri#urlpath(str?:string)
AScript_DeclareMethod(URI, urlpath)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	DeclareArg(env, "str", VTYPE_String, OCCUR_ZeroOrOnce);
	SetHelp("Sets or gets url-path part of the URI.");
}

AScript_ImplementMethod(URI, urlpath)
{
	Object_URI *pSelf = Object_URI::GetSelfObj(args);
	if (args.IsString(0)) {
		pSelf->SetUrlPath(args.GetString(0));
		return args.GetSelf();
	} else {
		return Value(env, pSelf->GetUrlPath());
	}
}

// assignment
Class_URI::Class_URI(Environment *pEnvOuter) : Class(pEnvOuter)
{
	AScript_AssignMethod(URI, scheme);
	AScript_AssignMethod(URI, user);
	AScript_AssignMethod(URI, password);
	AScript_AssignMethod(URI, host);
	AScript_AssignMethod(URI, port);
	AScript_AssignMethod(URI, urlpath);
}

Object *Class_URI::CreateDescendant(Environment &env, Signal sig, Class *pClass)
{
	ERROREND(env, "this function must not be called");
	return NULL;
}

}
