/***************************************************************************
                          SIMPLY POWERFUL TOOLKIT (SPTK)
                          CSQLite3Database.cpp  -  description
                             -------------------
    begin                : Fri Oct 28 2005
    copyright            : (C) 2005-2006 by Alexey Parshin
    email                : alexeyp@gmail.com
 ***************************************************************************/

/***************************************************************************
   This library is free software; you can redistribute it and/or modify it
   under the terms of the GNU Library General Public License as published by
   the Free Software Foundation; either version 2 of the License, or (at
   your option) any later version.

   This library is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library
   General Public License for more details.

   You should have received a copy of the GNU Library General Public License
   along with this library; if not, write to the Free Software Foundation,
   Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.

   Please report all bugs and problems to "alexeyp@gmail.com"
 ***************************************************************************/

#include <sptk3/sptk.h>
#if HAVE_SQLITE3
#include <sqlite3.h>
#include <sptk3/CSQLite3Database.h>
#include <sptk3/CDatabaseField.h>
#include <sptk3/CQuery.h>
#include <sptk3/CParams.h>
#include <string>
#include <stdio.h>

namespace sptk {

	class CSQLite3Field : public CDatabaseField {
		friend class CSQLite3Database;
	public:
		CSQLite3Field(const std::string fieldName,int fieldColumn,int fieldType,CVariantType dataType,int fieldLength,int fieldScale)
		: CDatabaseField(fieldName,fieldColumn,fieldType,dataType,fieldLength,fieldScale) {}
	};

}

using namespace std;
using namespace sptk;

CSQLite3Database::CSQLite3Database(string connectionString)
: CDatabase(connectionString) {
	m_connect = 0;
}

CSQLite3Database::~CSQLite3Database() {
	try {
		if (m_inTransaction && active())
			rollbackTransaction();
		close();
		while (m_queryList.size()) {
			try {
				CQuery *query = (CQuery *)m_queryList[0];
				query->disconnect();
			}
			catch (...) {
			}
		}
		m_queryList.clear();
	}
	catch (...) {
	}
}

void CSQLite3Database::openDatabase(const string newConnectionString) throw(CException) {
	if (!active()) {
		m_inTransaction = false;
		if (newConnectionString.length())
			m_connString = newConnectionString;
		if (sqlite3_open(m_connString.c_str(), &m_connect) != 0) {
			string error = sqlite3_errmsg(m_connect);
			sqlite3_close(m_connect);
			m_connect = 0;
			throw CException(error);
		}
	}
}

void CSQLite3Database::closeDatabase() throw(CException) {
	for (unsigned i = 0; i < m_queryList.size(); i++) {
		try {
			CQuery *query = (CQuery *)m_queryList[i];
			queryFreeStmt(query);
		}
		catch (...) {}
	}
	sqlite3_close(m_connect);
	m_connect = 0L;
}

unsigned long CSQLite3Database::handle() const {
	return (unsigned long)m_connect;
}

bool CSQLite3Database::active() const {
	return m_connect != 0L;
}

void CSQLite3Database::driverBeginTransaction() throw(CException) {
	if (!m_connect) open();

	if (m_inTransaction)
		throw CException("Transaction already started.");

	char *zErrMsg;
	if (sqlite3_exec(m_connect,"BEGIN TRANSACTION", 0, 0, &zErrMsg) != SQLITE_OK)
		throw CException(zErrMsg);

	m_inTransaction = true;
}

void CSQLite3Database::driverEndTransaction(bool commit) throw(CException) {
	if (!m_inTransaction)
		throw CException("Transaction isn't started.");

	string action;
	if (commit)
		action = "COMMIT";
	else  action = "ROLLBACK";

	char *zErrMsg;
	if (sqlite3_exec(m_connect,action.c_str(), 0, 0, &zErrMsg) != SQLITE_OK)
		throw CException(zErrMsg);

	m_inTransaction = false;
}

//-----------------------------------------------------------------------------------------------
static inline bool successful(int ret) {
	return ret==SQLITE_OK;
}

string CSQLite3Database::queryError(const CQuery *query) const {
	return sqlite3_errmsg(m_connect);;
}

// Doesn't actually allocate stmt, but makes sure
// the previously allocated stmt is released
void CSQLite3Database::queryAllocStmt(CQuery *query) {
	CSQLite3Lock lock(this);

	SQLHSTMT stmt = (SQLHSTMT)query->statement();
	if (stmt) sqlite3_finalize(stmt);
	querySetStmt(query,0L);
}

void CSQLite3Database::queryFreeStmt(CQuery *query) {
	CSQLite3Lock lock(this);

	SQLHSTMT stmt = (SQLHSTMT)query->statement();
	if (stmt) sqlite3_finalize(stmt);
	querySetStmt(query,0L);
	querySetPrepared(query,false);
}

void CSQLite3Database::queryCloseStmt(CQuery *query) {
	CSQLite3Lock lock(this);

   //sqlite3_reset((SQLHSTMT)query->statement());
	SQLHSTMT stmt = (SQLHSTMT)query->statement();
	if (stmt) sqlite3_finalize(stmt);
	querySetStmt(query,0L);
	querySetPrepared(query,false);
}

void CSQLite3Database::queryPrepare(CQuery *query) {
	CSQLite3Lock lock(this);

	SQLHSTMT stmt;
	const char *pzTail;
	if (sqlite3_prepare(m_connect,query->sql().c_str(),
			query->sql().length(),&stmt,&pzTail) != SQLITE_OK)
	{
		const char *errorMsg = sqlite3_errmsg(m_connect);
		throw CException(errorMsg,__FILE__,__LINE__,query->sql().c_str());
	}

   //sqlite3_clear_bindings(stmt);

	querySetStmt(query,stmt);
	querySetPrepared(query,true);
}

void CSQLite3Database::queryUnprepare(CQuery *query) {
	queryFreeStmt(query);
}

void CSQLite3Database::queryExecute(CQuery *query) {
	CSQLite3Lock lock(this);

	if (!query->prepared())
		throw CException("Query isn't prepared");
}

int CSQLite3Database::queryColCount(CQuery *query) {
	CSQLite3Lock lock(this);

	SQLHSTMT stmt = (SQLHSTMT)query->statement();

	short count = sqlite3_column_count(stmt);

	return count;
}

void CSQLite3Database::queryBindParameters(CQuery *query) {
	CSQLite3Lock lock(this);

	SQLHSTMT stmt = (SQLHSTMT)query->statement();

	for (unsigned i = 0; i < query->paramCount(); i++) {
		CParam *param = &query->param(i);
		CVariantType ptype = param->dataType();
      //SQLINTEGER& cblen = param->callbackLength();
		for (unsigned j = 0; j < param->bindCount(); j++) {

			int   rc = -1;
			short paramNumber = short(param->bindIndex(j) + 1);

			if (param->isNull())
				rc = sqlite3_bind_null(stmt,paramNumber);
			else
				switch (ptype) {
				case VAR_INT:
					rc = sqlite3_bind_int(stmt,paramNumber,param->getInteger());
					break;
				case VAR_FLOAT:
					rc = sqlite3_bind_double(stmt,paramNumber,param->getFloat());
					break;
				case VAR_STRING:
				case VAR_TEXT:
					rc = sqlite3_bind_text(stmt,paramNumber,param->getString(),param->dataSize(),SQLITE_STATIC);
					break;
				case VAR_BUFFER:
					rc = sqlite3_bind_blob(stmt,paramNumber,param->getString(),param->dataSize(),SQLITE_STATIC);
					break;
				case VAR_DATE:
				case VAR_DATE_TIME:
					throwException("Date and time types isn't yet supported for SQLite3");
               /*
               {
                  paramType = SQL_C_TIMESTAMP;
                  sqlType   = SQL_TIMESTAMP;
                  len = sizeof(TIMESTAMP_STRUCT);
                  TIMESTAMP_STRUCT *t = (TIMESTAMP_STRUCT *)param->conversionBuffer();
                  CDateTime dt = param->getDateTime();
                  buff = t;
                  if (dt) {
                     dt.decodeDate((short *)&t->year,(short *)&t->month,(short *)&t->day);
                     t->hour = t->minute = t->second = 0;
                     t->fraction = 0;
                  } else {
                     paramType = SQL_C_CHAR;
                     sqlType   = SQL_CHAR;
                     *(char *)buff = 0;
                  }
               }
               */
					break;
				default:
					throw CException("Unsupported type of parameter "+int2string(paramNumber),__FILE__,__LINE__,query->sql().c_str());
			}
			if (rc != SQLITE_OK) {
				string error = sqlite3_errmsg(m_connect);
				throw CException(error + ", in binding parameter "+int2string(paramNumber),__FILE__,__LINE__,query->sql().c_str());
			}
		}
	}
}

void CSQLite3Database::SQLITEtypeToCType(int sqliteType,CVariantType& dataType) {
	switch(sqliteType) {
		case SQLITE_INTEGER:
			dataType = VAR_INT;
			return;

		case SQLITE_FLOAT:
			dataType = VAR_FLOAT;
			return;

		case SQLITE_TEXT:
			dataType = VAR_STRING;
			return;
         /*
      case SQL_DATE:       // ODBC 2.0 only
      case SQL_TYPE_DATE:  // ODBC 3.0 only
         cType = SQL_C_TIMESTAMP;
         dataType = VAR_DATE;
         return;

      case SQL_TIME:
      case SQL_TIMESTAMP:
      case SQL_TYPE_TIME:
      case SQL_TYPE_TIMESTAMP:
         if (odbcType == SQL_TYPE_DATE) {
            cType = SQL_C_TIMESTAMP;
            dataType = VAR_DATE;
         } else {
            cType = SQL_C_TIMESTAMP;
            dataType = VAR_DATE_TIME;
         }
         return;
         */

		case SQLITE_BLOB:
			dataType = VAR_BUFFER;
			return;
	}
	dataType = VAR_NONE;
}

void CSQLite3Database::queryOpen(CQuery *query) {
	if (!active()) open();

	if (query->active()) return;

	if (active() && !query->statement())
		queryAllocStmt(query);

	if (!query->prepared()) queryPrepare(query);

	queryBindParameters(query);
	queryExecute(query);

	short count = queryColCount(query);

	query->fields().clear();

	SQLHSTMT stmt = (SQLHSTMT)query->statement();

	if (count < 1) {
		if (sqlite3_step(stmt) != SQLITE_DONE) {
			string error = queryError(query);
			queryCloseStmt(query);
			throw CException(error,__FILE__,__LINE__,query->sql().c_str());
		}
		queryCloseStmt(query);
		return;
	} else {
		querySetActive(query,true);

      // Reading the column attributes
		char  columnName[256];
		long  columnType;
		CVariantType dataType;
		for (short column = 1; column <= count; column++) {
			strncpy(columnName,sqlite3_column_name(stmt,column-1),255); columnName[255] = 0;
			columnType = sqlite3_column_type(stmt,column-1);
			SQLITEtypeToCType(columnType,dataType);
			if (columnName[0] == 0)
				sprintf(columnName,"column%02i",column);

			CSQLite3Field *field = new CSQLite3Field(
					columnName,
					column,
					columnType,
					dataType,0,0);
			query->fields().push_back(field);
		}
	}

	querySetEof(query,false);

	queryFetch(query);
}

static unsigned trimField(char *s,int sz) {
	register char *p = s + sz;
	char ch = s[0];
	s[0] = '!';
	while ( *(--p) == ' ' ) {
	}
	*(++p) = 0;
	if (ch == ' ' && s[1] == 0)
		return 0;
	s[0] = ch;
	return p - s;
}

void CSQLite3Database::queryFetch(CQuery *query) {
	if (!query->active())
		throw CException("Dataset isn't open",__FILE__,__LINE__,query->sql().c_str());

	SQLHSTMT    statement = (SQLHSTMT) query->statement();

	CSQLite3Lock lock(this);

	int rc = sqlite3_step(statement);

	switch (rc) {
		case SQLITE_DONE:
			querySetEof(query,true);
			return;
		case SQLITE_ROW:
			break;

		default:
			throw CException(queryError(query),__FILE__,__LINE__,query->sql().c_str());
	}

	unsigned fieldCount = query->fieldCount();
	unsigned dataLength = 0;

	if (!fieldCount) return;

	CSQLite3Field  *field = 0;
	for (unsigned column = 0; column < fieldCount; column++) {
		try {
			field = (CSQLite3Field *) & (*query)[column];
			short fieldType = (short) field->fieldType();
			if (!fieldType)
				fieldType = sqlite3_column_type(statement,column);

			dataLength = sqlite3_column_bytes(statement,column);

			if (dataLength) {
				switch (fieldType) {

					case SQLITE_INTEGER:
						field->setInteger(sqlite3_column_int64(statement,column));
						break;

					case SQLITE_FLOAT:
						field->setFloat(sqlite3_column_double(statement,column));
						break;

					case SQLITE_TEXT:
						field->setString((const char *)sqlite3_column_text(statement,column),dataLength);
						dataLength = trimField((char *)field->getString(),dataLength);
						break;

					case SQLITE_BLOB:
						field->setBuffer(sqlite3_column_blob(statement,column),dataLength);
						break;

					default:
						dataLength = 0;
						break;
				}

				field->dataSize(dataLength);

			} else {
				field->setString("");
				field->setNull();
			}
		}
		catch (exception& e) {
			throw CException("Can't read field "+field->fieldName()+"\n"+string(e.what()),__FILE__,__LINE__,query->sql().c_str());
		}
	}
}

#endif
