/*
    GDAM - Geoff & Dave's Audio Mixer
    Copyright (C) 1999    Dave Benson, Geoff Matters.

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA

    Contact:
        daveb@ffem.org <Dave Benson>
        geoff@ugcs.caltech.edu <Geoff Matters>
*/
#ifndef __GDAM_CHANNEL_H_
#define __GDAM_CHANNEL_H_

#ifdef __cplusplus 
extern "C" { 
#endif

typedef struct _GdamChannel GdamChannel;
typedef struct _GdamChannelEventHandler GdamChannelEventHandler;

#include <gdam/common/gdambuffer.h>
#include <gdam/common/gdamarg.h>
#include "gdamrequest.h"

/*
 * Automatic server launching code.
 *
 * If you want to launch the gdam-server if the connection
 * fails, you may pass a function pointer of type
 * GdamChannelServerLaunchFunc to gdam_channel_new_full.
 *
 * The function returns:
 *
 *        GDAM_CHANNEL_SERVER_LAUNCH_SECURE
 *
 *                    run the server with no listening socket,
 *                    just us connected to it.
 *
 *
 *        GDAM_CHANNEL_SERVER_LAUNCH_INSECURE
 *
 *                    run the server with a listening socket,
 *                    and we will be connected as well.
 *
 *        GDAM_CHANNEL_SERVER_LAUNCH_DEFER
 *
 *                    wait until gdam_channel_set_launch_server()
 *                    to do anything.
 *
 *        GDAM_CHANNEL_SERVER_LAUNCH_ABORT
 *
 *                    destroy the channel and exit the client.
 */
typedef enum {
	GDAM_CHANNEL_SERVER_LAUNCH_SECURE,
	GDAM_CHANNEL_SERVER_LAUNCH_INSECURE,
	GDAM_CHANNEL_SERVER_LAUNCH_DEFER,
	GDAM_CHANNEL_SERVER_LAUNCH_ABORT
} GdamChannelServerLaunch;
typedef GdamChannelServerLaunch
               (*GdamChannelServerLaunchFunc)(GdamChannel*   channel,
	                                      gpointer       user_data);

typedef void   (*GdamChannelOnConnectFunc)   (GdamChannel*   channel,
	                                      gpointer       user_data);
/*
 *   ____     _                  ____ _                            _
 *  / ___| __| | __ _ _ __ ___  / ___| |__   __ _ _ __  _ __   ___| |
 * | |  _ / _` |/ _` | '_ ` _ \| |   | '_ \ / _` | '_ \| '_ \ / _ \ |
 * | |_| | (_| | (_| | | | | | | |___| | | | (_| | | | | | | |  __/ |
 *  \____|\__,_|\__,_|_| |_| |_|\____|_| |_|\__,_|_| |_|_| |_|\___|_|
 * 
 * 
 * The client-side of a connection to the gdam-server.
 */
struct _GdamChannel {
	GdamBuffer		outgoing;

	/* Incoming data first goes here, then gets processed into
	 * responses which are handled by `first_pending'. */
	GdamBuffer		incoming;

	/* Requests which haven't been copied into outgoing. */
	GSList*			first_request;
	GSList*			last_request;

	/* Requests which have been sent (at least to `outgoing').
	 * These requests cannot be reordered. */
	GSList*			first_pending;
	GSList*			last_pending;

	/* num_pending is the length of the first/last_pending list.
	 * max_pending is used to determine if we should write out
	 *             first_request into outgoing and move the request
	 *             to last_pending...
	 *
	 *             Please note that max_pending is not relevant
	 *             to the number of requests you may dispatch-
	 *             that is unlimited.  (This is just a tweak
	 *             so we jam more data down a slow connection.)
	 */
	int			num_pending;
	int			max_pending;

	/* TODO:  "events" from the server must be handled. */

	gboolean              (*auth_handler)    (GdamChannel* channel,
	                                          GdamBuffer*  auth_response);

	
	/* Our `pollfd' which allows us to find out what we can
	 * do with `channel'. */
	GPollFD			poll_fd;

	/* Our mainloop source id. */
	guint			source_id;

	/* Map from event-connection id to event information
	 * (internal use only) */
	GHashTable*		event_to_info_map;

	int			ref_count;

	/* Whether the connection to the server is in the middle of
	 * of connecting.
	 * Don't read or write from `connecting' sockets... */
	unsigned		is_connecting : 1,

	/* Whether we should quit the main loop if
	 * connection fails... */
				quit_main : 1;

	/* a function to call if the connection is established. */
	GdamChannelOnConnectFunc on_connect_func;
	gpointer		on_connect_data;

	/* What to do if the client dies.
	 * (These may be set by the application) */
	GHookFunc		fatal_func;
	gpointer		fatal_func_data;

	/* Local id allocation */
	int			last_local_id;

	/*         server launching variables
	 *
	 * When the connection fails, we optionally go through a
	 * backup contortion of launching the server.
	 *
	 * If `launch_server_func' is non-NULL, we
	 * invoke it to see what we should do:
	 *    - it may tell us to launch the server immediately
	 *      in a secure or insecure mode.
	 *    - it may tell us to exit.
	 *    - it may tell us to wait until gdam_channel_set_launch_server
	 *      is called.
	 */
	GdamChannelServerLaunchFunc launch_server_func;
	gpointer                launch_server_data;

	/* the value to assume if launch_server_func is NULL.
	 * defaults to ABORT. */
	GdamChannelServerLaunch launch_server_val;

	int			launch_server_port;
	char*			launch_server_options;

	/* if launch_server_func returned GDAM_CHANNEL_SERVER_LAUNCH_DEFER,
	 * then this boolean is set until gdam_channel_set_launch_server
	 * is called. */
	gboolean		launch_server_deferred;

	/* each client has a number of named devices it opens on the
	 * server.  */
	GList*			device_list;

	GHookList               chat_handlers;
};


struct _GdamChannelEventHandler {
	guint			connection_id;

	GHookFunc		callback;
	gpointer		user_data;


	/* Returns TRUE if it is done and won't be called again... */
	gboolean              (*marshal)(GdamChannelEventHandler*,
	                                 GdamBuffer*,
					 guint connection_id);

};


/* Usage message that can be displayed to users... */
extern const char* gdam_channel_usage_message;

gboolean     gdam_channel_init                    (int*        argc,
                                                   char***     argv,
						   GdamChannel** channel_out);
gboolean     gdam_channel_init_full               (int*        argc,
                                                   char***     argv,
			                           GdamChannel** channel_out,
			                           GdamChannelServerLaunchFunc,
			                           gpointer    func_data,
				                   GdamChannelOnConnectFunc,
				                   gpointer    on_connect_data);
GdamChannel* gdam_channel_new                     ();
GdamChannel* gdam_channel_new_full                (const char* addr_spec,
                                                   GdamBuffer* auth_info,
				                   GdamChannelServerLaunchFunc,
				                   gpointer    func_data,
				                   GdamChannelOnConnectFunc,
				                   gpointer    on_connect_data);

int          gdam_channel_new_local_id            (GdamChannel* channel);


/* Call unref on any source you "new". */
void         gdam_channel_unref                   (GdamChannel* channel);
void         gdam_channel_ref                     (GdamChannel* channel);

/* Attach to the main loop... you have to do this for
 * events to get processed. */
void         gdam_channel_attach                  (GdamChannel* channel);
void         gdam_channel_detach                  (GdamChannel* channel);

/* Delete us (safe whether or not attached). */
void         gdam_channel_destroy                 (GdamChannel* channel);

void         gdam_channel_register_handler        (GdamChannel* channel,
                                                   GdamChannelEventHandler*);
void         gdam_channel_unregister_handler      (GdamChannel* channel,
                                                   guint        connection_id);

/* Allocating event handlers.
 *
 * Events are notification given from the server
 * that something has happened.
 */
GdamChannelEventHandler*
             gdam_channel_simple_handler_new      (guint connection_id,
						   gboolean one_shot,
	                                           GHookFunc handler_func,
						   gpointer handler_data);
GdamChannelEventHandler*
             gdam_channel_int_handler_new         (guint        connection_id,
						   gboolean     one_shot,
	                                           GdamRequestGotIntFunc,
						   gpointer     handler_data);
GdamChannelEventHandler*
             gdam_channel_time_handler_new        (guint        connection_id,
						   gboolean     one_shot,
	                                           GdamRequestGotTimeFunc,
						   gpointer     handler_data);
GdamChannelEventHandler*
             gdam_channel_2int_handler_new        (guint        connection_id,
						   gboolean     one_shot,
	                                           GdamRequestGot2IntFunc,
						   gpointer     handler_data);
GdamChannelEventHandler*
             gdam_channel_string_handler_new      (guint        connection_id,
						   gboolean     one_shot,
	                                           GdamRequestGotStringFunc,
						   gpointer     handler_data);



gboolean     gdam_channel_append_request          (GdamChannel* channel,
                                                   GdamRequest* request);

/* highly unrecommended -- lifo ordering of requests! */
gboolean     gdam_channel_prepend_request         (GdamChannel* channel,
                                                   GdamRequest* request);


/* if you deferred making a decision when asked to launch the server,
 * you must call this function eventually for the channel to
 * either launch the new server or abort. */
void         gdam_channel_set_launch_server       (GdamChannel* channel,
                                                   GdamChannelServerLaunch);
/*------------------------------deprecated-----------------------
 *             _     _
 *   __ _  ___| |__ | |_ _   _ _ __   __ _
 *  / _` |/ __| '_ \| __| | | | '_ \ / _` |
 * | (_| | (__| | | | |_| |_| | | | | (_| |
 *  \__,_|\___|_| |_|\__|\__,_|_| |_|\__, |
 *                                   |___/
 * all these are deprecated, here for back-compatibility
 * *only*.
 */
/* These are trivial functions built around the asynchronous core... */

guint        gdam_channel_get_default_device      (GdamChannel* channel);
guint        gdam_channel_create_sink             (GdamChannel* channel,
                                                   const char*  device_name);
guint        gdam_channel_create_empty_mix        (GdamChannel* channel);

guint        gdam_channel_create_source_generic_v (GdamChannel* channel,
                                                   const char*  type,
						   int          num_args,
						   GdamArg*     args);
guint        gdam_channel_create_source_generic   (GdamChannel* channel,
                                                   const char*  type,
						   int          num_args,
						   ...);
guint        gdam_channel_source_set_arg          (GdamChannel* channel,
                                                   int          source_id,
						   /* next is arg_name,
						    * arg_type,
						    * arg_value */
						   ...);
guint        gdam_channel_create_mp3              (GdamChannel* channel,
                                                   const char*  filename,
						   GdamTime     start,
						   GdamTime     length);
gboolean     gdam_channel_mix_add                 (GdamChannel* channel,
                                                   guint        mix_id,
						   guint        subsource_id,
						   GdamTime     when);
						   /* if when==-1,
						         add immediately.*/


gboolean     gdam_channel_synchronize             (GdamChannel* channel,
                                                   int          mobile_id,
                                                   int          fixed_id,
					           gdouble      mobile_bpm,
					           gdouble      mobile_start,
					           gdouble      fixed_bpm,
					           gdouble      fixed_start);
gboolean     gdam_channel_sync_preview            (GdamChannel* channel,
                                                   int          mobile_id,
                                                   int          fixed_id,
					           gdouble      mobile_bpm,
					           gdouble      mobile_start,
					           gdouble      fixed_bpm,
					           gdouble      fixed_start,
						   GdamTime*    time_out);


guint        gdam_channel_start_caching           (GdamChannel* channel,
                                                   int          to_drain_id,
						   GHookFunc    done_caching,
						   gpointer     done_data);
gboolean     gdam_channel_uncache                 (GdamChannel* channel,
                                                   guint        buffer_id);

gboolean     gdam_channel_detach_source           (GdamChannel* channel,
                                                   guint        source_id);
gboolean     gdam_channel_feed_sink               (GdamChannel* channel,
                                                   guint        device_id,
                                                   guint        source_id);
gboolean     gdam_channel_seek_relative           (GdamChannel* channel,
                                                   guint        source_id,
						   GdamTime     when);
#if 0
gboolean     gdam_channel_set_sequencer           (GdamChannel* channel,
                                                   guint        sequence_id,
                                                   guint        sequence_len,
						   int          num_elements,
			  const GdamSoundSpecSequencerElement*  elements);
				                   
GdamSoundSpec* gdam_channel_get_sound_spec        (GdamChannel* channel,
                                                   guint        source_id,
					           gboolean     can_recurse);
#endif
gboolean     gdam_channel_get_server_side_int     (GdamChannel* channel,
                                                   const char*  category,
						   const char*  variable,
						   gint*        output);
guint        gdam_channel_subscribe_source_ended  (GdamChannel* channel,
                                                   guint        source_id,
						   GHookFunc    on_ended,
						   gpointer     on_ended_data);
guint        gdam_channel_subscribe_source_timer  (GdamChannel* channel,
                                                   guint        source_id,
						   int          period,
						   GdamRequestGotTimeFunc,
						   gpointer     periodic_data);
gboolean     gdam_channel_unsubscribe             (GdamChannel* channel,
                                                   guint        subscription);

typedef void (*GdamChannelChatFunc) (const char  *username,
			             const char  *command,
			             const char  *text,
			             gpointer     trap_data);
guint        gdam_channel_trap_chat_messages      (GdamChannel *channel,
						   const char  *username,
						   const char  *command,
						   GdamChannelChatFunc func,
						   gpointer     func_data,
						   GDestroyNotify destroy);
void         gdam_channel_untrap_chat_messages    (GdamChannel *channel,
						   guint        trap_id);
void         gdam_channel_send_chat_message       (GdamChannel *channel,
						   const char  *recipient,
						   const char  *command,
						   const char  *text);

/* Process a request (synchronously). */
gboolean     gdam_channel_process_request         (GdamChannel* channel,
                                                   GdamRequest* request);

/* If you allocated a request waitable but don't want to wait
 * after all. */
void         gdam_channel_disown_request          (GdamChannel* channel,
                                                   GdamRequest* request);

#ifdef __cplusplus 
}
#endif

#endif
