/* test-oop.c, liboop, copyright 1999 Dan Egnor

   This is free software; you can redistribute it and/or modify it under the
   terms of the GNU Lesser General Public License, version 2.1 or later.
   See the file COPYING for details. */

#include <stdio.h>
#include <assert.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "oop.h"
#include "oop-read.h"

#ifdef HAVE_GLIB
#include <glib.h>
#include "oop-glib.h"
GMainLoop *glib_loop;
#endif

#ifdef HAVE_TCL
#include <tcl.h>
#include "oop-tcl.h"
#endif

#ifdef HAVE_WWW
/* Yuck: */
#define HAVE_CONFIG_H
#undef PACKAGE
#undef VERSION

#include "oop.h"
#include "HTEvent.h"
#include "oop-www.h"

#include "WWWLib.h"
#include "WWWInit.h"
#endif

#ifdef HAVE_READLINE
#include <readline/readline.h>
#include "oop-rl.h"
#endif

struct timer {
	struct timeval tv;
	int delay;
};

static oop_source_sys *source_sys;
static oop_adapter_signal *source_signal;

static void usage(void) {
	fputs(
"usage:   test-oop <source> <sink> [<sink> ...]\n"
"sources: sys      system event source\n"
"         signal   system event source with signal adapter\n"
#ifdef HAVE_GLIB
"         glib     GLib source adapter\n"
#endif
#ifdef HAVE_TCL
"         tcl      Tcl source adapter\n"
#endif
"sinks:   timer    some timers\n"
"         signal   some signal handlers\n"
"         echo     a stdin->stdout copy\n"
#ifdef HAVE_READLINE
"         readline like echo but with line editing\n"
#endif
#ifdef HAVE_ADNS
"         adns     some asynchronous DNS lookups\n"
#endif
#ifdef HAVE_WWW
"         libwww   some HTTP GET operations\n"
#endif
"         read:<delim-spec><nul-mode><shortrec-mode>[<maxrecsz>][,<bufsz>][:<data>]\n"
"                  <delim-spec>:    n | s<delim> | k<delim>\n"
"                  <delim>:         =<char> | n | <hex><hex>\n"
"                  <nul-mode>:      f | d | p\n"
"                  <shortrec-mode>: f | e | l | s\n"
	,stderr);
	exit(1);
}

/* -- timer ---------------------------------------------------------------- */

static oop_call_time on_timer;
static void *on_timer(oop_source *source,struct timeval tv,void *data) {
	struct timer *timer = (struct timer *) data;
	timer->tv = tv;
	timer->tv.tv_sec += timer->delay;
	source->on_time(source,timer->tv,on_timer,data);
	printf("timer: once every ");
	if (1 == timer->delay) printf("second\n");
	else printf("%d seconds\n",timer->delay);
	return OOP_CONTINUE;
}

static oop_call_signal stop_timer;
static void *stop_timer(oop_source *source,int sig,void *data) {
	struct timer *timer = (struct timer *) data;
	source->cancel_time(source,timer->tv,on_timer,timer);
	source->cancel_signal(source,SIGQUIT,stop_timer,timer);
	return OOP_CONTINUE;
}

static void add_timer(oop_source *source,int interval) {
	struct timer *timer = malloc(sizeof(*timer));
	gettimeofday(&timer->tv,NULL);
	timer->delay = interval;
	source->on_signal(source,SIGQUIT,stop_timer,timer);
	on_timer(source,timer->tv,timer);
}

/* -- signal --------------------------------------------------------------- */

static oop_call_signal on_signal;
static void *on_signal(oop_source *source,int sig,void *data) {
	switch (sig) {
	case SIGINT:
		puts("signal: SIGINT (control-C) caught.  "
		     "(Use SIGQUIT, control-\\, to terminate.)");
		break;
	case SIGQUIT:
		puts("signal: SIGQUIT (control-\\) caught, terminating.");
		source->cancel_signal(source,SIGINT,on_signal,NULL);
		source->cancel_signal(source,SIGQUIT,on_signal,NULL);
		break;
	default:
		assert(0 && "unknown signal?");
	}
	return OOP_CONTINUE;
}

/* -- echo ----------------------------------------------------------------- */

static oop_call_fd on_data;
static void *on_data(oop_source *source,int fd,oop_event event,void *data) {
	char buf[BUFSIZ];
	int r = read(fd,buf,sizeof(buf));
	write(1,buf,r);
	return OOP_CONTINUE;
}

static oop_call_signal stop_data;
static void *stop_data(oop_source *source,int sig,void *data) {
	source->cancel_fd(source,0,OOP_READ);
	source->cancel_signal(source,SIGQUIT,stop_data,NULL);
	return OOP_CONTINUE;
}

/* -- readline ------------------------------------------------------------- */

#ifdef HAVE_READLINE

static void on_readline(const char *input) {
	if (NULL == input)
		puts("\rreadline: EOF");
	else {
		fputs("readline: \"",stdout);
		fputs(input,stdout);
		puts("\"");
	}
}

static void *stop_readline(oop_source *src,int sig,void *data) {
	oop_readline_cancel(src);
	src->cancel_signal(src,SIGQUIT,stop_readline,NULL);
	rl_callback_handler_remove();
	return OOP_CONTINUE;
}

static void add_readline(oop_source *src) {
	rl_callback_handler_install(
		(char *) "> ", /* readline isn't const-correct */
		(VFunction *) on_readline);
	oop_readline_register(src);
	src->on_signal(src,SIGQUIT,stop_readline,NULL);
}

#else

static void add_readline(oop_source *src) {
	fputs("sorry, readline not available\n",stderr);
	usage();
}

#endif

/* -- adns ----------------------------------------------------------------- */

#ifdef HAVE_ADNS

#include "adns.h"
#include "oop-adns.h"

#undef ADNS_CANCEL
#define NUM_Q 6
oop_adns_query *q[NUM_Q];
oop_adapter_adns *adns;

static void cancel_adns(void) {
	int i;

	for (i = 0; i < NUM_Q; ++i)
		if (NULL != q[i]) {
			oop_adns_cancel(q[i]);
			q[i] = NULL;
		}

	if (NULL != adns) {
		oop_adns_delete(adns);
		adns = NULL;
	}
}

static void *stop_lookup(oop_source *src,int sig,void *data) {
	cancel_adns();
	src->cancel_signal(src,SIGQUIT,stop_lookup,NULL);
	return OOP_CONTINUE;
}

static void *on_lookup(oop_adapter_adns *adns,adns_answer *reply,void *data) {
	int i;
	for (i = 0; i < NUM_Q; ++i) if (data == &q[i]) q[i] = NULL;

	printf("adns: %s =>",reply->owner);
	if (adns_s_ok != reply->status)
		printf(" error: %s\n",adns_strerror(reply->status));
	else {
		if (NULL != reply->cname) printf(" (%s)",reply->cname);
		assert(adns_r_a == reply->type);
		for (i = 0; i < reply->nrrs; ++i)
			printf(" %s",inet_ntoa(reply->rrs.inaddr[i]));
		printf("\n");
	}

	free(reply);
#ifdef ADNS_CANCEL
	cancel_adns();
#endif
	return OOP_CONTINUE;
}

static void get_name(int i,const char *name) {
	q[i] = oop_adns_submit(
	       adns,NULL,name,adns_r_a,adns_qf_owner,
	       on_lookup,&q[i]);
}

static void add_adns(oop_source *src) {
	adns = oop_adns_new(src,0,NULL);
	get_name(0,"g.mp");
	get_name(1,"cnn.com");
	get_name(2,"slashdot.org");
	get_name(3,"love.ugcs.caltech.edu");
	get_name(4,"intel.ugcs.caltech.edu");
	get_name(5,"ofb.net");
	src->on_signal(src,SIGQUIT,stop_lookup,NULL);
}

#else

static void add_adns(oop_source *src) {
	fputs("sorry, adns not available\n",stderr);
	usage();
}

#endif

/* -- libwww --------------------------------------------------------------- */

#ifdef HAVE_WWW

static int remaining = 0;

static int on_print(const char *fmt,va_list args) {
	return (vfprintf(stdout,fmt,args));
}

static int on_trace (const char *fmt,va_list args) {
	return (vfprintf(stderr,fmt,args));
}

static int on_complete(HTRequest *req,HTResponse *resp,void *x,int status) {
	HTChunk *chunk = (HTChunk *) HTRequest_context(req);
	char *address = HTAnchor_address((HTAnchor *) HTRequest_anchor(req));

	HTPrint("%d: done with %s\n",status,address);
	HTMemory_free(address);
	HTRequest_delete(req);

	if (NULL != chunk) HTChunk_delete(chunk);

	if (0 == --remaining) {
	/* stop ... */
	}

	return HT_OK;
}

static void get_uri(const char *uri) {
	HTRequest *req = HTRequest_new();
	HTRequest_setOutputFormat(req, WWW_SOURCE);
	HTRequest_setContext(req,HTLoadToChunk(uri,req));
	++remaining;
}

static void *stop_www(oop_source *source,int sig,void *x) {
	oop_www_cancel();
	HTProfile_delete();
	source->cancel_signal(source,sig,stop_www,x);
	return OOP_CONTINUE;
}

static void add_www(oop_source *source) {
	puts("libwww: known bug: termination (^\\) may abort due to cached "
             "connections, sorry.");
	HTProfile_newNoCacheClient("test-www","1.0");
	oop_www_register(source);

	HTPrint_setCallback(on_print);
	HTTrace_setCallback(on_trace);

	HTNet_addAfter(on_complete, NULL, NULL, HT_ALL, HT_FILTER_LAST);
	HTAlert_setInteractive(NO);

	get_uri("http://ofb.net/~egnor/oop/");
	get_uri("http://ofb.net/does.not.exist");
	get_uri("http://slashdot.org/");
	get_uri("http://www.w3.org/Library/");
	get_uri("http://does.not.exist/");

	source->on_signal(source,SIGQUIT,stop_www,NULL);
}

#else

static void add_www(oop_source *source) {
	fputs("sorry, libwww not available\n",stderr);
	usage();
}

#endif

typedef void on_read_err_func(oop_source*,oop_read*);

static void *on_read(oop_source *source, oop_read *rd,
		     oop_rd_event event, const char *errmsg, int errnoval,
		     const char *data, size_t recsz, void *next_v) {
	on_read_err_func **next= next_v;
	size_t off;
	int c;

	printf("read %s %s%s%s%s %s ",
	       next ? "error" : "ok",

	       event == OOP_RD_OK      ? "OK" :
	       event == OOP_RD_EOF     ? "EOF" :
	       event == OOP_RD_PARTREC ? "PARTREC" :
	       event == OOP_RD_LONG    ? "LONG" :
	       event == OOP_RD_NUL     ? "NUL" :
	       event == OOP_RD_SYSTEM  ? "SYSTEM" :
	       (assert(!"event must be valid"), (char*)0),
	       
	       errmsg?" `":"", errmsg?errmsg:"", errmsg?"'":"",
	       errnoval ? strerror(errnoval) : "Zero");

	if (data) {

		printf("%lu:\"", (unsigned long)recsz);

		for (off=0; off<recsz; off++) {
			c = (unsigned char)data[off];
			if (c==' ' || (isprint(c) && !isspace(c))) {
				putchar(c);
			} else {
				switch (c) {
				case '\n': fputs("\\n",stdout); break;
				case '\t': fputs("\\t",stdout); break;
			default:   printf("\\x%02x",c);
				}
			}
		}

		assert(!data[recsz]);

		putchar('"');

	} else {
		fputs("null",stdout);
	}

	putchar('\n');

	if (next)
		(*next)(source,rd);

	return OOP_CONTINUE;
}

static void *stop_read(oop_source *src,int sig,void *rd_v) {
	oop_read *rd= rd_v;

	src->cancel_signal(src,SIGQUIT,stop_read,rd);
	oop_rd_delete_tidy(rd);
	return OOP_CONTINUE;
}

static void on_read_immed_err(oop_source *src, oop_read *rd) {
	puts("read: terminating");
	stop_read(src,0,rd);
}

static void on_read_std_err(oop_source *src, oop_read *rd) {
	static on_read_err_func *const next= on_read_immed_err;
	int r;

	puts("read: switching to plain immediate mode");
	r= oop_rd_read(rd,OOP_RD_STYLE_IMMED,0, on_read,0, on_read,(void*)&next);
	if (r) { perror("oop_rd_read[2]"); exit(1); }
}

static void read_bad(const char *errmsg) {
	fprintf(stderr,"invalid modes for read:...: %s\n",errmsg);
	usage();
}

static void add_read(oop_source *source, const char *modes) {
	static on_read_err_func *const next= on_read_std_err;

	oop_readable *ra;
	oop_read *rd;
	oop_rd_style style;
	size_t bufsz, maxrecsz;
	int r;
	char delimspec[3];
	char *ep;

	switch (*modes++) {
	case 'n': style.delim_mode= OOP_RD_DELIM_NONE;  break;
	case 's': style.delim_mode= OOP_RD_DELIM_STRIP; break;
	case 'k': style.delim_mode= OOP_RD_DELIM_KEEP;  break;
	default: read_bad("invalid delim_mode, must be one of nsk");
	}
	if (style.delim_mode != OOP_RD_DELIM_NONE) {
		switch ((delimspec[0] = *modes++)) {
		case '=': style.delim= *modes++; break;
		case 'n': style.delim= '\n'; break;
		case '\0': read_bad("missing delimiter");
		default:
			delimspec[1]= *modes++;
			delimspec[2]= 0;
			style.delim= strtoul(delimspec,&ep,16);
			if (ep != modes) read_bad("invalid delimiter");
		}
	}

	switch (*modes++) {
        case 'f': style.nul_mode= OOP_RD_NUL_FORBID;  break;
        case 'd': style.nul_mode= OOP_RD_NUL_DISCARD; break;
        case 'p': style.nul_mode= OOP_RD_NUL_PERMIT;  break;
	default: read_bad("invalid nul_mode, must be one of fdp");
	}

	switch (*modes++) {
        case 'f': style.shortrec_mode= OOP_RD_SHORTREC_FORBID;  break;
        case 'e': style.shortrec_mode= OOP_RD_SHORTREC_EOF;     break;
        case 'l': style.shortrec_mode= OOP_RD_SHORTREC_LONG;    break;
        case 's': style.shortrec_mode= OOP_RD_SHORTREC_SOONEST; break;
	default: read_bad("invalid shortrec_mode, must be one of fels");
	}

	maxrecsz= strtoul(modes,&ep,10);
	if (*ep && *ep != ',' && *ep != ':') read_bad("invalid maxrecsz");
	modes= *ep==',' ? ep+1 : ep;

	bufsz= strtoul(modes,&ep,10);
	if (*ep && *ep != ':') read_bad("invalid bufsz");

	if (*ep != ':') {
		ra= oop_readable_fd(source,0);
		if (!ra) { perror("oop_readable_fd"); exit(1); }
	} else {
		modes= ep+1;
		ra= oop_readable_mem(source,modes,strlen(modes));
		if (!ra) { perror("oop_readable_fd"); exit(1); }
	}		

	rd= oop_rd_new(source,ra,
		       bufsz ? malloc(bufsz) : 0,
		       bufsz);
	r= oop_rd_read(rd,&style,maxrecsz, on_read,0, on_read,(void*)&next);
	if (r) { perror("oop_rd_read"); exit(1); }
	source->on_signal(source,SIGQUIT,stop_read,rd);
}

/* -- core ----------------------------------------------------------------- */

static void *stop_loop_delayed(oop_source *source,struct timeval tv,void *x) {
	return OOP_HALT;
}

static void *stop_loop(oop_source *source,int sig,void *x) {
	/* give everyone else a chance to shut down. */
	source->on_time(source,OOP_TIME_NOW,stop_loop_delayed,NULL);
	source->cancel_signal(source,SIGQUIT,stop_loop,NULL);
	return OOP_CONTINUE;
}

static oop_source *create_source(const char *name) {
	if (!strcmp(name,"sys")) {
		source_sys = oop_sys_new();
		return oop_sys_source(source_sys);
	}

	if (!strcmp(name,"signal")) {
		source_sys = oop_sys_new();
		source_signal = oop_signal_new(oop_sys_source(source_sys));
		return oop_signal_source(source_signal);
	}

#ifdef HAVE_GLIB
	if (!strcmp(name,"glib")) {
		puts("glib: known bug: termination (^\\) won't quit, sorry.");
		glib_loop = g_main_new(FALSE);
		return oop_glib_new();
	}
#endif

	fprintf(stderr,"unknown source \"%s\"\n",name);
	usage();
	return NULL;
}

static void run_source(const char *name) {
	if (!strcmp(name,"sys")
	||  !strcmp(name,"signal")) {
		oop_sys_run_once(source_sys);
		oop_sys_run_once(source_sys);
		oop_sys_run_once(source_sys);
		oop_sys_run(source_sys);
	}

#ifdef HAVE_GLIB
	if (!strcmp(name,"glib"))
		g_main_run(glib_loop);
#endif
}

static void delete_source(const char *name) {
	if (!strcmp(name,"sys"))
		oop_sys_delete(source_sys);
	if (!strcmp(name,"signal")) {
		oop_signal_delete(source_signal);
		oop_sys_delete(source_sys);
	}

#ifdef HAVE_GLIB
	if (!strcmp(name,"glib")) {
		oop_glib_delete();
		g_main_destroy(glib_loop);
	}
#endif
}

static void add_sink(oop_source *src,const char *name) {
	if (!strcmp(name,"timer")) {
		add_timer(src,1);
		add_timer(src,2);
		add_timer(src,3);
		return;
	}

	if (!strcmp(name,"signal")) {
		src->on_signal(src,SIGINT,on_signal,NULL);
		src->on_signal(src,SIGQUIT,on_signal,NULL);
		return;
	}

	if (!strcmp(name,"echo")) {
		src->on_fd(src,0,OOP_READ,on_data,NULL);
		src->on_signal(src,SIGQUIT,stop_data,NULL);
		return;
	}

	if (!strcmp(name,"readline")) {
		add_readline(src);
		return;
	}

	if (!strcmp(name,"adns")) {
		add_adns(src);
		return;
	}

	if (!strcmp(name,"libwww")) {
		add_www(src);
		return;
	}

	if (!strncmp(name,"read:",5)) {
		add_read(src,name+5);
		return;
	}

	fprintf(stderr,"unknown sink \"%s\"\n",name);
	usage();
}

static void init(oop_source *source,int count,char *sinks[]) {
	int i;
	source->on_signal(source,SIGQUIT,stop_loop,NULL);
	puts("test-oop: use ^\\ (SIGQUIT) for clean shutdown or "
	     "^C (SIGINT) to stop abruptly.");

	for (i = 0; i < count; ++i)
		add_sink(source,sinks[i]);
}

/* -- tcl source ----------------------------------------------------------- */

#ifdef HAVE_TCL
static int tcl_count;
static char **tcl_sinks;

static int tcl_init(Tcl_Interp *interp) {
	init(oop_tcl_new(),tcl_count,tcl_sinks);
	return TCL_OK;
} 
#endif

/* -- main ----------------------------------------------------------------- */

int main(int argc,char *argv[]) {
	if (argc < 3) usage();

#ifdef HAVE_TCL
	if (!strcmp(argv[1],"tcl")) { /* Tcl is a little ... different. */
		tcl_count = argc - 2;
		tcl_sinks = argv + 2;
		Tcl_Main(1,argv,tcl_init);
	} else
#endif
	{
		oop_source * const source = create_source(argv[1]);
		init(source,argc - 2,argv + 2);
		run_source(argv[1]);
		delete_source(argv[1]);
	}

	return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1