//-----------------------------------------------------------------------------
// AScript sqlite3 module
//-----------------------------------------------------------------------------
#include <ascript.h>
extern "C" {
#include <sqlite3.h>
}

AScript_BeginModule(sqlite3)

//-----------------------------------------------------------------------------
// Object_SQLite3
//-----------------------------------------------------------------------------
AScript_DeclarePrivClass(SQLite3);

class Object_SQLite3 : public Object {
public:
	class IteratorQuery : public Iterator {
	private:
		Object_SQLite3 *_pObj;
		sqlite3_stmt *_pStmt;
	public:
		inline IteratorQuery(Object_SQLite3 *pObj, sqlite3_stmt *pStmt) :
							Iterator(false), _pObj(pObj), _pStmt(pStmt) {}
		virtual ~IteratorQuery();
		virtual bool DoNext(Environment &env, Signal sig, Value &value);
		virtual String ToString(Signal sig) const;
	};
private:
	sqlite3 *_db;
	String _fileName;
public:
	AScript_DeclareObjectAccessor(SQLite3)
public:
	inline Object_SQLite3(Environment &env) :
							Object(AScript_PrivClass(SQLite3)), _db(NULL) {}
	virtual ~Object_SQLite3();
	virtual Object *Clone() const;
	virtual String ToString(Signal sig, bool exprFlag);
	bool Open(Signal sig, const char *fileName);
	void Close();
	Value Exec(Signal sig, const char *sql, Args &args);
	bool ExecNoResult(Signal sig, const char *sql);
	IteratorQuery *Query(Signal sig, const char *sql);
	Value GetColumnNames(Signal sig, const char *sql);
public:
	void SetError_NotOpened(Signal sig);
private:
	static int Callback(void *user, int argc, char **argv, char **azColName);
};

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

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

bool Object_SQLite3::Open(Signal sig, const char *fileName)
{
	int rc = ::sqlite3_open(fileName, &_db);
	if (rc != SQLITE_OK) {
		sig.SetError(ERR_RuntimeError, "SQLite3 failed to create SQLite3 object");
		return false;
	}
	_fileName = fileName;
	return true;
}

void Object_SQLite3::Close()
{
	if (_db != NULL) {
		::sqlite3_close(_db);
		_db = NULL;
		_fileName = "";
	}
}

Value Object_SQLite3::Exec(Signal sig, const char *sql, Args &args)
{
	if (_db == NULL) {
		SetError_NotOpened(sig);
		return Value::Null;
	}
	char *errMsg;
	Value result;
	Function::ResultComposer resultComposer(*this, args, result);
	int rc = ::sqlite3_exec(_db, sql, Callback, &resultComposer, &errMsg); 
	if (rc != SQLITE_OK) {
		sig.SetError(ERR_RuntimeError, "SQLite3 %s", errMsg);
		return Value::Null;
	}
	return result;
}

bool Object_SQLite3::ExecNoResult(Signal sig, const char *sql)
{
	if (_db == NULL) {
		SetError_NotOpened(sig);
		return false;
	}
	char *errMsg;
	int rc = ::sqlite3_exec(_db, sql, NULL, NULL, &errMsg); 
	if (rc != SQLITE_OK) {
		sig.SetError(ERR_RuntimeError, "SQLite3 %s", errMsg);
		return false;
	}
	return true;
}

Object_SQLite3::IteratorQuery *Object_SQLite3::Query(Signal sig, const char *sql)
{
	if (_db == NULL) {
		SetError_NotOpened(sig);
		return NULL;
	}
	sqlite3_stmt *pStmt;
	const char *pzTail;
	int rc = ::sqlite3_prepare(_db, sql, -1, &pStmt, &pzTail);
	if (rc != SQLITE_OK) {
		sig.SetError(ERR_RuntimeError, "SQLite3 %s", ::sqlite3_errmsg(_db));
		return NULL;
	}
	return new IteratorQuery(Object_SQLite3::Reference(this), pStmt);
}

Value Object_SQLite3::GetColumnNames(Signal sig, const char *sql)
{
	Environment &env = *this;
	if (_db == NULL) {
		SetError_NotOpened(sig);
		return Value::Null;
	}
	sqlite3_stmt *pStmt;
	const char *pzTail;
	int rc = ::sqlite3_prepare(_db, sql, -1, &pStmt, &pzTail);
	if (rc != SQLITE_OK) {
		sig.SetError(ERR_RuntimeError, "SQLite3 %s", ::sqlite3_errmsg(_db));
		return Value::Null;
	}
	Value result;
	ValueList &valList = result.InitAsList(env);
	int nCols = ::sqlite3_column_count(pStmt);
	for (int iCol = 0; iCol < nCols; iCol++) {
		valList.push_back(Value(env, ::sqlite3_column_name(pStmt, iCol)));
	}
	::sqlite3_finalize(pStmt);
	return result;
}

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

int Object_SQLite3::Callback(void *user, int argc, char **argv, char **azColName)
{
	Function::ResultComposer *pResultComposer =
						reinterpret_cast<Function::ResultComposer *>(user);
	Environment &env = pResultComposer->GetEnv();
	Value value;
	ValueList &valList = value.InitAsList(env);
	for (int i = 0; i < argc; i++) {
		valList.push_back(Value(env, argv[i]));
	}
	pResultComposer->Store(value);
	return SQLITE_OK;
}

void Object_SQLite3::SetError_NotOpened(Signal sig)
{
	sig.SetError(ERR_RuntimeError, "database not opened");
}

//-----------------------------------------------------------------------------
// Object_SQLite3::IteratorQuery
//-----------------------------------------------------------------------------
Object_SQLite3::IteratorQuery::~IteratorQuery()
{
	Object::Delete(_pObj);
	::sqlite3_finalize(_pStmt);
}


bool Object_SQLite3::IteratorQuery::DoNext(Environment &env, Signal sig, Value &value)
{
	if (::sqlite3_step(_pStmt) != SQLITE_ROW) {
		return false;
	}
	ValueList &valList = value.InitAsList(*_pObj);
	int nCols = ::sqlite3_column_count(_pStmt);
	for (int iCol = 0; iCol < nCols; iCol++) {
		int type = ::sqlite3_column_type(_pStmt, iCol);
		if (type == SQLITE_INTEGER) {
			valList.push_back(Value(static_cast<Number>(::sqlite3_column_int(_pStmt, iCol))));
		} else if (type == SQLITE_FLOAT) {
			valList.push_back(Value(static_cast<Number>(::sqlite3_column_double(_pStmt, iCol))));
		} else if (type == SQLITE_TEXT) {
			valList.push_back(Value(*_pObj,
				reinterpret_cast<const char *>(::sqlite3_column_text(_pStmt, iCol))));
		} else if (type == SQLITE_BLOB) {
			//::sqlite3_column_blob(_pStmt, iCol);
			valList.push_back(Value::Null);
		} else if (type == SQLITE_NULL) {
			valList.push_back(Value::Null);
		} else {
			sig.SetError(ERR_RuntimeError, "somthing's wrong in SQLite3");
			return false;
		}
	}
	return true;
}

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

//-----------------------------------------------------------------------------
// AScript interfaces for Object_SQLite3
//-----------------------------------------------------------------------------
// sqlite3.sqlite3#exec(sql:string):map
AScript_DeclareMethod(SQLite3, exec)
{
	SetMode(RSLTMODE_Normal, FLAG_Map);
	DeclareArg(env, "sql", VTYPE_String);
	SetHelp(
	"Executes an SQL statement and returns the result as a list.");
}

AScript_ImplementMethod(SQLite3, exec)
{
	Object_SQLite3 *pObj = Object_SQLite3::GetSelfObj(args);
	return pObj->Exec(sig, args.GetString(0), args);
}

// sqlite3.sqlite3#query(sql:string):map {block?}
AScript_DeclareMethod(SQLite3, query)
{
	SetMode(RSLTMODE_Normal, FLAG_Map);
	DeclareArg(env, "sql", VTYPE_String);
	DeclareBlock(OCCUR_ZeroOrOnce);
	SetHelp(
	"Executes an SQL statement and returns the result as an iterator.\n"
	"You should use query() instead of exec() when it's likely that you get a large\n"
	"size of data as the result\n");
}

AScript_ImplementMethod(SQLite3, query)
{
	Object_SQLite3 *pObj = Object_SQLite3::GetSelfObj(args);
	Iterator *pIterator = pObj->Query(sig, args.GetString(0));
	if (sig.IsSignalled()) return Value::Null;
	return ReturnIterator(env, sig, args, pIterator);
}

// sqlite3.sqlite3#getcolnames(sql:string):map
AScript_DeclareMethod(SQLite3, getcolnames)
{
	SetMode(RSLTMODE_Normal, FLAG_Map);
	DeclareArg(env, "sql", VTYPE_String);
	DeclareBlock(OCCUR_ZeroOrOnce);
}

AScript_ImplementMethod(SQLite3, getcolnames)
{
	Object_SQLite3 *pObj = Object_SQLite3::GetSelfObj(args);
	return pObj->GetColumnNames(sig, args.GetString(0));
}

// sqlite3.sqlite3#transaction() {block}
AScript_DeclareMethod(SQLite3, transaction)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	DeclareBlock(OCCUR_Once);
	SetHelp(
	"Executes the block within a transaction. The process is like follows:\n"
	"1.Executes a sqlit3 command 'BEGIN TRANSACTION'\n"
	"2.Executes code in the block\n"
	"3.Executes a sqlite3 command 'END TRANSACTION'");
}

AScript_ImplementMethod(SQLite3, transaction)
{
	Object_SQLite3 *pObj = Object_SQLite3::GetSelfObj(args);
	const Function *pFuncBlock =
						args.GetBlockFunc(env, sig, GetSymbolForBlock());
	if (sig.IsSignalled()) return Value::Null;
	if (!pObj->ExecNoResult(sig, "BEGIN TRANSACTION")) return Value::Null;
	Environment envBlock(&env, ENVTYPE_Block);
	Args argsSub(ValueList::Null);
	Value result = pFuncBlock->Eval(envBlock, sig, argsSub);
	// "END TRANSACTION" has the same effect as "COMMIT"
	if (!pObj->ExecNoResult(sig, "END TRANSACTION")) return Value::Null;
	return result;
}

// sqlite3.sqlite3#close()
AScript_DeclareMethod(SQLite3, close)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	SetHelp("Shuts down the connection with an SQLite3 server.");
}

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

// assignment
AScript_ImplementPrivClass(SQLite3)
{
	AScript_AssignMethod(SQLite3, exec);
	AScript_AssignMethod(SQLite3, query);
	AScript_AssignMethod(SQLite3, getcolnames);
	AScript_AssignMethod(SQLite3, transaction);
	AScript_AssignMethod(SQLite3, close);
}

//-----------------------------------------------------------------------------
// Module functions: sqlite3
//-----------------------------------------------------------------------------
// db = sqlite3.open(filename:string) {block?}
AScript_DeclareFunction(open)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	DeclareArg(env, "filename", VTYPE_String);
	DeclareBlock(OCCUR_ZeroOrOnce);
	SetHelp(
	"Opens an SQLite3 database file.\n"
	"If block is not specified, it returns a connection handle with an SQLite3 server.\n"
	"If block is specified, it executes the program in the block with a connection\n"
	"handle as a block parameter, and returns the result afterwards. The connection\n"
	"handle will automatically closed when the block finishes\n"
	"Block parameter format: |sql:sqlite3|");
}

AScript_ImplementFunction(open)
{
	Object_SQLite3 *pObj = new Object_SQLite3(env);
	if (!pObj->Open(sig, args.GetString(0))) {
		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;
}

// Module entry
AScript_ModuleEntry()
{
	// class realization
	AScript_RealizePrivClass(SQLite3, "sqlite3", env.LookupClass(VTYPE_Object));
	// function assignment
	AScript_AssignFunction(open);
}

AScript_ModuleTerminate()
{
}

AScript_EndModule(sqlite3, sqlite3)

AScript_RegisterModule(sqlite3)
