#include "StorageCorrectThread.h"
#include "Logger.h"
#include "ProcessConfig.h"

#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <sqlite3.h>



#define CORRECT_DELAY_TIME             40   // 단위 Min. (5분단위 값) 
                                            // 현재 시간 기준으로 지정된 값만큼 이전의 Storage 통계가 존재하는지 확인하여 없을 경우 보정 처리 수행.

#define SQLITE_DB_LOCK_TIMEOUT       3000   // 세션 중에 SQLITE_BUSY 상황 발생 문제 해결을 위해 DB Lock 관련 timeout 설정값. ( 3 sec )


// 생성자..
CStorageCorrectThread::CStorageCorrectThread()
{
	// Thread Handle 초기화
	m_threadHandle = 0;
}

// 소멸자..
CStorageCorrectThread::~CStorageCorrectThread()
{
	// Thread 가 동작 중인 경우 Thread 동작 정지 처리.
	if( m_threadHandle != 0 )
		pthread_cancel(m_threadHandle); 

}


// Thread 에 정의된 작업을 시작한다...
bool CStorageCorrectThread::Start()
{
	int nResult = pthread_create( &m_threadHandle, NULL, CStorageCorrectThread::threadFunc, this );
	if( nResult != 0 )
	{
		// Thread 생성 실패시...
		int errorNum = errno;
		LOG( LERR, "StorageCorrectThread: Thread create failed.[%d][%s]", errorNum, strerror(errorNum) );

		return false;
	}

	return true;
}


// Thread 함수...
void * CStorageCorrectThread::threadFunc( void * arg )
{
	CStorageCorrectThread * pObject = reinterpret_cast<CStorageCorrectThread *>(arg);
	pthread_detach( pthread_self() );

	// 본 보정 작업은 아주 급한 보정 작업이 아니므로.... 천천히 수행시킨다.
	sleep( 60 );

	pthread_testcancel();
	pObject->Execute();
	pthread_testcancel();
	
	// Thread 종료시 Handle 초기화
	pObject->m_threadHandle = 0;

	return NULL;
}

// timestamp 가 변경될 때마다... 
// 지정된 예전 timestamp 에 해당하는 통계가 존재하는지 확인한 후....
// 존재하지 않을 경우... 그 이전 timestamp 값을 예전 timestamp 값에 저장하여 보정 처리한다.
void CStorageCorrectThread::Execute()
{

	// 변수 선언 및 초기화..
	time_t currentTime;           // 현재 시간 정보를 저장하기 위한 변수..
	
	unsigned int nowTimestamp;            // 현재 시간의 timestamp 정보를 저장하기 위한 변수.
	unsigned int lastCheckTimestamp = 0;  // 마지막 처리한 timestamp 값을 저장하기 위한 변수.


	// Local DB 관련 변수 초기화.
	sqlite3      * db   = NULL;
	sqlite3_stmt * stmt = NULL;

	int db_result;
	int nRetryCount;

	char szQuery[2048];
	char * db_err;

	const char * select_sql = "SELECT count(*) FROM storage_stat WHERE timestamp = ?" ;


	// Logging
	_LOG( LINF, "StorageCorrectThread: start...");


	// 주기적으로 Storage 통계 정보가 빠져있는지 확인하여... 보정 처리를 수행한다.
	while( 1 )
	{

		// 1. 현재 시간 정보를 추출하여 5분단위 timesamp 를 계산

		// timesatmp 변경시마다 수행할 경우 다른 모듈과 sqlite 경쟁으로 인해 SQLITE_BUSY 상황 발생 (#21440)
		// 이를 회피하기 위해 150 sec delay 처리함.		
		currentTime = time(0);
		nowTimestamp = ((currentTime - 150 + 299)/300);


		// 2. timestamp 가 변경되지 않은 경우..
		if( nowTimestamp == lastCheckTimestamp )
		{
			sleep(15);
			continue;
		}
		else
		{
			// 만약 시간 동기화 이상으로 이전에 계산된 timestamp 값이 잘못된 경우....  보정처리..
			if( nowTimestamp < lastCheckTimestamp )
			{
				LOG( LWAR, "StorageCorrectThread: Last timestamp not valid. skip and reset. now[%u] last[%u]", nowTimestamp, lastCheckTimestamp );
				lastCheckTimestamp = 0;
				
				sleep(60);
				continue;
			}

			// 3. timestamp 가 정상적으로 변경된 경우..
			//    - 보정 작업을 진행할 timestamp 값을 확인한다.
			unsigned int correctTimestamp = nowTimestamp - (CORRECT_DELAY_TIME / 5);

			_LOG( LINF, "StorageCorrectThread: correct job start. timestamp now[%u] correct[%u].", nowTimestamp, correctTimestamp );


			// 4. Local DB 와의 연결을 수행한다.			
			db_result = sqlite3_open( CProcessConfig::GetInstance()->GetLocalDbFileName(), &db);
			if( db_result != SQLITE_OK )
			{
				LOG( LERR, "StorageCorrectThread: sqlite3 open failed.[%s][%d][%s]", CProcessConfig::GetInstance()->GetLocalDbFileName(), db_result, sqlite3_errmsg(db));
				sqlite3_close(db);
				db = NULL;
			}
			else
			{
				nRetryCount = 0;


				// 보정 대상 Timestamp 에 대한 Storage 서비스 정보가 존재하는지 확인한다.
				// bind 관련 Query 를 수행할 stmt 객체 생성.
				db_result = sqlite3_prepare(db, select_sql, strlen(select_sql), &stmt, NULL );

				// sqlite3_prepare() 함수 실행시 SQLITE_BUSY 오류가 발생할 수 있으므로.. 재시도 로직을 추가한다.
				while( db_result == SQLITE_BUSY  && nRetryCount < 20 )
				{
					// sqlite 에서 SQLITE_BUSY 반환 관련 lock 대기 timeout 을 설정.
					sqlite3_busy_timeout( db, SQLITE_DB_LOCK_TIMEOUT );
					++nRetryCount;

					LOG( LWAR, "StorageCorrectThread: sqlite3 prepare SQLITE_BUSY, Retry[%d]", nRetryCount );

					db_result = sqlite3_prepare(db, select_sql, strlen(select_sql), &stmt, NULL );	
				}

				if( db_result != SQLITE_OK )
				{
					LOG( LERR, "StorageCorrectThread: sqlite3 prepare failed.[%d][%s]", db_result, sqlite3_errmsg(db));
					sqlite3_close(db);
					db = NULL;
					stmt = NULL;
				}
				else
				{
					// SELECT 함수에 대한 매개변수 bind 처리.
					sqlite3_bind_int  ( stmt, 1, correctTimestamp );

					// select query 수행.
					db_result = sqlite3_step(stmt);
					nRetryCount = 0;


					// 만약 BUSY 상태로 인해 오류가 발생하면..20번 재시도
					while( db_result == SQLITE_BUSY  && nRetryCount < 20 )
					{
						sqlite3_busy_timeout( db, SQLITE_DB_LOCK_TIMEOUT );
						++nRetryCount;

						LOG( LWAR, "StorageCorrectThread: sqlite3 step SQLITE_BUSY, Retry[%d]",nRetryCount );

						// query 재시도
						db_result = sqlite3_step(stmt);
					}


					// select 조회 결과가 존재하는 경우... SQLITE_ROW (100 ) 을 반환...
					// select 조회 결과가 없는 경우.. SQLITE_DONE (101) 을 반환.

					// select query 수행시 오류가 발생한 경우...
					if( db_result != SQLITE_ROW && db_result != SQLITE_DONE )
					{
						LOG( LERR, "StorageCorrectThread: sqlite3 step error.[%d][%s]", db_result, sqlite3_errmsg(db));

						// Local DB 오류 발생시.. DB 에 문제가 있는 것으로 보고.. DB 객체를 초기화한다.
						sqlite3_finalize(stmt);
						sqlite3_close(db);

						db = NULL;
						stmt = NULL;
					}
					else
					{
						// select Query 가 정상적으로 수행된 경우....
						// 1 row 만 존재하므로...
						db_result = sqlite3_column_int(stmt, 0);

						// Local DB stmt 관련 해제 처리.
						sqlite3_finalize(stmt);
						stmt = NULL;


						// 해당 timestamp 에 대한 조회 결과가 0 인 경우....
						if( db_result == 0 )
						{
							// 보정 대상 Timestamp 에 대한 Storage 사용량 정보가 존재하지 않는 경우...
							// - 이전 timestmap 값을 이용하여 Storage 사용량 정보를 보정 처리한다. ( SELECT -> INSERT )
							snprintf( szQuery, 2047,
									"INSERT INTO storage_stat( timestamp, user_seq, svc_seq, quota, used, check_resource_id, check_get_lastmodified, insert_time, send_flag) "
									"SELECT %u, user_seq, svc_seq, quota, used, check_resource_id, check_get_lastmodified, datetime('now', 'localtime'), 0 "
									"FROM storage_stat "
									"WHERE timestamp = (%u - 1) "
									, correctTimestamp, correctTimestamp );

							// sqlite 에.. Query 수행.

							db_result = sqlite3_exec( db, szQuery, NULL, NULL, &db_err);
							nRetryCount = 0;

							// 만약 BUSY 상태로 인해 오류가 발생하면..30번 재시도
							while( db_result == SQLITE_BUSY  && nRetryCount < 30 )
							{
								sqlite3_free(db_err);
								sqlite3_busy_timeout( db, SQLITE_DB_LOCK_TIMEOUT );
								++nRetryCount;

								LOG( LWAR, "StorageCorrectThread: local db insert sqlite3_exec SQLITE_BUSY. Retry [%d]",nRetryCount );

								// Query 재수행
								db_result = sqlite3_exec( db, szQuery, NULL, NULL, &db_err);	
							}	

							
							if( db_result != SQLITE_OK )
							{
								_LOG( LDBG, "StorageCorrectThread: correct job local db insert failed. [%u][%s]", correctTimestamp, db_err );
								sqlite3_free(db_err);
							}
							else
							{
								_LOG( LINF, "StorageCorrectThread: correct job end. timestamp[%u], [%d]row insert.", correctTimestamp, sqlite3_changes(db) );

							}
						}
						else
						{
							_LOG( LINF, "StorageCorrectThread: correct job timestamp[%u], [%d]row already exist..", correctTimestamp, db_result );

						}
					}
				}
			}
					

			// Local DB 연결 해제.
			if( stmt != NULL )
			{
				sqlite3_finalize(stmt);
				stmt = NULL;
			}

			if( db != NULL )
			{
				sqlite3_close(db);
				db = NULL;
			}

			//_LOG( LINF, "StorageCorrectThread: correct job end. timestamp now[%u] correct[%u].", nowTimestamp, correctTimestamp );

			lastCheckTimestamp = nowTimestamp;
			sleep(60);

		}
	}

	return;
}
