/*
 * Copyright (c)2004 Cat's Eye Technologies.  All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 
 *   Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * 
 *   Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in
 *   the documentation and/or other materials provided with the
 *   distribution.
 * 
 *   Neither the name of Cat's Eye Technologies nor the names of its
 *   contributors may be used to endorse or promote products derived
 *   from this software without specific prior written permission. 
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE. 
 */

/*
 * connection.c
 * $Id: connection.c,v 1.8 2004/07/10 06:59:03 cpressey Exp $
 * This code was derived in part from:
 * $_DragonFly: src/test/caps/client.c,v 1.3 2004/03/31 20:27:34 dillon Exp $
 * and is therefore also subject to the license conditions on that file.
 */

#include <stdarg.h>
#include <stdlib.h>
#include <string.h>

#include "system.h"
#include "dfui.h"
#include "extbuf.h"
#include "encoding.h"
#include "strpkg.h"
#include "mem.h"
#include "dump.h"

#include "conn_caps.h"
#include "conn_npipe.h"
#include "conn_tcp.h"

struct dfui_connection *
dfui_connection_new(int transport, char *rendezvous)
{
	struct dfui_connection *c = NULL;

	if (
#ifdef HAS_CAPS
	    transport == DFUI_TRANSPORT_CAPS ||
#endif
#ifdef HAS_NPIPE
	    transport == DFUI_TRANSPORT_NPIPE ||
#endif
#ifdef HAS_TCP
	    transport == DFUI_TRANSPORT_TCP ||
#endif
	    0) {
		/* We're OK. */
	} else {
		return(NULL);
	}

	if (dfui_debug_file == NULL) {
		dfui_debug_file = stderr;
	} else {
		setvbuf(dfui_debug_file, NULL, _IOLBF, 0);
	}

	DFUI_MALLOC(c, dfui_connection);
	c->rendezvous = dfui_strdup(rendezvous);
	c->transport = transport;
	c->ebuf = extbuf_new(16384);
	c->t_data = NULL;

	switch (transport) {
#ifdef HAS_CAPS
	case DFUI_TRANSPORT_CAPS:
		DFUI_MALLOC(c->t_data, dfui_conn_caps);
		T_CAPS(c)->cid = 0;
		bzero(&T_CAPS(c)->msgid, sizeof(T_CAPS(c)->msgid));

		/*
		 * XXX Ideally, this value should grow as needed.
		 * However, CAPS currently has a size limit of
		 * 128K internally.
		 */
		T_CAPS(c)->size = 128 * 1024;
		if ((T_CAPS(c)->buf = malloc(T_CAPS(c)->size)) == NULL) {
			free(T_CAPS(c));
			free(c->rendezvous);
			free(c);
			return(NULL);
		}

		/*
		 * Set up dispatch functions.
		 */
		c->be_start = dfui_caps_be_start;
		c->be_stop = dfui_caps_be_stop;
		c->be_ll_exchange = dfui_caps_be_ll_exchange;

		c->fe_connect = dfui_caps_fe_connect;
		c->fe_disconnect = dfui_caps_fe_disconnect;
		c->fe_ll_request = dfui_caps_fe_ll_request;
		break;
#endif /* HAS_CAPS */

#ifdef HAS_NPIPE
	case DFUI_TRANSPORT_NPIPE:
		DFUI_MALLOC(c->t_data, dfui_conn_npipe);
		T_NPIPE(c)->in_pipename = NULL;
		T_NPIPE(c)->out_pipename = NULL;
		T_NPIPE(c)->in = NULL;
		T_NPIPE(c)->out = NULL;

		/*
		 * Set up dispatch functions.
		 */
		c->be_start = dfui_npipe_be_start;
		c->be_stop = dfui_npipe_be_stop;
		c->be_ll_exchange = dfui_npipe_be_ll_exchange;

		c->fe_connect = dfui_npipe_fe_connect;
		c->fe_disconnect = dfui_npipe_fe_disconnect;
		c->fe_ll_request = dfui_npipe_fe_ll_request;
		break;
#endif /* HAS_NPIPE */

#ifdef HAS_TCP
	case DFUI_TRANSPORT_TCP:
                DFUI_MALLOC(c->t_data, dfui_conn_tcp);
		T_TCP(c)->listen_sd = -1;
		T_TCP(c)->connected_sd = -1;
		T_TCP(c)->is_connected = 0;

                /*
                 * Set up dispatch functions.
                 */
                c->be_start = dfui_tcp_be_start;
                c->be_stop = dfui_tcp_be_stop;
		c->be_ll_exchange = dfui_tcp_be_ll_exchange;

                c->fe_connect = dfui_tcp_fe_connect;
                c->fe_disconnect = dfui_tcp_fe_disconnect;
		c->fe_ll_request = dfui_tcp_fe_ll_request;
		break;
#endif /* HAS_TCP */
	}

	return(c);
}

void
dfui_connection_free(struct dfui_connection *c)
{
	if (c == NULL)
		return;

	switch (c->transport) {
#ifdef HAS_CAPS
	case DFUI_TRANSPORT_CAPS:
		if (T_CAPS(c) != NULL) {
			if (T_CAPS(c)->buf != NULL)
				free(T_CAPS(c)->buf);
			DFUI_FREE(T_CAPS(c), dfui_conn_caps);
		}
		break;
#endif
#ifdef HAS_NPIPE
	case DFUI_TRANSPORT_NPIPE:
		if (T_NPIPE(c) != NULL) {
			if (T_NPIPE(c)->in_pipename != NULL)
				free(T_NPIPE(c)->in_pipename);
			if (T_NPIPE(c)->out_pipename != NULL)
				free(T_NPIPE(c)->out_pipename);
			if (T_NPIPE(c)->in != NULL)
				fclose(T_NPIPE(c)->in);
			if (T_NPIPE(c)->out != NULL)
				fclose(T_NPIPE(c)->out);
			DFUI_FREE(T_NPIPE(c), dfui_conn_npipe);
		}
		break;
#endif
#ifdef HAS_TCP
	case DFUI_TRANSPORT_TCP:
		if (T_TCP(c) != NULL) {
			/* XXX close sockets/files here */
			DFUI_FREE(T_NPIPE(c), dfui_conn_tcp);
		}
		break;
#endif
	}

	if (c->rendezvous != NULL)
		free(c->rendezvous);
	DFUI_FREE(c, dfui_connection);
}

/*
 * VERY HIGH LEVEL
 */

/*
 * Create and present a generic `dialog box'-type form for the user
 * and return their response.  actions is a pipe-seperated list of
 * actions to be put on the form (e.g. "OK|Cancel".)  The return
 * value is the ordinal position of the action that was selected,
 * starting at 1 for the first action.  A return value of 0 indicates
 * that an error occurred.  A return value of -1 indicates that the
 * front end aborted the communications.
 */
int
dfui_be_present_dialog(struct dfui_connection *c, char *title,
			char *actions, char *fmt, ...)
{
	struct dfui_form *f;
	struct dfui_response *r;
	va_list args;
	char *message;
	char action_id[256], action_name[256];
	int start, end, counter, i;

	va_start(args, fmt);
	vasprintf(&message, fmt, args);
	va_end(args);
	
	f = dfui_form_create("dialog", title, message, "", NULL);

	free(message);

	start = end = 0;
	while (actions[end] != '\0') {
		end = start;
		while (actions[end] != '|' && actions[end] != '\0')
			end++;

		if ((end - start) >= 256)
			break;
		strncpy(action_name, &actions[start], end - start);
		action_name[end - start] = '\0';
		strcpy(action_id, action_name);
		for(i = 0; action_id[i] != '\0'; i++) {
			if (action_id[i] == ' ')
				action_id[i] = '_';
		}
		dfui_form_action_add(f, dfui_id_new(action_id),
		    dfui_info_new(action_name, "", ""));

		start = end + 1;
	}

	if (!dfui_be_present(c, f, &r)) {
		dfui_form_free(f);
		dfui_response_free(r);
		return(-1);
	}

	strlcpy(action_name, dfui_response_get_action_id(r), 256);
	for(i = 0; action_name[i] != '\0'; i++) {
		if (action_name[i] == '_')
			action_name[i] = ' ';
	}

	start = end = 0;
	counter = 1;
	while (actions[end] != '\0') {
		end = start;
		while (actions[end] != '|' && actions[end] != '\0')
			end++;

		if ((end - start) >= 256)
			break;
		if (strlen(action_name) == (end - start) &&
		    strncmp(action_name, &actions[start], end - start) == 0) {
			break;
		}
		counter++;

		start = end + 1;
	}

	dfui_form_free(f);
	dfui_response_free(r);

	return(counter);
}

/******** BACKEND ********/

/*
 * Connect to the frontend.
 */
dfui_err_t
dfui_be_start(struct dfui_connection *c)
{
	return(c->be_start(c));
}

/*
 * Tell the frontend that we're done and disconnect from it.
 */
dfui_err_t
dfui_be_stop(struct dfui_connection *c)
{
	return(c->be_stop(c));
}

/*
 * Present a form to the user.  This call is synchronous;
 * it does not return until the user has selected an action.
 */
dfui_err_t
dfui_be_present(struct dfui_connection *c,
		struct dfui_form *f, struct dfui_response **r)
{
	struct dfui_strpkg *p;

	p = dfui_strpkg_new_dynamic(16384);
	dfui_encode_form(p, f);

	c->be_ll_exchange(c, DFUI_BE_MSG_PRESENT, dfui_strpkg_buf(p));

	dfui_strpkg_free(p);

	/* check for ABORT reply */
	if (extbuf_buf(c->ebuf)[0] == DFUI_FE_MSG_ABORT) {
		return(DFUI_FAILURE);
	}

	/*
	 * Now we've got the response; so decode it.
	 */

	p = dfui_strpkg_new_static(extbuf_buf(c->ebuf) + 1, extbuf_len(c->ebuf) - 1);
	*r = dfui_decode_response(p);
	dfui_strpkg_free(p);

	return(DFUI_SUCCESS);
}

/*
 * Begin showing a progress bar to the user.
 * This function is asynchronous; it returns immediately.
 * The assumption is that the backend will make subsequent
 * calls to dfui_be_progress_update() frequently, and in
 * them, check to see if the user cancelled.
 */
dfui_err_t
dfui_be_progress_begin(struct dfui_connection *c, struct dfui_progress *pr)
{
	struct dfui_strpkg *p;

	p = dfui_strpkg_new_dynamic(16384);
	dfui_encode_progress(p, pr);

	c->be_ll_exchange(c, DFUI_BE_MSG_PROG_BEGIN, dfui_strpkg_buf(p));
	dfui_strpkg_free(p);

	/* response might have been be READY or ABORT */
	if (extbuf_buf(c->ebuf)[0] == DFUI_FE_MSG_ABORT) {
		return(DFUI_FAILURE);
	} else {
		return(DFUI_SUCCESS);
	}
}

dfui_err_t
dfui_be_progress_update(struct dfui_connection *c,
			struct dfui_progress *pr, int *cancelled)
{
	struct dfui_strpkg *p;

	p = dfui_strpkg_new_dynamic(16384);
	dfui_encode_progress(p, pr);

	c->be_ll_exchange(c, DFUI_BE_MSG_PROG_UPDATE, dfui_strpkg_buf(p));
	dfui_strpkg_free(p);

	/* response might have been READY, CANCEL, or ABORT */

	*cancelled = 0;	
	if (extbuf_buf(c->ebuf)[0] == DFUI_FE_MSG_CANCEL) {
		*cancelled = 1;
	}
	if (extbuf_buf(c->ebuf)[0] == DFUI_FE_MSG_ABORT) {
		return(DFUI_FAILURE);
	} else {
		return(DFUI_SUCCESS);
	}
}

dfui_err_t
dfui_be_progress_end(struct dfui_connection *c)
{
	c->be_ll_exchange(c, DFUI_BE_MSG_PROG_END, "");

	/* response might have been be READY or ABORT */
	if (extbuf_buf(c->ebuf)[0] == DFUI_FE_MSG_ABORT) {
		return(DFUI_FAILURE);
	} else {
		return(DFUI_SUCCESS);
	}
}

/******** FRONTEND ********/

dfui_err_t
dfui_fe_connect(struct dfui_connection *c)
{
	return(c->fe_connect(c));
}

dfui_err_t
dfui_fe_disconnect(struct dfui_connection *c)
{
	dfui_debug("DISCONNECTING<<>>\n");
	return(c->fe_disconnect(c));
}

/*
 * Receive a message from the backend.  This call is synchronous;
 * it does not return until a message comes in from the backend.
 * After this call, the message type is available in *msgtype,
 * and the message itself (if any) is available in *payload, ready
 * to be casted to its real type (as per *msgtype).
 */
dfui_err_t
dfui_fe_receive(struct dfui_connection *c, char *msgtype, void **payload)
{
	struct dfui_strpkg *p;

	c->fe_ll_request(c, DFUI_FE_MSG_READY, "");
	*msgtype = extbuf_buf(c->ebuf)[0];
	switch (*msgtype) {
	case DFUI_BE_MSG_PRESENT:
		p = dfui_strpkg_new_static(extbuf_buf(c->ebuf) + 1, extbuf_len(c->ebuf) - 1);
		(struct dfui_form *)(*payload) = dfui_decode_form(p);
		dfui_strpkg_free(p);
		return(DFUI_SUCCESS);

	case DFUI_BE_MSG_PROG_BEGIN:
		p = dfui_strpkg_new_static(extbuf_buf(c->ebuf) + 1, extbuf_len(c->ebuf) - 1);
		(struct dfui_progress *)(*payload) = dfui_decode_progress(p);
		dfui_strpkg_free(p);
		return(DFUI_SUCCESS);

	case DFUI_BE_MSG_PROG_UPDATE:
		p = dfui_strpkg_new_static(extbuf_buf(c->ebuf) + 1, extbuf_len(c->ebuf) - 1);
		(struct dfui_progress *)(*payload) = dfui_decode_progress(p);
		dfui_strpkg_free(p);
		return(DFUI_SUCCESS);

	case DFUI_BE_MSG_PROG_END:
		*payload = NULL;
		return(DFUI_SUCCESS);

	case DFUI_BE_MSG_STOP:
		*payload = NULL;
		return(DFUI_SUCCESS);

	default:
		/* XXX ??? */
		return(DFUI_FAILURE);
	}
}

/*
 * Submit the result of a form to the backend.
 */
dfui_err_t
dfui_fe_submit(struct dfui_connection *c, struct dfui_response *r)
{
	struct dfui_strpkg *p;
	dfui_err_t request_error;

	p = dfui_strpkg_new_dynamic(16384);
	dfui_encode_response(p, r);

	dfui_debug("ENCODE<<%s>>\n", dfui_strpkg_buf(p));
	request_error = c->fe_ll_request(c, DFUI_FE_MSG_SUBMIT,
	    dfui_strpkg_buf(p));
	/* XXX we should check for READY from the backend? */
	dfui_strpkg_free(p);

	return(request_error);
}

dfui_err_t
dfui_fe_progress_continue(struct dfui_connection *c)
{
	c->fe_ll_request(c, DFUI_FE_MSG_CONTINUE, "");
	return(DFUI_SUCCESS);
}

dfui_err_t
dfui_fe_progress_cancel(struct dfui_connection *c)
{
	c->fe_ll_request(c, DFUI_FE_MSG_CANCEL, "");
	return(DFUI_SUCCESS);
}

dfui_err_t
dfui_fe_confirm_stop(struct dfui_connection *c)
{
	c->fe_ll_request(c, DFUI_FE_MSG_CONTINUE, "");
	return(DFUI_SUCCESS);
}

/*
 * Abort the backend.
 * Note that you still must call dfui_fe_disconnect after this.
 */
dfui_err_t
dfui_fe_abort(struct dfui_connection *c)
{
	c->fe_ll_request(c, DFUI_FE_MSG_ABORT, "");
	return(DFUI_SUCCESS);
}
