/**
 * Licensed under the dual GPL/BSD license.  (See LICENSE file for more info.)
 *
 * \file ipcevents.c
 *
 * \author chris@open1x.org
 *
 *  This program listens on the event channel for the supplicant and displays the
 *  events that are generated.  It can be viewed as an event "sniffer" that can
 *  be used to debug new event types, or to learn how the event system works.
 **/

#ifndef WINDOWS
#include <string.h>
#include <unistd.h>
#endif

#include <libxml/parser.h>

#include "libxsupgui/xsupgui.h"
#include "libxsupgui/xsupgui_xml_common.h"
#include "libxsupgui/xsupgui_request.h"
#include "libxsupgui/xsupgui_events.h"
#include "libxsupgui/xsupgui_events_state.h"
#include "src/ipc_events_index.h"

#ifdef WINDOWS
#include <windows.h>
#endif

/**
 * \brief Process a state transition event generated by the supplicant.
 *
 * @param[in] intf   A string that provides the interface name.
 * @param[in] sm   An integer that indicates which state machine generated the
 *                 event. See \ref ipc_events_index.h for the list of state
 *                 machines that can generate events.  
 * @param[in] oldstate   The state that the state machine was in before this
 *                       event was triggered.
 * @param[in] newstate   The state the state machine is now in.
 *
 * \note You may need to change some supplicant build options in order to
 *       get all of the different state machines to generate events.
 **/
void state_transition(char *intf, int sm, int oldstate, int newstate)
{
	char *state = NULL, *oldstatestr = NULL, *newstatestr = NULL;

	state = xsupgui_events_state_get_statemachine_str(sm);

	switch (sm) {
	case IPC_STATEMACHINE_PHYSICAL:
		oldstatestr =
		    xsupgui_events_state_get_wireless_state_str(oldstate);
		newstatestr =
		    xsupgui_events_state_get_wireless_state_str(newstate);
		break;

	case IPC_STATEMACHINE_8021X:
		oldstatestr =
		    xsupgui_events_state_get_8021X_state_str(oldstate);
		newstatestr =
		    xsupgui_events_state_get_8021X_state_str(newstate);
		break;

	case IPC_STATEMACHINE_8021X_BACKEND:
		oldstatestr =
		    xsupgui_events_state_get_8021Xbe_state_str(oldstate);
		newstatestr =
		    xsupgui_events_state_get_8021Xbe_state_str(newstate);
		break;

	case IPC_STATEMACHINE_EAP:
		oldstatestr = xsupgui_events_state_get_eap_state_str(oldstate);
		newstatestr = xsupgui_events_state_get_eap_state_str(newstate);
		break;

	default:
		oldstatestr = NULL;
		newstatestr = NULL;
		break;
	}

	printf
	    ("%s state machine transitioned from %s to %s on interface %s.\n\n",
	     state, oldstatestr, newstatestr, intf);

	free(state);
	free(oldstatestr);
	free(newstatestr);
}

/**
 * \brief Parse the TNC UI batch event, and display it's information.
 **/
void ipcevents_parse_tnc_ui_batch_request_event()
{
	uint32_t oui, msgid, imcID, connID;
	tnc_msg_batch *data;
	int i = 0;

	if (xsupgui_events_get_tnc_ui_batch_request_event
	    (&imcID, &connID, &oui, &msgid, &data) == 0) {
		printf("Got a message from OUI %d with message ID of %d.\n",
		       oui, msgid);

		if (data == NULL) {
			printf("Didn't get an data!?\n");
			return;
		}

		while (data[i].oui != 0) {
			printf("\tIMC ID        : %d\n", data[i].imcID);
			printf("\tConnection ID : %d\n", data[i].connectionID);
			printf("\tMessage OUI   : %d\n", data[i].oui);
			printf("\tMessage ID    : %d\n", data[i].msgid);
			printf("\tParameter     : %s\n", data[i].parameter);
			printf("\n");
			i++;
		}

		xsupgui_events_free_tnc_msg_batch_data(&data);
	}
}

/**
 * \brief Parse a UI event code, and return a text string that indicates what
 *        the event code means.
 *
 * @param[in] uievt   An integer that indicates the type of event that was
 *                    received.
 * @param[out] desc   A string that describes the event that \ref uievt
 *                    references.
 * 
 * \retval 0    Current 0 is always returned.
 **/
int ipcevents_get_ui_string(int uievt, char **desc)
{
	switch (uievt) {
	case IPC_EVENT_UI_IP_ADDRESS_SET:
		(*desc) = strdup("An interface has had it's IP address set!\n");
		break;

	case IPC_EVENT_INTERFACE_INSERTED:
		(*desc) = strdup("An interface has been inserted.\n");
		break;

	case IPC_EVENT_INTERFACE_REMOVED:
		(*desc) = strdup("An interface has been removed.\n");
		break;

	case IPC_EVENT_SIGNAL_STRENGTH:
		(*desc) = strdup("Signal strength was updated.\n");
		break;

	case IPC_EVENT_BAD_PSK:
		(*desc) = strdup("An invalid PSK was used.\n");
		break;

	case IPC_EVENT_UI_AUTH_TIMEOUT:
		(*desc) = strdup("Authentication timed out.\n");
		break;

	case IPC_EVENT_UI_GOING_TO_SLEEP:
		(*desc) = strdup("The machine is going to sleep.\n");
		break;

	case IPC_EVENT_UI_SLEEP_CANCELLED:
		(*desc) =
		    strdup("The machine's sleep attempt was cancelled.\n");
		break;

	case IPC_EVENT_UI_WAKING_UP:
		(*desc) = strdup("The machine is waking up.\n");
		break;

	case IPC_EVENT_UI_LINK_UP:
		(*desc) = strdup("Link up.\n");
		break;

	case IPC_EVENT_UI_LINK_DOWN:
		(*desc) = strdup("Link down.\n");
		break;

	case IPC_EVENT_UI_INT_CTRL_CHANGED:
		(*desc) = strdup("Interface control changed.\n");
		break;

	case IPC_EVENT_UI_TROUBLETICKET_DONE:
		(*desc) = strdup("Trouble ticket creation complete!\n");
		break;

	case IPC_EVENT_UI_TROUBLETICKET_ERROR:
		(*desc) = strdup("Error creating trouble ticket!\n");
		break;

	case IPC_EVENT_UI_NEED_UPW:
		(*desc) = strdup("Username/password needed.\n");
		break;

	case IPC_EVENT_UI_POST_CONNECT_TIMEOUT:
		(*desc) =
		    strdup
		    ("Connection has been lost.  Ask the user if they want to connect to something else.\n");
		break;

	case IPC_EVENT_UI_CONNECTION_DISCONNECT:
		(*desc) =
		    strdup("A connection was disconnected by UI request.\n");
		break;

	case IPC_EVENT_PSK_SUCCESS:
		(*desc) = strdup("A PSK auth was successful.\n");
		break;

	case IPC_EVENT_CONNECTION_UNBOUND:
		(*desc) = strdup("A connection was unbound.\n");
		break;

	default:
		printf("Unknown UI event : %d\n", uievt);
		(*desc) = strdup("An unknown UI event occurred.\n");
		break;
	}

	return 0;
}

#ifdef WINDOWS
int crashdump_add_file(char *temp, char temp2)
{
	return 0;
}
#endif

int main()
{
	long int result = 0;
	char *logline = NULL, *ints = NULL, *connname = NULL, *eapmethod = NULL, *chalstr = NULL, *arg = NULL;
	char *intname = NULL;
	int cur_debug_level = 10;	///< The user defined debug level in use.
	int sm = 0, oldstate = 0, newstate = 0, evttype = 0;
	uint32_t value321, value322, value323, value324, tncconnectionid;
	int uievt;
	int count = 0;

	printf("Attempting to connect to the supplicant event listener.\n");

	// Attempt to connect to the supplicant's event pipe.  Try once a second so
	// that we don't overload the CPU by spinning.
	while (xsupgui_connect_event_listener() != 0) {
		printf("Supplicant doesn't appear to be alive.  Will check again in 1 second.\n");
#ifdef WINDOWS
		Sleep(1000);
#else
		sleep(1);
#endif
	}

	// Let the user know that we are ready to display events as they come in.
	printf("\n\nWaiting for events. . . .\n\n");

	// Loop as long as we believe that we have a chance of getting an event.
	// -1 is a parser error, -2 means the connection was terminated.
	while (result >= -1) {
		// Request the next event that comes across the channel.
		// NOTE : THIS CALL *WILL* BLOCK!
		result = xsupgui_process(&evttype);

		if (result != REQUEST_SUCCESS) {
			printf("Error getting data : %ld\n", result);
		}

		if (result == REQUEST_SUCCESS) {
			// We got an event.  So we need to look at the type it is, and
			// request that it be processed properly.
			switch (evttype) {
			case IPC_EVENT_LOG:
				// Process a log message.
				result = xsupgui_events_generate_log_string(&ints,
								       &logline);
				if ((result <= cur_debug_level)
				    && (result == REQUEST_SUCCESS)) {
					printf("Log event for interface %s :\n",
					       ints);
					printf("\t%s\n\n", logline);
				}

				if (ints != NULL)
					free(ints);
				if (logline != NULL)
					free(logline);
				break;

			case IPC_EVENT_ERROR:
				// Process an error message.
				result = xsupgui_events_get_error(&uievt, &logline);
				if (result == 0) {
					printf("Error (%d) : %s\n", uievt,
					       logline);
					free(logline);
				}
				break;

			case IPC_EVENT_UI:
				// Process a UI event.
				result = xsupgui_events_get_ui_event(&uievt, &ints,
								&arg);
				if (result == 0) {
					result = ipcevents_get_ui_string(uievt,
								    &logline);
					if (result == 0) {
						printf("Got UI Event : %s\n",
						       logline);
						if (uievt == IPC_EVENT_SIGNAL_STRENGTH) {
							printf("\tSignal Strength : %s%%\n",
							     arg);
						}

						free(logline);
					} else {
						printf
						    ("Couldn't get UI event string!\n");
					}

					if (ints != NULL)
						free(ints);
					if (arg != NULL)
						free(arg);
				} else {
					printf("Couldn't parse UI event!\n");
				}
				break;

			case IPC_EVENT_STATEMACHINE:
				// Process a state machine message.
				result = xsupgui_events_get_state_change(&ints, &sm,
								    &oldstate,
								    &newstate,
								    &tncconnectionid);
				state_transition(ints, sm, oldstate, newstate);
				free(ints);
				ints = NULL;
				break;

			case IPC_EVENT_SCAN_COMPLETE:
				// Process a scan complete event.
				result = xsupgui_events_get_scan_complete_interface(&ints);
				printf("Scan complete on interface %s.\n",
				       ints);
				free(ints);
				ints = NULL;
				break;

			case IPC_EVENT_REQUEST_PWD:
				// Process a password request event.
				result = xsupgui_events_get_passwd_challenge(&connname, &intname, &eapmethod, &chalstr);
				printf("Password requested for connection '%s'!\n",
				     connname);
				printf("\tEAP method       : %s\n", eapmethod);
				printf("\tChallenge String : %s\n", chalstr);
				free(connname);
				free(eapmethod);
				free(chalstr);
				break;

			case IPC_EVENT_TNC_UI:
				// Process a TNC UI event.  (This event is basically a TNC IMC
				// sending some sort of status of notification to a UI component.)
				if (xsupgui_events_get_tnc_ui_event(&value321, &value322) == 0) {
					printf("Got a TNC UI event.\n");
					printf("\tOUI          : %d\n",
					       value321);
					printf("\tNotification : %d\n",
					       value322);
				}
				break;

			case IPC_EVENT_TNC_UI_REQUEST:
				// Process a TNC UI request.  This is triggered when an IMC asks
				// the UI to do something.  In general, the UI should respond, but
				// since this program isn't interactive, we can't. ;)
				if (xsupgui_events_get_tnc_ui_request_event(&value321, &value322, &value323,
				     &value324) == 0) {
					printf
					    ("Got a TNC UI request response event.\n");
					printf("IMC ID          : %d\n",
					       value321);
					printf("Connection ID   : %d\n",
					       value322);
					printf("OUI             : %d\n",
					       value323);
					printf("Request         : %d\n",
					       value324);
				}
				break;

			case IPC_EVENT_TNC_UI_BATCH_REQUEST:
				// A TNC IMC sent a batch of information that it wants the UI
				// to process.  In general, these messages are vendor specific,
				// so there isn't anything we can really do with them.
				ipcevents_parse_tnc_ui_batch_request_event();
				break;

			case IPC_EVENT_COM_BROKEN:
				// The xsupgui library notified us that it's event connection
				// has been broken.  This is the right way to determine when the
				// supplicant isn't going to send us more data, since it isn't
				// platform specific.
				printf("Communication with the supplicant has been broken.\n");
				result = -2;	// To break out of the loop.
				break;

			default:
				printf("Unknown event received!  (Event : %ld)\n",
				     result);
				break;
			}

			// Always free the event doc when you are done working with it.  
			// Otherwise, you might end up leaking lots of memory.
			xsupgui_free_event_doc();
		}

		count++;
	}

	// Let the user know that the connection has been broken.  Then, make
	// sure that we clean up after ourselves.
	printf("Pipe was disconnected, or got an error.\n");
	xsupgui_disconnect_event_listener();

	return 0;
}
