#include <ascript.h>
#include <libpq-fe.h>

AScript_BeginModule(postgresql)

//-----------------------------------------------------------------------------
// Object_PostgreSQL
//-----------------------------------------------------------------------------
AScript_DeclarePrivClass(PostgreSQL);

class Object_PostgreSQL : public Object {
public:
	class IteratorTuple : public Iterator {
	private:
		Object_PostgreSQL *_pObj;
		PGresult *_res;
		int _iTuple;
	public:
		inline IteratorTuple(Object_PostgreSQL *pObj, PGresult *res) :
						Iterator(false), _pObj(pObj), _res(res), _iTuple(0) {}
		virtual ~IteratorTuple();
		virtual bool DoNext(Environment &env, Signal sig, Value &value);
		virtual String ToString(Signal sig) const;
	};
private:
    PGconn *_conn;
public:
	AScript_DeclareObjectAccessor(PostgreSQL)
public:
	Object_PostgreSQL();
	virtual ~Object_PostgreSQL();
	virtual Object *Clone() const;
	virtual String ToString(Signal sig, bool exprFlag);
	bool Connect(Signal sig, const char *pghost, const char *pgport,
		const char *pgoptions, const char *pgtty, const char *dbName, const char *login, const char *pwd);
	void Close();
	Iterator *Exec(Signal sig, const char *command);
};

Object_PostgreSQL::Object_PostgreSQL() :
					Object(AScript_PrivClass(PostgreSQL)), _conn(NULL)
{
}

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

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

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

bool Object_PostgreSQL::Connect(Signal sig, const char *pghost, const char *pgport,
	const char *pgoptions, const char *pgtty, const char *dbName, const char *login, const char *pwd)
{
	_conn = ::PQsetdbLogin(pghost, pgport, pgoptions, pgtty, dbName, login, pwd);
	if (::PQstatus(_conn) == CONNECTION_BAD) {
		sig.SetError(ERR_RuntimeError, "PostgreSQL %s", ::PQerrorMessage(_conn));
	    ::PQfinish(_conn);
	    _conn = NULL;
	    return false;
    }
	return true;
}

void Object_PostgreSQL::Close()
{
	if (_conn != NULL) {
		::PQfinish(_conn);
		_conn = NULL;
	}
}

Iterator *Object_PostgreSQL::Exec(Signal sig, const char *command)
{
	if (_conn == NULL) return NULL;
	PGresult *res = ::PQexec(_conn, command);
	if (res == NULL || ::PQresultStatus(res) != PGRES_COMMAND_OK) {
		sig.SetError(ERR_RuntimeError, "PostgreSQL %s", ::PQerrorMessage(_conn));
		::PQclear(res);
		return NULL;
	} else if (::PQresultStatus(res) == PGRES_TUPLES_OK) {
		Iterator *pIterator =
				new IteratorTuple(Object_PostgreSQL::Reference(this), res);
		return pIterator;
	}
	::PQclear(res);
	return NULL;
}

//-----------------------------------------------------------------------------
// Object_PostgreSQL::IteratorTuple
//-----------------------------------------------------------------------------
Object_PostgreSQL::IteratorTuple::~IteratorTuple()
{
	Object::Delete(_pObj);
	::PQclear(_res);
}

bool Object_PostgreSQL::IteratorTuple::DoNext(Environment &env, Signal sig, Value &value)
{
	//Environment &env = *_pObj;
	if (_iTuple >= ::PQntuples(_res)) return false;
	int nFields = ::PQnfields(_res);
	ValueList &valList = value.InitAsList(env);
	for (int iField = 0; iField < nFields; iField++) {
		valList.push_back(Value(env, ::PQgetvalue(_res, _iTuple, iField)));
	}
	_iTuple++;
	return true;
}

String Object_PostgreSQL::IteratorTuple::ToString(Signal sig) const
{
	return String("<iterator:postgresql#tuple>");
}

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

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

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

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

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

//-----------------------------------------------------------------------------
// AScript module functions: postgresql
//-----------------------------------------------------------------------------
// postgresql.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 *pghost = args.IsString(0)? args.GetString(0) : NULL;
	const char *login = args.IsString(1)? args.GetString(1) : NULL;
	const char *pwd = args.IsString(2)? args.GetString(2) : NULL;
	const char *dbName = args.IsString(3)? args.GetString(3) : NULL;
	const char *pgport = NULL;
	const char *pgoptions = NULL;
	const char *pgtty = NULL;
	Object_PostgreSQL *pObj = new Object_PostgreSQL();
	if (!pObj->Connect(sig, pghost, pgport, pgoptions, pgtty, dbName, login, pwd)) {
		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;
}

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

AScript_ImplementFunction(test)
{
	return Value::Null;
}

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

AScript_ModuleTerminate()
{
}

AScript_EndModule(postgresql, postgresql)

AScript_RegisterModule(postgresql)
