#include <ascript.h>
#include <mysql.h>

AScript_BeginModule(mysql)

//-----------------------------------------------------------------------------
// Object_MySQL
//-----------------------------------------------------------------------------
AScript_DeclarePrivClass(MySQL);

class Object_MySQL : public Object {
public:
	class IteratorQuery : public Iterator {
	private:
		Object_MySQL *_pObj;
		MYSQL_RES *_pRes;
	public:
		inline IteratorQuery(Object_MySQL *pObj, MYSQL_RES *pRes) :
								Iterator(false), _pObj(pObj), _pRes(pRes) {}
		virtual ~IteratorQuery();
		virtual bool DoNext(Environment &env, Signal sig, Value &value);
		virtual String ToString(Signal sig) const;
	};
private:
	MYSQL _mysql;
public:
	AScript_DeclareObjectAccessor(MySQL)
public:
	Object_MySQL();
	virtual ~Object_MySQL();
	virtual Object *Clone() const;
	virtual String ToString(Signal sig, bool exprFlag);
	bool Connect(Signal sig, const char *host, const char *user, const char *passwd,
		const char *db, unsigned int port, const char *unix_socket, unsigned long client_flag);
	void Close();
	Iterator *Query(Signal sig, const char *stmt_str);
};

Object_MySQL::Object_MySQL() : Object(AScript_PrivClass(MySQL))
{
	::mysql_init(&_mysql);
}

Object_MySQL::~Object_MySQL()
{
	Close();
}

Object *Object_MySQL::Clone() const
{
	return NULL;
}

String Object_MySQL::ToString(Signal sig, bool exprFlag)
{
	String str;
	str += "<mysql";
	str += ">";
	return str;
}

bool Object_MySQL::Connect(Signal sig, const char *host, const char *user, const char *passwd,
	const char *db, unsigned int port, const char *unix_socket, unsigned long client_flag)
{
	if (::mysql_real_connect(&_mysql, host, user, passwd,
								db, port, unix_socket, client_flag) == NULL) {
		sig.SetError(ERR_RuntimeError, "MySQL %s\n", ::mysql_error(&_mysql));
		return false;
	}
	return true;
}

void Object_MySQL::Close()
{
	::mysql_close(&_mysql);
}

Iterator *Object_MySQL::Query(Signal sig, const char *stmt_str)
{
	if (::mysql_query(&_mysql, stmt_str) != 0) {
		sig.SetError(ERR_RuntimeError, "MySQL %s\n", ::mysql_error(&_mysql));
		return NULL;
	}
	MYSQL_RES *pRes = ::mysql_store_result(&_mysql);
	if (pRes != NULL) {
		return new IteratorQuery(Object_MySQL::Reference(this), pRes);
	} else if (::mysql_field_count(&_mysql) == 0) {
		return NULL; // no error, but returns NULL
	} else {
		sig.SetError(ERR_RuntimeError, "MySQL %s\n", ::mysql_error(&_mysql));
		return NULL;
	}
}

//-----------------------------------------------------------------------------
// Object_MySQL::IteratorQuery
//-----------------------------------------------------------------------------
Object_MySQL::IteratorQuery::~IteratorQuery()
{
	Object::Delete(_pObj);
	::mysql_free_result(_pRes);
}

bool Object_MySQL::IteratorQuery::DoNext(Environment &env, Signal sig, Value &value)
{
	//Environment &env = *_pObj;
	unsigned int nFields = ::mysql_num_fields(_pRes);
	MYSQL_ROW row = ::mysql_fetch_row(_pRes);
	if (row == NULL) return false;
	ValueList &valList = value.InitAsList(env);
	
	for (unsigned int i = 0; i < nFields; i++) {
		valList.push_back(Value(env, row[i]));
	}
	
	return true;
}

String Object_MySQL::IteratorQuery::ToString(Signal sig) const
{
	return String("<iterator:MySQL#query>");
}

//-----------------------------------------------------------------------------
// AScript interfaces for Object_MySQL
//-----------------------------------------------------------------------------
// mysql.mysql#close()
AScript_DeclareMethod(MySQL, close)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	SetHelp("Shuts down the connection with an MySQL server.");
}

AScript_ImplementMethod(MySQL, close)
{
	Object_MySQL *pObj = Object_MySQL::GetSelfObj(args);
	pObj->Close();
	return Value::Null;
}

// mysql.mysql#query(stmt:string)
AScript_DeclareMethod(MySQL, query)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	DeclareArg(env, "stmt", VTYPE_String);
}

AScript_ImplementMethod(MySQL, query)
{
	Object_MySQL *pObj = Object_MySQL::GetSelfObj(args);
	Iterator *pIterator = pObj->Query(sig, args.GetString(0));
	// Object_MySQL::Query() may return NULL even if no error occurs.
	if (pIterator == NULL) return Value::Null;
	return ReturnIterator(env, sig, args, pIterator);
}

// assignment
AScript_ImplementPrivClass(MySQL)
{
	AScript_AssignMethod(MySQL, query);
}

//-----------------------------------------------------------------------------
// AScript module functions: mysql
//-----------------------------------------------------------------------------
// mysql.connect(host?:string, user?:string, passwd?:string, db?:string) {block?}
AScript_DeclareFunction(connect)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	DeclareArg(env, "host", VTYPE_String, OCCUR_ZeroOrOnce);
	DeclareArg(env, "user", VTYPE_String, OCCUR_ZeroOrOnce);
	DeclareArg(env, "passwd", VTYPE_String, OCCUR_ZeroOrOnce);
	DeclareArg(env, "db", VTYPE_String, OCCUR_ZeroOrOnce);
	DeclareBlock(OCCUR_ZeroOrOnce);
}

AScript_ImplementFunction(connect)
{
	const char *host = args.IsString(0)? args.GetString(0) : NULL;
	const char *user = args.IsString(1)? args.GetString(1) : NULL;
	const char *passwd = args.IsString(2)? args.GetString(2) : NULL;
	const char *db = args.IsString(3)? args.GetString(3) : NULL;
	unsigned int port = 0;
	const char *unix_socket = NULL;
	unsigned long client_flag = 0;
	Object_MySQL *pObj = new Object_MySQL();
	if (!pObj->Connect(sig, host, user, passwd, db, port, unix_socket, client_flag)) {
		delete pObj;
		return Value::Null;
	}
	Value result(pObj);
	if (args.IsBlockSpecified()) {
		const Function *pFuncBlock =
						args.GetBlockFunc(env, sig, GetSymbolForBlock());
		if (pFuncBlock == NULL) return Value::Null;
		ValueList valListArg(result);
		Args argsSub(valListArg);
		pFuncBlock->Eval(env, sig, argsSub);
		result = Value::Null; // object is destroyed here
	}
	return result;
}

// mysql.test()
AScript_DeclareFunction(test)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
}

AScript_ImplementFunction(test)
{
	MYSQL mysql;
	::mysql_init(&mysql);
	::mysql_real_connect(&mysql, NULL, "user", "password", "dbname", 0, NULL, 0);
	if (::mysql_error(&mysql) != 0) {
		::printf("%s\n", ::mysql_error(&mysql));
		return Value::Null;
	}
	::mysql_query(&mysql, "select * from people");
	if (::mysql_error(&mysql) != 0) {
		::printf("%s\n", ::mysql_error(&mysql));
		return Value::Null;
	}
	MYSQL_RES *results = ::mysql_store_result(&mysql);
	MYSQL_ROW row;
	while ((row = ::mysql_fetch_row(results)) != NULL) {
		::printf("%s\n", row[0]);
	}
	::mysql_free_result(results);
	::mysql_close(&mysql);
	::mysql_server_end();
	return Value::Null;
}

// Module entry
AScript_ModuleEntry()
{
	// class realization
	AScript_RealizePrivClass(MySQL, "mysql", env.LookupClass(VTYPE_Object));
	// function assignment
	AScript_AssignFunction(connect);
	AScript_AssignFunction(test);
}

AScript_ModuleTerminate()
{
	::mysql_server_end();
}

AScript_EndModule(mysql, mysql)

AScript_RegisterModule(mysql)
