//
// Object_DateTime
//

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

namespace AScript {

//-----------------------------------------------------------------------------
// Object_DateTime
//-----------------------------------------------------------------------------
Object_DateTime::Object_DateTime(const Object_DateTime &obj) :
										Object(obj), _dateTime(obj._dateTime)
{
}

Object_DateTime::~Object_DateTime()
{
}

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

bool Object_DateTime::DoPropDir(Signal sig, SymbolSet &symbols)
{
	if (!Object::DoPropDir(sig, symbols)) return false;
	symbols.insert(AScript_Symbol(year));
	symbols.insert(AScript_Symbol(month));
	symbols.insert(AScript_Symbol(day));
	symbols.insert(AScript_Symbol(hour));
	symbols.insert(AScript_Symbol(min));
	symbols.insert(AScript_Symbol(sec));
	symbols.insert(AScript_Symbol(usec));
	symbols.insert(AScript_Symbol(wday));
	symbols.insert(AScript_Symbol(yday));
	symbols.insert(AScript_Symbol(unixtime));
	return true;
}

Value Object_DateTime::DoPropGet(Signal sig, const Symbol *pSymbol, bool &evaluatedFlag)
{
	evaluatedFlag = true;
	if (pSymbol->IsIdentical(AScript_Symbol(year))) {
		return Value(static_cast<Number>(_dateTime.GetYear()));
	} else if (pSymbol->IsIdentical(AScript_Symbol(month))) {
		return Value(static_cast<Number>(_dateTime.GetMonth()));
	} else if (pSymbol->IsIdentical(AScript_Symbol(day))) {
		return Value(static_cast<Number>(_dateTime.GetDay()));
	} else if (pSymbol->IsIdentical(AScript_Symbol(hour))) {
		return Value(static_cast<Number>(_dateTime.GetHour()));
	} else if (pSymbol->IsIdentical(AScript_Symbol(min))) {
		return Value(static_cast<Number>(_dateTime.GetMin()));
	} else if (pSymbol->IsIdentical(AScript_Symbol(sec))) {
		return Value(static_cast<Number>(_dateTime.GetSec()));
	} else if (pSymbol->IsIdentical(AScript_Symbol(usec))) {
		return Value(static_cast<Number>(_dateTime.GetUSec()));
	} else if (pSymbol->IsIdentical(AScript_Symbol(wday))) {
		return Value(static_cast<Number>(_dateTime.GetDayOfWeek()));
	} else if (pSymbol->IsIdentical(AScript_Symbol(yday))) {
		return Value(static_cast<Number>(_dateTime.GetDayOfYear() + 1));
	} else if (pSymbol->IsIdentical(AScript_Symbol(unixtime))) {
		return Value(static_cast<Number>(_dateTime.GetUnixTime()));
	}
	evaluatedFlag = false;
	return Value::Null;
}

Value Object_DateTime::DoPropSet(Signal sig,
				const Symbol *pSymbol, const Value &value, bool &evaluatedFlag)
{
	evaluatedFlag = true;
	if (pSymbol->IsIdentical(AScript_Symbol(year))) {
		long num = value.GetLong();
		if (num < 0 || num > 9999) {
			sig.SetError(ERR_ValueError, "invalid number for datetime's year");
			return Value::Null;
		}
		_dateTime.SetYear(static_cast<short>(num));
		return Value(num);
	} else if (pSymbol->IsIdentical(AScript_Symbol(month))) {
		long num = value.GetLong();
		if (num < 1 || num > 12) {
			sig.SetError(ERR_ValueError, "invalid number for datetime's month");
			return Value::Null;
		}
		_dateTime.SetMonth(static_cast<char>(num));
		return Value(num);
	} else if (pSymbol->IsIdentical(AScript_Symbol(day))) {
		long num = value.GetLong();
		if (num < 1 || num > 31) {
			sig.SetError(ERR_ValueError, "invalid number for datetime's day");
			return Value::Null;
		}
		_dateTime.SetDay(static_cast<char>(num));
		return Value(num);
	} else if (pSymbol->IsIdentical(AScript_Symbol(hour))) {
		long num = value.GetLong();
		if (num < 0 || num > 23) {
			sig.SetError(ERR_ValueError, "invalid number for datetime's hour");
			return Value::Null;
		}
		_dateTime.SetHour(static_cast<char>(num));
		return Value(num);
	} else if (pSymbol->IsIdentical(AScript_Symbol(min))) {
		long num = value.GetLong();
		if (num < 0 || num > 59) {
			sig.SetError(ERR_ValueError, "invalid number for datetime's min");
			return Value::Null;
		}
		_dateTime.SetMin(static_cast<char>(num));
		return Value(num);
	} else if (pSymbol->IsIdentical(AScript_Symbol(sec))) {
		long num = value.GetLong();
		if (num < 0 || num > 59) {
			sig.SetError(ERR_ValueError, "invalid number for datetime's sec");
			return Value::Null;
		}
		_dateTime.SetSec(static_cast<char>(num));
		return Value(num);
	} else if (pSymbol->IsIdentical(AScript_Symbol(usec))) {
		long num = value.GetLong();
		if (num < 0 || num > 999999) {
			sig.SetError(ERR_ValueError, "invalid number for datetime's usec");
			return Value::Null;
		}
		_dateTime.SetUSec(num);
		return Value(num);
	}
	return DoPropGet(sig, pSymbol, evaluatedFlag);
}

String Object_DateTime::ToString(Signal sig, bool exprFlag)
{
	String str;
	str += "<datetime:";
	str += _dateTime.ToString("%Y-%m-%dT%H:%M:%S");
	str += _dateTime.GetTZOffsetStr(true);
	str += ">";
	return str;
}

//-----------------------------------------------------------------------------
// AScript interfaces for Object_DateTime
//-----------------------------------------------------------------------------
// datetime#format(format)
AScript_DeclareMethod(DateTime, format)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	DeclareArg(env, "format", VTYPE_Any);
	SetHelp(
	"Returns a string after converting datetime properties with the specified format.\n"
	"The format can either be a string to specify a user-specific format or a symbol of\n"
	"custom-defined one. A user-specific format contains following specifiers.\n"
	"  %d  day of month\n"
	"  %H  hour in 24-hour format\n"
	"  %I  hour in 12-hour format\n"
	"  %m  month\n"
	"  %M  minute\n"
	"  %S  second\n"
	"  %w  day of week\n"
	"  %y  lower two digits of year\n"
	"  %Y  year\n"
	"A custom-defined format is one of the symbols: `w3c, `http and `asctime.");
}

AScript_ImplementMethod(DateTime, format)
{
	static const char *weekNames[] = {
		"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat",
	};
	static const char *monthNames[] = { "",
		"Jan", "Feb", "Mar", "Apr", "May", "Jun",
		"Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
	};
	const DateTime &dateTime = Object_DateTime::GetSelfObj(args)->GetDateTime();
	if (args.IsString(0)) {
		return Value(env, dateTime.ToString(args.GetString(0)).c_str());
	} else if (args.IsSymbol(0)) {
		const Symbol *pSymbol = args.GetSymbol(0);
		char str[64];
		if (pSymbol->IsIdentical(AScript_Symbol(w3c))) {
			::sprintf(str, "%04d-%02d-%02dT%02d:%02d:%02d%s",
				dateTime.GetYear(), dateTime.GetMonth(), dateTime.GetDay(),
				dateTime.GetHour(), dateTime.GetMin(), dateTime.GetSec(),
				dateTime.GetTZOffsetStr(true).c_str());
		} else if (pSymbol->IsIdentical(AScript_Symbol(http))) {
			String strTZ;
			if (dateTime.HasTZOffset()) {
				strTZ = ' ';
				strTZ += dateTime.GetTZOffsetStr(false);
			}
			::sprintf(str, "%s, %02d %s %04d %02d:%02d:%02d%s",
				weekNames[dateTime.GetDayOfWeek()], dateTime.GetDay(),
				monthNames[dateTime.GetMonth()], dateTime.GetYear(),
				dateTime.GetHour(), dateTime.GetMin(), dateTime.GetSec(),
				strTZ.c_str());
		} else if (pSymbol->IsIdentical(AScript_Symbol(asctime))) {
			String strTZ;
			if (dateTime.HasTZOffset()) {
				strTZ = ' ';
				strTZ += dateTime.GetTZOffsetStr(false);
			}
			::sprintf(str, "%s %s %2d %02d:%02d:%02d%s %04d",
				weekNames[dateTime.GetDayOfWeek()],
				monthNames[dateTime.GetMonth()], dateTime.GetDay(),
				dateTime.GetHour(), dateTime.GetMin(), dateTime.GetSec(),
				strTZ.c_str(), dateTime.GetYear());
		} else {
			sig.SetError(ERR_ValueError, "unknown format symbol %s", pSymbol->GetName());
			return Value::Null;
		}
		return Value(env, str);
	}
	SetError_InvalidValType(sig, args.GetValue(0));
	return Value::Null;
}

// datetime#settzoff(mins:number):reduce
AScript_DeclareMethod(DateTime, settzoff)
{
	SetMode(RSLTMODE_Reduce, FLAG_None);
	DeclareArg(env, "mins", VTYPE_Number);
}

AScript_ImplementMethod(DateTime, settzoff)
{
	DateTime &dateTime = Object_DateTime::GetSelfObj(args)->GetDateTime();
	dateTime.SetTZOffset(args.GetLong(0) * 60);
	return args.GetSelf();
}

// datetime#clrtzoff():reduce
AScript_DeclareMethod(DateTime, clrtzoff)
{
	SetMode(RSLTMODE_Reduce, FLAG_None);
}

AScript_ImplementMethod(DateTime, clrtzoff)
{
	DateTime &dateTime = Object_DateTime::GetSelfObj(args)->GetDateTime();
	dateTime.ClearTZOffset();
	return args.GetSelf();
}

// datetime#utc()
AScript_DeclareMethod(DateTime, utc)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
}

AScript_ImplementMethod(DateTime, utc)
{
	const DateTime &dateTime = Object_DateTime::GetSelfObj(args)->GetDateTime();
	if (!dateTime.HasTZOffset()) {
		sig.SetError(ERR_ValueError, "datetime has no timezone offset");
		return Value::Null;
	}
	return Value(env, dateTime.ToUTC());
}

// assignment
Class_DateTime::Class_DateTime(Environment *pEnvOuter) : Class(pEnvOuter)
{
	AScript_AssignMethod(DateTime, format);
	AScript_AssignMethod(DateTime, settzoff);
	AScript_AssignMethod(DateTime, clrtzoff);
	AScript_AssignMethod(DateTime, utc);
}

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

}
