/*
 * LS_DB_Sqlite.cpp
 *
 */
#ifdef NOT_X86

#include "EH_Exception.h"
#include "EH_DB_Sqlite.h"
#include "EH_DEBUG.h"
#include <unistd.h>

#include "ace/Process_Mutex.h"
#include "ace/Thread_Mutex.h"

#define	DB_PATHNAME					"/etc/dropbear/accessory/hj_db/kk_db.db"
#define	DB_BEGIN_TRANSACTION		"BEGIN TRANSACTION;"
#define	DB_COMMIT_TRANSACTION		"COMMIT;"
#define	DB_ROLLBACK_TRANSACTION		"ROLLBACK;"

//ACE_Process_Mutex  gProcessSqliteLock("sds_sqlite_lk");
ACE_Thread_Mutex   gThreadSqliteLock;

static int execSql_callback(void *p_otherData, int argc, char **argv, char **azColName);

EH_DB::EH_DB(string username, string passwd, string addr, string port, string dbname) {
    m_pConn = NULL;
    m_iTransactionFlag = 0;
}

/*
 * If already have a connection,don't get a new connection.
 * if success return 0, else are other values.
 */
int EH_DB::OpenConnection() {
	int i_errCode = SQLITE_OK;

#if 0
	sqlite3 *p_db = NULL;

    gProcessSqliteLock.acquire();
    if (m_pConn == NULL) {
        i_errCode = sqlite3_open(DB_PATHNAME, &p_db);
        if ( i_errCode == SQLITE_OK ) {
        	m_pConn = p_db;
        } else {
        	EH_WARNING("open db failed.\n");
        	sqlite3_close(p_db);
        }
    }
    gProcessSqliteLock.release();
#endif

    return i_errCode;
}

int EH_DB::ResetConnection() {
	int i_errCode = SQLITE_OK;

#if 0
    CloseConnection();
    i_errCode = OpenConnection();
#endif

    return i_errCode;
}

void EH_DB::CloseConnection() {
#if 0
	gProcessSqliteLock.acquire();
    if (m_pConn) {
        sqlite3_close((sqlite3 *)m_pConn);
        m_pConn = NULL;
    }
    m_iTransactionFlag = 0;
    gProcessSqliteLock.release();
#endif
}

int EH_DB::StartTransaction() {
	int i_errCode = SQLITE_ERROR;
	int i_retryCount = 0, i_retFlag = 0;
	char *p_errMsg = NULL;
	string str_errMsg;

	//EH_DEBUG("StartTransaction try get lock...\n");
	//gProcessSqliteLock.acquire();
	gThreadSqliteLock.acquire();
	//EH_DEBUG("StartTransaction try get lock ok.\n");
	if ( !m_pConn ) {
		sqlite3 *p_db = NULL;

		i_errCode = sqlite3_open(DB_PATHNAME, &p_db);
		if ( i_errCode == SQLITE_OK ) {
			m_pConn = p_db;

			sqlite3_exec((sqlite3*) m_pConn,"ATTACH DATABASE '/tmp/zigbee_dev_status.db' as 'zds_in_ramdisk'",
					                                                                           0,0,&p_errMsg);
		} else {
			EH_WARNING("open db failed.\n");
			sqlite3_close(p_db);
		}
	}

	if ( !m_iTransactionFlag && m_pConn) {
		while (1) {
			i_errCode = sqlite3_exec((sqlite3*) m_pConn, DB_BEGIN_TRANSACTION,
					                                   NULL, NULL, &p_errMsg);

			switch (i_errCode) {
			case SQLITE_OK: {
				m_iTransactionFlag = 1;
				m_iErrCode = 0;
				i_retFlag = 1;
				break;
			}

			case SQLITE_BUSY:
			case SQLITE_LOCKED:
				if (i_retryCount < 10) {
					i_retryCount++;
					i_retFlag = 0;
					if (p_errMsg) {
						sqlite3_free(p_errMsg);
						p_errMsg = NULL;
					}

					usleep(100 * 1000);
					EH_WARNING("do:%s SQLITE_BUSY or SQLITE_LOCKED,try %d.\n",
							    DB_BEGIN_TRANSACTION, i_retryCount);
					break;
				} else {/*do default case;*/
					EH_WARNING("do:%s SQLITE_BUSY or SQLITE_LOCKED.\n",
							    DB_BEGIN_TRANSACTION);
				}

			default: {
				i_retFlag = 1;
				if (p_errMsg) {
					str_errMsg = p_errMsg;
					str_errMsg.append(" database EXCEPTION!");
					sqlite3_free(p_errMsg);
					p_errMsg = NULL;
				} else {
					str_errMsg = "EXCEPTION: SQL error or missing database";
				}
				break;
			}
			}

			if (i_retFlag)
				break;
		}

		//EH_DEBUG("StartTransaction with error code:%d.conn:%d.total try:%d.\n",i_errCode,m_pConn,
		//		                                                                   i_retryCount);
		if (m_iErrCode != SQLITE_OK) {
			EH_WARNING("StartTransaction with error code:%d.conn:%d.total try:%d.\n",i_errCode,m_pConn,
					                                                                     i_retryCount);
		}
	} else {
		EH_WARNING("StartTransaction transaction in use or no conn.trans flag:%d conn:%d.\n",
				                                                m_iTransactionFlag,m_pConn);
	}

	return i_errCode;
}

int EH_DB::CommitTransaction() {
	int i_errCode = SQLITE_ERROR;
	int i_retryCount = 0, i_retFlag = 0;
	char *p_errMsg = NULL;
	string str_errMsg;

	if ( m_iTransactionFlag && m_pConn ) {
		m_iTransactionFlag = 0;

		while (1) {
			i_errCode = sqlite3_exec((sqlite3*)m_pConn, m_iErrCode ? DB_ROLLBACK_TRANSACTION : DB_COMMIT_TRANSACTION,
							                                                                  NULL, NULL, &p_errMsg);
			switch (i_errCode) {
			case SQLITE_OK: {
				i_retFlag = 1;
				break;
			}

			case SQLITE_BUSY:
			case SQLITE_LOCKED:
				if (i_retryCount < 10) {
					i_retryCount++;
					i_retFlag = 0;
					if (p_errMsg) {
						sqlite3_free(p_errMsg);
						p_errMsg = NULL;
					}

					usleep(100 * 1000);
					EH_WARNING("do:%s SQLITE_BUSY or SQLITE_LOCKED,try %d.\n",
							   m_iErrCode ? DB_ROLLBACK_TRANSACTION : DB_COMMIT_TRANSACTION, i_retryCount);
					break;
				} else {/*do default case;*/
					EH_WARNING("do:%s SQLITE_BUSY or SQLITE_LOCKED.\n",
							   m_iErrCode ? DB_ROLLBACK_TRANSACTION : DB_COMMIT_TRANSACTION);
				}

			default: {
				i_retFlag = 1;
				if (p_errMsg) {
					str_errMsg = p_errMsg;
					str_errMsg.append(" database EXCEPTION!");
					sqlite3_free(p_errMsg);
					p_errMsg = NULL;
				} else {
					str_errMsg = "EXCEPTION: SQL error or missing database";
				}
				break;
			}
			}

			if (i_retFlag)
				break;
		}

		if (m_iErrCode != SQLITE_OK) {
			EH_WARNING("CommitTransaction with error code:%d.conn:%d.trans code:%d.\n",i_errCode, m_pConn, m_iErrCode);
		}
	} else {
		EH_WARNING("CommitTransaction no transaction or no conn.trans flag:%d conn:%d.\n",
						                                      m_iTransactionFlag,m_pConn);
	}

	if ( m_pConn) {
		// close the db. it will open when invoke StartTransaction
		sqlite3_close((sqlite3 *)m_pConn);
		m_pConn = NULL;
	}
	m_iTransactionFlag = 0;
	m_iErrCode = 0;

	//EH_DEBUG("CommitTransaction try release lock...\n");
	gThreadSqliteLock.release();
	//gProcessSqliteLock.release();
	//EH_DEBUG("CommitTransaction release lock ok.\n");
	return i_errCode;
}

int EH_DB::AbortTransaction() {
	EH_WARNING("Abort transaction try do commit.\n");
	m_iErrCode = 1;
	return CommitTransaction();
}

/*
 * must be called when exception occur when in executing a transaction.
 */
int EH_DB::ExceptionInTransaction() {
	EH_WARNING("Exception in transaction try do commit.\n");
	m_iErrCode = 1;
	return CommitTransaction();
}

DBRES EH_DB::ExecuteQuery(string sql) {
	int i_errCode = SQLITE_ERROR;
	int i_retryCount = 0, i_retFlag = 0;;
	char *p_errMsg = NULL;
	string str_errMsg;

	if (m_pConn == NULL) {
		throw EH_EXCEPTION("ExecuteQuery EXCEPTION:connection instance is NULL");
	}
	if ( !m_iTransactionFlag ) {
		throw EH_EXCEPTION("ExecuteQuery EXCEPTION:transaction is not start");
	}

	//EH_WARNING("ExecuteQuery,in sql:%s.\n",sql.c_str());
	DBRES result = new DBRESVEC();
	while( 1 ) {
		i_errCode = sqlite3_exec((sqlite3*)m_pConn, sql.c_str(), execSql_callback, (void*)result, &p_errMsg);

		switch (i_errCode) {
		case SQLITE_OK:
			i_retFlag = 1;
			break;

		case SQLITE_BUSY:
		case SQLITE_LOCKED:
			if ( i_retryCount < 10 ) {
				i_retryCount++;
				i_retFlag = 0;
				if ( p_errMsg ) {
					sqlite3_free(p_errMsg);
					p_errMsg = NULL;
				}

				usleep(100*1000);
				EH_WARNING("do:%s SQLITE_BUSY or SQLITE_LOCKED,try:%d.\n",sql.c_str(),i_retryCount);
				break;
			} else {/*do default case;*/
				EH_WARNING("do:%s SQLITE_BUSY or SQLITE_LOCKED.\n",sql.c_str());
			}

		default:
			i_retFlag = 1;
			m_iErrCode = 1;
			if ( p_errMsg ) {
				str_errMsg = p_errMsg;
				str_errMsg.append(" database EXCEPTION!");
				sqlite3_free(p_errMsg);
				p_errMsg = NULL;
			} else {
				str_errMsg = "EXCEPTION: SQL error or missing database";
			}
			break;
		}

		if ( i_retFlag )
			break;
	}

	if ( m_iErrCode ) {
		EH_WARNING("ExecuteQuery:%s failed.error info:%s.\n",sql.c_str(),str_errMsg.c_str());
		string exceptionString = "ExecuteQuery:"+sql+"failed.error info:"+str_errMsg;
		throw EH_EXCEPTION(exceptionString.c_str());
	}
	//EH_WARNING("ExecuteQuery,out sql:%s.\n",sql.c_str());

    return result;
}

void EH_DB::ExecuteUpdate(string sql) {
	int i_errCode = SQLITE_ERROR;
	int i_retryCount = 0, i_retFlag = 0;;
	char *p_errMsg = NULL;
	string str_errMsg;

	if (m_pConn == NULL) {
		throw EH_EXCEPTION("ExecuteUpdate EXCEPTION:connection instance is NULL");
	}
	if ( !m_iTransactionFlag ) {
		throw EH_EXCEPTION("ExecuteUpdate EXCEPTION:transaction is not start");
	}

	//EH_WARNING("ExecuteUpdate,in sql:%s.\n",sql.c_str());
	while( 1 ) {
		i_errCode = sqlite3_exec((sqlite3*)m_pConn, sql.c_str(), NULL, (void*)NULL, &p_errMsg);

		switch (i_errCode) {
		case SQLITE_OK:
			i_retFlag = 1;
			break;

		case SQLITE_BUSY:
		case SQLITE_LOCKED:
			if ( i_retryCount < 10 ) {
				i_retryCount++;
				i_retFlag = 0;
				if ( p_errMsg ) {
					sqlite3_free(p_errMsg);
					p_errMsg = NULL;
				}

				usleep(100*1000);
				EH_WARNING("do:%s SQLITE_BUSY or SQLITE_LOCKED,try:%d.\n",sql.c_str(),i_retryCount);
				break;
			} else {//do default case;
				EH_WARNING("do:%s SQLITE_BUSY or SQLITE_LOCKED.\n",sql.c_str());
			}

		default:
			i_retFlag = 1;
			m_iErrCode = 1;
			if ( p_errMsg ) {
				str_errMsg = p_errMsg;
				str_errMsg.append(" database EXCEPTION!");
				sqlite3_free(p_errMsg);
				p_errMsg = NULL;
			} else {
				str_errMsg = "EXCEPTION: SQL error or missing database";
			}
			break;
		}

		if ( i_retFlag )
			break;
	}

	if ( m_iErrCode ) {
		EH_WARNING("ExecuteUpdate:%s failed.error info:%s.\n",sql.c_str(),str_errMsg.c_str());
		string exceptionString = "ExecuteUpdate:"+sql+"failed.error info:"+str_errMsg;
		throw EH_EXCEPTION(exceptionString.c_str());
	}

	//EH_WARNING("ExecuteUpdate,out sql:%s.\n",sql.c_str());
}

void EH_DB::ReleaseResultSet(DBRES& res) {
    if (!res) {
        return;
    }

    DBRESVEC::const_iterator it = res->begin();
    while (it != res->end()) {
    	DBROW row = *it;
        if (row) {
            delete row;
            row = NULL;
        }
        ++it;
    }
    delete res;
    res = NULL;
}

static int execSql_callback(void *p_otherData, int argc, char **argv, char **azColName) {
	if ( !p_otherData )
		return 0;

	DBRES result = (DBRES)p_otherData;
	DBROW row = new DBROWMAP();
	for (int i = 0; i < argc; i++) {
		string colName = azColName[i] ? azColName[i] : "";
		string vlaue = argv[i] ? argv[i] : "";
		row->insert(DBROWMAP::value_type(colName, vlaue));
	}
	result->push_back(DBRESVEC::value_type(row));

	return 0;
}

EH_DB::~EH_DB() {
    CloseConnection();
}

#endif
