/*
 * driver.c
 *
 *  test driver for c wrapper API
 *  Requirements to run:
 *  	ASA with profile configure with basic auth and a banner message
 *
 *  Driver will performfollowing:
 *  	1. create an API handle
 *  	2. intialize the API
 *  	3. connect to ASA, wait for response prompt
 *  	4. select group and submit login with username/password
 *  	5. accept banner
 *  	6. run for 5 seconds connected, printing out connection stats
 *  	7. disconnect
 *  	8. deactivate API
 *  	9. delete API handle
 */

#include "vpncapi.h"
#include <stdio.h>
#include <string.h>

#ifndef WIN32
#include <pthread.h>
#include <unistd.h>
#else
#include <windows.h>
#include <process.h>
#endif


// event thread signals main thread when api has events to handle
// other way to do this is pipe
#ifndef WIN32
static pthread_mutex_t s_event_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t s_event_cond = PTHREAD_COND_INITIALIZER;
static int sb_event = 0;
#else
CRITICAL_SECTION s_cb_event_section;
HANDLE s_cb_event;
#endif

// ASA settings
static const char* pszHostname = "10.86.94.218";
static const char* pszUsername = "dtls";
static const char* pszPassword = "dtls";
static const char* pszGroup = "tg1";


static int s_LoginState=0;
static int s_PostBannerState=0;
static int s_ConnectedState=0;
static int s_DisconnectedState=0;
static int s_ExitState=0;
static int s_CertEnrollmentRequestedState=0;
static int s_CompletedState=0;

static int s_TimeConnected=0;

// callback implementations
void my_service_cb()
{
	printf("drivers serviceready cb called\n");
}

void my_userprompt_cb(const struct CONNECT_PROMPT_INFO* pPrompt)
{
	unsigned int i;

	printf("drivers userprompt cb called with prompt name: %s\n", pPrompt->pszMessage);
	printf("prompt entries:\n");
	//loop through all prompt entries

	for (i=0; i<pPrompt->nEntries; i++)
	{
		const struct PROMPT_ENTRY* entry = pPrompt->ppEntries[i];
		printf("\t%s:%s\n", entry->pszName, entry->pszValue);

		// hack to login
		if ( strcmp(entry->pszName, "username") == 0 )
		{
			s_LoginState = 1;
		}
	}

}
void my_notice_cb(const char* pszMessage, enum MessageType eType, int iSensitive)
{
	printf("noticecb: %s\n", pszMessage);
}
void my_banner_cb(const char* pszMessage)
{
	printf("bannercb: %s\n", pszMessage);
	// TODO: remove
	s_PostBannerState = 1;
}
void my_state_cb(const enum VPNState state, const enum VPNSubState subState, const char* stateString)
{
	printf("statecb received: %s\n", stateString);
	if (state == CONNECTED)
	{
    	printf("driver- successfully connected to %s !!!\n", pszHostname);
		s_ConnectedState = 1;
		s_DisconnectedState = 0;
	}
	else if (state == DISCONNECTED)
	{
		s_DisconnectedState = 1;
	}
}
void my_exit_notice_cb(const char* pszNotice, const int returnCode)
{
	printf("exitnoticecb: %s return code: %i\n", pszNotice, returnCode);
}

void my_stats_cb(const struct VPN_STATS* pStats)
{
    unsigned int i;
    printf("statscb:\n \tstate: %s\n \ttime connected: %s\n \tpacket received: %s\n", pStats->pszState, pStats->pszTimeConnected, pStats->pszBytesReceived);

    printf("\troute info: there are %lu secured routes\n", pStats->nSecureRoutes);

    for (i=0; i<pStats->nSecureRoutes; i++)
    {
        struct ROUTE_INFO* pRoute = pStats->ppSecureRoutes[i];
        printf("\t\troute: %s %s [host name(s): %s]\n", pRoute->pszNetwork, pRoute->pszSubnet, 
                (pRoute->pszHostNames && *pRoute->pszHostNames ? pRoute->pszHostNames : "N/A"));
    }

    printf("\tprotocol info: there are %lu PROTOCOL_INFO\n", pStats->nProtocolInfo);
    for (i=0; i<pStats->nProtocolInfo; i++)
    {
	    struct PROTOCOL_INFO* pProtocol = pStats->ppProtocolInfo[i];
	    printf("\t\tprotocol: %s state: %s cipher: %s\n", pProtocol->pszProtocol, pProtocol->pszState, pProtocol->pszCipher);
    }

    printf("Dynamic tunnel exclusion: %s\n", pStats->pszDynamicTunnelExclusion);
    printf("Dynamic tunnel inclusion: %s\n", pStats->pszDynamicTunnelInclusion);

    s_TimeConnected++;
}

// this method invoked by CM thread (not GUI thread)
// signal s_event_cond so GUI processes events
/*
void my_event_available_cb()
{
#ifndef WIN32
	pthread_mutex_lock(&s_event_mutex);
	pthread_cond_signal(&s_event_cond);
	sb_event = 1;
	pthread_mutex_unlock(&s_event_mutex);
#else
    EnterCriticalSection(&s_cb_event_section);
    SetEvent(s_cb_event);
    LeaveCriticalSection(&s_cb_event_section);
#endif
}
*/

void my_wmhint_cb(const enum WMHint hint, const enum WMHintReason reason)
{
	printf("wmhintcb: %i, %i\n", hint, reason);
}

void my_deliver_weblaunch_host_cb(const char* pszActiveHost)
{
	printf("deliverweblaunchhostcb: %s", pszActiveHost);
}

void my_deliver_weblaunch_host()
{
#ifndef WIN32
	pthread_mutex_lock(&s_event_mutex);
	pthread_cond_signal(&s_event_cond);
	sb_event = 1;
	pthread_mutex_unlock(&s_event_mutex);
#else
    EnterCriticalSection(&s_cb_event_section);
    SetEvent(s_cb_event);
    LeaveCriticalSection(&s_cb_event_section);
#endif

}

void printHostnames(HVPNAPI* pHandle)
{
    const char* pszDefaultHostname = NULL;
    unsigned int i=0;
    struct STRING_LIST hosts;
    int initialEventsReceived = 0;
    int hasEvent = 0;
    int bTerminate=0;

    pszDefaultHostname = vpnapi_get_default_hostname(pHandle);
    hosts = vpnapi_get_hostnames(pHandle);
    printf("default hostname: %s\n", pszDefaultHostname);
    printf("available hosts: %lu\n", hosts.nStrings);
    for (i=0; i<hosts.nStrings; i++)
    {
        printf("\t%s\n", hosts.ppszStrings[i]);
    }
}

int main(int argc, char **argv)
{
    int result;
    HVPNAPI* pHandle = NULL;
    struct VPNAPI_CALLBACKS callbacks;
    int initialEventsReceived = 0;
    int hasEvent = 0;
    int bTerminate=0;

#ifdef WIN32
    InitializeCriticalSection(&s_cb_event_section);
    s_cb_event = CreateEvent(NULL, TRUE, FALSE, NULL);
#endif

	// define callbacks to register to wrapper layer
	callbacks.pServiceReadyCB = my_service_cb;
	callbacks.pUserPromptCB = my_userprompt_cb;
	//callbacks.pEventAvailableCB = my_event_available_cb;
	callbacks.pNoticeCB = my_notice_cb;
	callbacks.pBannerCB = my_banner_cb;
	callbacks.pStateCB = my_state_cb;
	callbacks.pStatsCB = my_stats_cb;
	callbacks.pWMHintCB = my_wmhint_cb;
	callbacks.pWeblaunchHostCB = my_deliver_weblaunch_host_cb;

	pHandle = vpnapi_create(&callbacks);

	// check if vpn available
	result = vpnapi_is_available(pHandle);
	if (!result)
	{
		printf("error: vpn service not available\n");
		return -1;
	}
	else
	{
		printf("vpn service is available\n");
	}

	//attach
	result = vpnapi_attach(pHandle, ClientType_GUI, 1, 1);
	if (!result)
	{
		printf("error: attach failed\n");
		return -1;
	}

	// wait for initial events
    while (!initialEventsReceived)
    {
        initialEventsReceived = vpnapi_is_event_available(pHandle);
#ifdef WIN32
        Sleep(1000); // windows Sleep take ms
#else
        sleep(1);
#endif
    }

	// process initial events
	vpnapi_process_events(pHandle);

	printf("attached...\n");
	printf("connecting to %s\n", pszHostname);

    //print hostnames
    printHostnames(pHandle);

	//connect
	result = vpnapi_connect(pHandle, pszHostname);
	if (!result)
	{
		printf("error connecting\n");
		return -1;
	}

	// wait for callbacks to occur
	bTerminate = 0;
	while (!bTerminate)
	{
		/*
#ifndef WIN32
        // cond on sb_event
        pthread_mutex_lock(&s_event_mutex);
        // unlock mutex, wait for sb_event set
        while (!sb_event)
        {
            pthread_cond_wait(&s_event_cond, &s_event_mutex);
        }
#else
        WaitForSingleObject(s_cb_event, INFINITE); 
        ResetEvent(s_cb_event);
        EnterCriticalSection(&s_cb_event_section);
#endif
        // at this point, we have event_mutex and sb_event = 1
		*/

		 hasEvent = vpnapi_is_event_available(pHandle);
		if (hasEvent)
		{
			hasEvent = 0;
			vpnapi_process_events(pHandle);
		}
		else
		{
#ifdef WIN32
			Sleep(1000);
#else
			sleep(1);
#endif
		}

        vpnapi_process_events(pHandle);
/*
#ifndef WIN32
        sb_event = 0;
        pthread_mutex_unlock(&s_event_mutex);
#else
        LeaveCriticalSection(&s_cb_event_section);
#endif
*/

        if (s_LoginState)
        {
            unsigned int i;
            struct STRING_LIST entryNames;
        	printf("reached login state\n");

            //print out prompt entry names
            entryNames = vpnapi_prompt_get_entry_names(pHandle);
            for (i=0; i<entryNames.nStrings; i++)
            {
                printf("promptentry name: %s\n", entryNames.ppszStrings[i]);
            }

        	// fillin login info
        	result = vpnapi_prompt_set_entry_value(pHandle, "username", pszUsername);
        	if (!result)
        	{
        		printf("failed to set username\n");
        		return -1;
        	}

        	result = vpnapi_prompt_set_entry_value(pHandle, "password", pszPassword);
        	if (!result)
        	{
        		printf("failed to set password");
        		return -1;
        	}

        	result = vpnapi_prompt_set_entry_value(pHandle, "group_list", pszGroup);
        	if (!result)
        	{
        		printf("failed to set group");
        		return -1;
        	}

        	vpnapi_user_submit(pHandle);
        	s_LoginState = 0;
        }
        else if (s_PostBannerState)
        {
        	// accept banner
        	printf("driver- accepting banner...\n");
        	vpnapi_set_banner_response(pHandle, 1);
        	s_PostBannerState = 0;
        }
        else if (s_ConnectedState)
        {
        	if (s_TimeConnected > 5)
        	{
                const struct PREFERENCE_INFO* pPrefInfo;
                const char* tablevels[3] = {"\t", "\t\t", "\t\t\t"};
            	int childlevel=0;

        		printf("\n\ntesting preferences\n===================================\n");
        		// print prefs, change a prefernce val, print prefs after to make sure value saved

            	printf("user controllable preferences before change:\n");
        		pPrefInfo = vpnapi_get_preferences(pHandle); // get all user controllable prefs
        		if (pPrefInfo)
        		{
					unsigned int i=0;
                    const struct PREFERENCE* pPrev;
					for (i=0; i<pPrefInfo->nPrefs; i++)
					{
						struct PREFERENCE *pCurrent = pPrefInfo->ppPrefs[i];
                        printf("%s %s : %s\n", tablevels[pCurrent->depth], pCurrent->promptentry.pszName, pCurrent->promptentry.pszValue);
					}

					// change preference
					pPrev = vpnapi_get_preference_by_name(pHandle, "LocalLanAccess");
                    if (strcmp(pPrev->promptentry.pszValue, "true") == 0)
					{
						printf("setting LocalLanAccss to false\n");
						result= vpnapi_preference_set_value(pHandle, "LocalLanAccess", "false");
					}
					else
					{
						printf("setting LocalLanAccss to true\n");
						result= vpnapi_preference_set_value(pHandle, "LocalLanAccess", "true");
					}

					if (!result)
					{
						printf("error: can't set val for LocalLanAccess\n");
					}
					result = vpnapi_save_preferences(pHandle);
					if (!result)
					{
						printf("error: can't save preferences");
					}
					else
					{
						printf("succssfully saved prefereces!!\n");
					}

					printf("user controllable preferences after change:\n");
					pPrefInfo = vpnapi_get_preferences(pHandle); // get all user controllable prefs
					for (i=0; i<pPrefInfo->nPrefs; i++)
					{
						struct PREFERENCE *pCurrent = pPrefInfo->ppPrefs[i];
						printf("%s %s : %s\n", tablevels[pCurrent->depth], pCurrent->promptentry.pszName, pCurrent->promptentry.pszValue);
					}

                    printf("invoking reset stats...\n");
                    vpnapi_reset_stats(pHandle);

                    printf("invoking get stats...\n");
                    vpnapi_get_stats(pHandle);
                    
                    printf("invoking get state...\n");
                    vpnapi_get_state(pHandle);

                    printf("are we connected?... %i\n", vpnapi_is_connected(pHandle));
                    
                    printf("is vpn service available?... %i\n", vpnapi_is_vpn_service_available(pHandle));

                    printf("OperatingMode == AlwaysOnVpn?.. %i\n", vpnapi_is_operating_mode(pHandle, AlwaysOnVpn));
        		}
        		else
        		{
        			printf("error: can't fetch prefernces\n");
        		}

				// disconect
        		printf("driver- disconnecting after 5s\n");
				vpnapi_disconnect(pHandle);
				s_ConnectedState=0;
				s_ExitState=1;
        	}
        }
        else if (s_ExitState && s_DisconnectedState)
        {
        	printf("driver- deactivating api\n");
        	vpnapi_detach(pHandle);
        	bTerminate=1;
        }

	}

	vpnapi_delete(pHandle);
}
