// db_mssql.cpp : Defines the entry point for the DLL application.
//

#define WIN32_LEAN_AND_MEAN		// Exclude rarely-used stuff from Windows headers
#include <windows.h>
#include <stdio.h>
#include "db_mssql.h"

#import "C:\Program Files\Common Files\System\ADO\msado15.dll" no_namespace rename("EOF","adoEOF")

_ConnectionPtr g_pConnection;

BOOL Init()
{
	if (OleInitialize(NULL) == S_OK) {
		return SUCCEEDED(g_pConnection.CreateInstance("ADODB.Connection"));
	}
	return FALSE;
}

void Fini()
{
	db_disconnect();
	OleUninitialize(); //收尾工作
}

/* get the hex string presentation of a binary
    dst     :   hex string
    data    :   binary data
    size    :   size of data
    !!make sure dst's size >= size*2+1
*/
char* ToHexString(char* dst, const unsigned char* data, size_t size)
{
	char pBuf[4];
    size_t i;

    if (!dst) return NULL;

	memset( dst, 0, sizeof( char ) * (size * 2 + 1) );
	for(i=0; i<size; i++ )
	{
		memset( pBuf, 0, sizeof( char ) * 4 );
		sprintf( pBuf, "%02x", (unsigned char)data[i] );
		strcat(dst, pBuf);
	}
	return dst;
}


BOOL APIENTRY DllMain( HANDLE hModule, 
                       DWORD  ul_reason_for_call, 
                       LPVOID lpReserved
					 )
{
	switch (ul_reason_for_call)
	{
	case DLL_PROCESS_ATTACH:
		Init();
		break;
	case DLL_THREAD_ATTACH:
		break;
	case DLL_THREAD_DETACH:
		break;
	case DLL_PROCESS_DETACH:
		Fini();
		break;
	}
    return TRUE;
}

DLL_EXPORT_API char* DLL_CALL_TYPE db_get_plugin_interface_version()
{
	return "0.3";
}

DLL_EXPORT_API BOOL DLL_CALL_TYPE db_connect(LPSTR conn_str)
{

	try
	{
		{
			if (SUCCEEDED(g_pConnection->Open(conn_str, "", "", adConnectUnspecified)))
			{	
				//MessageBox(NULL, "Connection Ok", "conn result", MB_OKCANCEL);
				return TRUE;
			}
		}
	}
	catch (_com_error e)
	{
		//MessageBox(NULL, e.ErrorMessage(), "conn error", MB_OKCANCEL);
		OutputDebugString(e.ErrorMessage());
		return FALSE;
	} 
	catch (...)
	{
	}
	return FALSE;


}

DLL_EXPORT_API BOOL DLL_CALL_TYPE db_update(const unsigned char* info_hash, int completed_num, int peer_num, int seeder_num)
// BCTracker call this function to store record of one hashinfo into database.
{
	char sql[256];
	char temp[42];

	
	if ((info_hash == NULL) || (completed_num < 0) || (peer_num < 0) || (seeder_num < 0) )
	{
		return FALSE;
	}
	
	// generate sql:  "EXEC BcTracker_Update 'c6caff01afe91a45df1933682cdc30893a76a640', 0, 1, 1"
	// store procedure: 
	//
	//CREATE PROCEDURE BcTracker_Update
	//	@hash_string char(40), 
	//	@completed_num int,
	//	@peer_num int,
	//	@seeder_num int
	//AS
	//
	//IF EXISTS (SELECT HashString FROM TrackerStatus WHERE HashString=@hash_string)
	//	BEGIN
	//		UPDATE TrackerStatus SET CompletedNum=CompletedNum+@completed_num, 
	//			PeerNum=@peer_num, SeederNum=@seeder_num
	//      		WHERE HashString=@hash_string
	//	END
	//ELSE
	//	BEGIN
	//		INSERT INTO TrackerStatus ( CompletedNum, PeerNum, SeederNum, HashString)
	//		VALUES ( @completed_num, @peer_num, @seeder_num, @hash_string)
	//	END
	//GO

	strcpy(sql, "EXEC BcTracker_Update '");
    ToHexString(temp, info_hash, 20);	
	strcat(sql, temp); 
	strcat(sql, "', ");
    itoa(completed_num, temp, 10);
	strcat(sql, temp); 
	strcat(sql, ", ");
	itoa(peer_num, temp, 10);
	strcat(sql, temp); 
	strcat(sql, ", ");
	itoa(seeder_num, temp, 10);
	strcat(sql, temp); 

	try {
		g_pConnection->Execute(_bstr_t(sql), NULL, adExecuteNoRecords);
		return TRUE;
	}catch(_com_error e) {
		OutputDebugString(e.ErrorMessage());
		return FALSE;
	}
	return FALSE;
}

DLL_EXPORT_API BOOL DLL_CALL_TYPE db_disconnect()
{
	try
	{
		if (g_pConnection != NULL && g_pConnection->State == adStateOpen) 
		{
			if (SUCCEEDED(g_pConnection->Close()))
			{
				return TRUE;
			}
		}
	}
	catch(_com_error e)
	{
		OutputDebugString(e.ErrorMessage());
		return FALSE;
	} 
	return FALSE;
}


DLL_EXPORT_API int DLL_CALL_TYPE db_fetch(info_list_t** add_list_head, BOOL need_auth)
// BCTracker call this function once at startup to fetch all records of hashinfo from database.
// The singly linked list used to pass records is created by this funcion, and released by BCTracker.
// return value: number of records if succeed, -1 if failed.
{
	_RecordsetPtr	pRecordset;
	char sql[256];
	_variant_t value;
	info_list_t* p;
	int n = 0;

	*add_list_head = NULL;

#if (0)
	int i;
	*add_list_head = NULL;
	for (i = 0; i < 100; i++) {
		p = (info_list_t*) new info_list_t;
		if (i % 2 == 0) {
			strncpy(p->info, "1234567890123456789012345678901234567890", 40);			
		}
		else {
			strncpy(p->info, "0987654321098765432109876543210987654321", 40);			
		}
		p->next = *add_list_head;
		*add_list_head = p;
		n++;
	}
	return n;
#endif	

	if(need_auth)
		strcpy(sql, "select HashInfo from metatable where Auditing=1 and localTF=1");
	else
		strcpy(sql, "select HashInfo from metatable where localTF=1");
	
	try	{
		pRecordset.CreateInstance("ADODB.Recordset");
		
		pRecordset->Open(
			_bstr_t(sql), 
			_variant_t((IDispatch*)g_pConnection, true),
			adOpenStatic, 
			adLockOptimistic, 
			adCmdText);

			
		if (pRecordset == NULL) 
			return -1;
		
		n = 0;
		*add_list_head = NULL;
		//if (pRecordset->BOF)
			//pRecordset->MoveFirst(); //移到数据库顶部
		while (!(pRecordset->adoEOF)) {
			//value = pRecordset->GetCollect(_variant_t("localTF"));
			value = pRecordset->GetCollect(_variant_t(long(0)));
			switch (value.vt)	{
			case VT_BSTR:
			case VT_LPSTR:
			case VT_LPWSTR:
				strcpy(sql, (char*)(_bstr_t)value);
				break;
			default:
				return -1;
			}
			
			if (strlen(sql) == 40) {
				p = (info_list_t*) new info_list_t;
				if(p == NULL)
				{
					return -1;
				}
				memcpy(p->info, sql, 40);
				p->next = *add_list_head;
				*add_list_head = p;
				n++;
			}
			
			pRecordset->MoveNext();
		}
		
		return n;
			
	}catch (_com_error e) {
		OutputDebugString(e.ErrorMessage());
		return -1;
	}
	return -1;
}


DLL_EXPORT_API int DLL_CALL_TYPE db_fetch2(info_list_t** add_list_head, info_list_t** delete_list_head)
// BCTracker call this function periodically after db_fetch() to fetch changed records of hashinfo from database.
// The singly linked list used to pass records is created by this funcion, and released by BCTracker.
// return value: number of records if succeed, -1 if failed.
{
	_RecordsetPtr	pRecordset;
	char sql[256];
	_variant_t value;
	info_list_t* p;
	int n = 0;

	if(add_list_head == NULL || delete_list_head == NULL)
		return -1;

	*add_list_head = NULL;
	*delete_list_head = NULL;

	strcpy(sql, "select HashInfo,isAdd from HashInfoTemp");

	try	
	{
		pRecordset.CreateInstance("ADODB.Recordset");

		pRecordset->Open(
			_bstr_t(sql), 
			_variant_t((IDispatch*)g_pConnection, true),
			adOpenStatic, 
			adLockOptimistic, 
			adCmdText);


		if (pRecordset == NULL) 
			return -1;

		n = 0;
		*add_list_head = NULL;
		*delete_list_head = NULL;
		//if (pRecordset->BOF)
		//pRecordset->MoveFirst(); //移到数据库顶部
		while (!(pRecordset->adoEOF)) 
		{
			//value = pRecordset->GetCollect(_variant_t("localTF"));
			value = pRecordset->GetCollect(_variant_t(long(0)));
			switch (value.vt)	
			{
			case VT_BSTR:
			case VT_LPSTR:
			case VT_LPWSTR:
				strcpy(sql, (char*)(_bstr_t)value);
				break;
			default:
				return -1;
			}
			
			bool is_add = false;
			value = pRecordset->GetCollect(_variant_t(long(1)));
			switch (value.vt)	
			{
			case VT_BOOL:
				is_add = (bool)value;
				break;
			default:
				return -1;
			}


			if (strlen(sql) == 40) 
			{
				p = (info_list_t*) new info_list_t;
				if(p == NULL)
				{
					return -1;
				}
				memcpy(p->info, sql, 40);

				if(is_add)
				{
					p->next = *add_list_head;
					*add_list_head = p;
				}
				else
				{
					p->next = *delete_list_head;
					*delete_list_head = p;
				}
				n++;
			}

			// Delete the record
			pRecordset->Delete(adAffectCurrent);
			pRecordset->UpdateBatch(adAffectCurrent);

			pRecordset->MoveNext();
		}

		return n;

	}
	catch (_com_error e) 
	{
		OutputDebugString(e.ErrorMessage());
		return -1;
	}
	return -1;
}
