/*
 * Copyright (c) 1991-1994 Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. 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.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the Computer Systems
 *	Engineering Group at Lawrence Berkeley Laboratory.
 * 4. Neither the name of the University nor of the Laboratory may be used
 *    to endorse or promote products derived from this software without
 *    specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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.
 */
static const char rcsid[] =
    "@(#) $Header: main.cc,v 1.48 96/05/03 06:27:20 van Exp $ (LBL)";

#include "config.h"
#include <math.h>
#include <signal.h>
#include <ctype.h>
#ifdef WIN32
#include <process.h>
#else
#include <sys/param.h>
#include <sys/utsname.h>
#include <sys/wait.h>
#endif
#include "sys-time.h"
#ifdef sgi
#include <getopt.h>
#endif
#ifdef _AIX
#include <sys/socket.h>
#endif

extern "C" {
#include <tk.h>
}

#ifndef WIN32
#include <netdb.h>
#endif

#include "inet.h"
#include "Tcl.h"

extern "C" int Tk_StripchartCmd(ClientData, Tcl_Interp*, int ac, const char** av);
#ifdef WIN32
extern "C" int WinPutsCmd(ClientData, Tcl_Interp*, int ac, char** av);
extern "C" int WinGetUserName(ClientData, Tcl_Interp*, int ac, char** av);
extern "C" int WinPutRegistry(ClientData, Tcl_Interp*, int ac, char** av);
extern "C" int WinGetRegistry(ClientData, Tcl_Interp*, int ac, char** av);
#endif

/*XXX*/
#define PROTOTYPES 1 
#include "global.h"
#include "md5.h"

#if defined(sun) && defined(__svr4__)
#include <sys/utsname.h>
#define gethostname(name, len) { \
	struct utsname _uts_; \
\
	if (uname(&_uts_) < 0) { \
		perror("uname"); \
		exit(1); \
	}\
\
	strcpy((name), _uts_.nodename); \
}
#endif
 
static void
usage()
{
	fprintf(stderr, "\
usage: vat [-aAcdEjJkLMnrRsSv] [-C conf] [-D dump] [-f format] [-F device]\n\
\t[-g geometry] [-d display] [-I chan] [-K key] [-N name] [-K key ]\n\
\t[-t ttl] [-u script] [-U socket] [-X res=val] dest/port[/fmt/ttl]\n"
	);
	exit(1);
}

static class UsageCommand : public TclObject {
public:
	UsageCommand() : TclObject("usage") {}
	int command(int argc, const char*const* argv) {
		usage();
		/*NOTREACHED*/
		return (0);
	}
} cmd_usage;

#ifndef SIGARGS
#define SIGARGS ...
#endif

#ifdef SIGCHLD
static SIGRET ReapChild(SIGARGS)
{
	while (waitpid(-1, 0, WNOHANG) > 0)
		;
}
#endif

extern void adios();

static SIGRET DoQuit(SIGARGS)
{
	adios();
}

void initcatchers()
{
	signal(SIGINT, (void (*)(int))DoQuit);
#ifdef SIGHUP
	signal(SIGHUP, (void (*)(int))DoQuit);
#endif
	signal(SIGTERM, (void (*)(int))DoQuit);
#ifdef SIGCHLD
	signal(SIGCHLD, (void (*)(int))ReapChild);
#endif
}

static class AdiosCommand : public TclObject {
public:
	AdiosCommand() : TclObject("adios") {}
	int command(int argc, const char*const* argv) {
		adios();
		/*NOTREACHED*/
		return (0);
	}
} cmd_adios;

static class HaveFontCommand : public TclObject {
public:
	HaveFontCommand() : TclObject("havefont") {}
	int command(int argc, const char*const* argv) {
		Tcl& t = Tcl::instance();
		if (argc != 2)
			t.result("0");
		else {
			Tk_Window tk = t.tkmain();
			Tk_Uid uid = Tk_GetUid((char*)argv[1]);
			Tk_Font p = Tk_GetFont(t.interp(), tk, uid);
			t.result(p != 0 ? "1" : "0");
		}
		return (TCL_OK);
	}
} cmd_havefont;

static class GetHostNameCommand : public TclObject {
public:
	GetHostNameCommand() : TclObject("gethostname") {}
	int command(int argc, const char*const* argv) {
		Tcl& tcl = Tcl::instance();
		char* bp = tcl.buffer();
		tcl.result(bp);
		gethostname(bp, MAXHOSTNAMELEN);
		return (TCL_OK);
	}
} cmd_gethostname;

extern "C" char version[];

static class VersionCommand : public TclObject {
public:
	VersionCommand() : TclObject("version") {}
	int command(int argc, const char*const* argv) {
		Tcl::instance().result(version);
		return (TCL_OK);
	}
} cmd_version;

extern "C" char *optarg;
extern "C" int optind;
extern "C" int opterr;

int yesno(const char* s)
{
	if (s == 0)
		return (0);
	if (isdigit(*s))
		return (atoi(s));
	int ret;
	switch (*s) {
	case 't': case 'T':
	case 'y': case 'Y':
	case '1':
		ret = 1;
		break;
	case 'o': case 'O':
		ret = (s[1] == 'n' || s[1] == 'N');
		break;
	default:
		ret = 0;
		break;
	}
	return (ret);
}

#ifndef sgi
inline
#endif
static void
set_option(Tk_Window tk, const char* name, const char* value)
{
	char wrk[80];
	sprintf(wrk, "Vat.%s", name);
	Tk_AddOption(tk, wrk, (char*)value, TK_USER_DEFAULT_PRIO + 1);
}

static void
toggle_option(const char* name)
{
	Tcl& tcl = Tcl::instance();
	const char* value = tcl.attr(name);
	if (value != 0 && yesno(value))
		tcl.add_option(name, "0");
	else
		tcl.add_option(name, "1");
}

static int
is_whitespace(const char* s)
{
	int c;
	while ((c = *s++) != 0)
		if (!isspace(c))
			return (0);
	return (1);
}

#if defined(__hpux) || defined(sco)
#ifdef sco
#define gethostid xgethostid
#else
#include <sys/socket.h>
#endif
gethostid()
{
	int id;
	char hostname[256];		/* 255 is max legal DNS name */
	size_t hostname_size = 256;
	struct hostent *hostp;
	struct in_addr addru;		/* union for conversion */

	(void) gethostname(hostname, hostname_size);
	hostname[hostname_size] = '\0'; /* make sure it is null-terminated */

	hostp = gethostbyname(hostname);
	if(hostp == NULL)
		/* our own name was not found!  punt. */
		id = 0;
	else {
		/* return first address of host */
		memcpy(&(addru.s_addr), hostp->h_addr_list[0], 4);
		id = addru.s_addr;
	}
  
	return id;
}
#endif

#ifdef __svr4__
#include <sys/systeminfo.h>
#define gethostid xgethostid
gethostid()
{
	char wrk[32];
	if (sysinfo(SI_HW_SERIAL, wrk, sizeof(wrk)) > 0)
		return (atoi(wrk));
	return (0);
}
#endif

/*
 * From the RTP spec.
 */
u_int32_t
heuristic_random()
{
	struct {
		struct  timeval tv;
		clock_t cpu;
		pid_t   pid;
		u_long  hid;
		uid_t   uid;
		gid_t   gid;
		struct  utsname name;
	} s;

	gettimeofday(&s.tv, 0);
	uname(&s.name);
	s.cpu  = clock();
	s.pid  = getpid();
	s.hid  = gethostid();
	s.uid  = getuid();
	s.gid  = getgid();

	MD5_CTX context;
	MD5Init(&context);
	MD5Update(&context, (u_char*)&s, sizeof(s));
	u_int32_t out[4];
	MD5Final((u_char *)out, &context);
	return (out[0] ^ out[1] ^ out[2] ^ out[3]);
}

#include "bitmaps/speaker.xbm"
#include "bitmaps/headphone.xbm"
#include "bitmaps/lineout.xbm"
#include "bitmaps/lineout2.xbm"
#include "bitmaps/lineout3.xbm"
#include "bitmaps/mic.xbm"
#include "bitmaps/linein.xbm"
#include "bitmaps/linein2.xbm"
#include "bitmaps/linein3.xbm"
#include "bitmaps/square.xbm"
void
loadbitmaps(Tcl_Interp* tcl)
{
	Tk_DefineBitmap(tcl, Tk_GetUid("speaker"),
			speaker_bits, speaker_width, speaker_height);
	Tk_DefineBitmap(tcl, Tk_GetUid("headphone"),
			headphone_bits, headphone_width, headphone_height);
	Tk_DefineBitmap(tcl, Tk_GetUid("lineout"),
			lineout_bits, lineout_width, lineout_height);
	Tk_DefineBitmap(tcl, Tk_GetUid("lineout2"),
			lineout2_bits, lineout2_width, lineout2_height);
	Tk_DefineBitmap(tcl, Tk_GetUid("lineout3"),
			lineout3_bits, lineout3_width, lineout3_height);
	Tk_DefineBitmap(tcl, Tk_GetUid("mike"),
			mic_bits, mic_width, mic_height);
	Tk_DefineBitmap(tcl, Tk_GetUid("linein"),
			linein_bits, linein_width, linein_height);
	Tk_DefineBitmap(tcl, Tk_GetUid("linein2"),
			linein2_bits, linein2_width, linein2_height);
	Tk_DefineBitmap(tcl, Tk_GetUid("linein3"),
			linein3_bits, linein3_width, linein3_height);
	Tk_DefineBitmap(tcl, Tk_GetUid("square"),
			square_bits, square_width, square_height);
}

const char*
disparg(int argc, const char*const* argv, const char* optstr)
{
	const char* display = 0;
	int op;
	while ((op = getopt(argc, (char**)argv, (char*)optstr)) != -1) {
		if (op == 'd') {
			display = optarg;
			break;
		}
		else if (op == '?')
			usage();
	}
#ifdef linux
	optind = 0;
#else
	optind = 1;
#endif
	return (display);
}

char*
parse_assignment(char* cp)
{
	cp = strchr(cp, '=');
	if (cp != 0) {
		*cp = 0;
		return (cp + 1);
	} else
		return ("true");
}

extern "C" {
int
TkPlatformInit(Tcl_Interp *interp)
{
	Tcl_SetVar(interp, "tk_library", ".", TCL_GLOBAL_ONLY);
#ifndef WIN32
	extern void TkCreateXEventSource(void);
	TkCreateXEventSource();
#endif
	return (TCL_OK);
}
}

int
main(int argc, char **argv)
{
	srandom(heuristic_random());

	opterr = 0;
	const char* options = "aAB:cC:d:DEf:F:g:I:jJkK:lLMnN:p:P:rRsSt:U:u:X:";
	const char* display = disparg(argc, (const char*const*)argv, options);

	Tcl::init("vat");
	Tcl& tcl = Tcl::instance();
#ifdef WIN32
	if (display == NULL)
		display = "localhost:0";
#endif
#ifdef TCL_WINDOW_EVENTS
	tcl.evalf(display?
		    "set argv \"-name vat -display %s\"" :
		    "set argv \"-name vat\"",
		  display);
	Tk_Window tk = 0;
	if (Tk_Init(tcl.interp()) == TCL_OK)
		tk = Tk_MainWindow(tcl.interp());
#else
	Tk_Window tk = Tk_CreateMainWindow(tcl.interp(),
					   (char*)display, "vat", "Vat");
#endif
	if (tk == 0) {
		fprintf(stderr, "vat: %s\n", tcl.result());
		exit(1);
	}
	tcl.tkmain(tk);

	loadbitmaps(tcl.interp());
	tcl.CreateCommand("stripchart", Tk_StripchartCmd, (ClientData)tk);
#ifdef WIN32
	tcl.CreateCommand("puts", WinPutsCmd, (ClientData)tk);
	tcl.CreateCommand("getusername", WinGetUserName, (ClientData)tk);
	tcl.CreateCommand("putregistry", WinPutRegistry, (ClientData)tk);
	tcl.CreateCommand("getregistry", WinGetRegistry, (ClientData)tk);
#endif
	EmbeddedTcl::init();
	tcl.evalc("init_resources");

	int op;
	while ((op = getopt(argc, argv, (char*)options)) != -1) {
		switch (op) {

		default:
			usage();
			exit(1);

		case 'a':
			toggle_option("speakerAGC");
			break;
		case 'A':
			toggle_option("mikeAGC");
			break;
		case 'B':
			tcl.add_option("maxbw", optarg);
			break;
		case 'c':
			tcl.add_option("lectureMode", "0");
			break;
		case 'C':
			tcl.add_option("conferenceName", optarg);
			break;
		case 'd':
			/* display (already processed) */
			break;
		case 'D':
			tcl.add_option("meterDisable", "1");
			break;
		case 'E':
			tcl.add_option("externalEchoCancel", "1");
			break;
		case 'f':
			tcl.add_option("audioFormat", optarg);
			break;
		case 'F':
			tcl.add_option("audioFileName", optarg);
			break;
		case 'g':
			tcl.add_option("geometry", optarg);
			break;
		case 'I':
			tcl.add_option("confBusChannel", optarg);
			break;
		case 'j':
			tcl.add_option("outputPort", "Jack");
			break;
		case 'J':
			toggle_option("speakerMute");
			break;
		case 'k':
			tcl.add_option("keepSites", "1");
			break;
		case 'K':
			/*XXX probably do not want this in X server*/
			tcl.add_option("sessionKey", optarg);
			break;
		case 'l':
			tcl.add_option("lectureMode", "1");
			break;
		case 'L':
			tcl.add_option("keepSites", "0");
			break;
		case 'M':
			toggle_option("mikeMute");
			break;
		case 'N':
			tcl.add_option("rtpName", optarg);
			break;
		case 'n':
			tcl.add_option("sessionType", "vat");
			break;
		case 'P':
			tcl.add_option("defaultPriority", optarg);
			break;
		case 'r':
			tcl.add_option("sessionType", "rtp");
			break;
		case 'R':
			tcl.add_option("recvOnly", "true");
			break;
		case 's':
			tcl.add_option("outputPort", "Speaker");
			break;
		case 'S':
			tcl.add_option("muteNewSites", "1");
			break;
		case 't':
			tcl.add_option("defaultTTL", optarg);
			break;
		case 'u':
			tcl.add_option("startupScript", optarg);
			break;
		case 'U':
			if (optarg[0] >= '0' && optarg[0] <= '9') {
				tcl.add_option("defaultDevice", "af");
				tcl.add_option("useAF", "true");
				tcl.add_option("afDevice", optarg);
			} else
				tcl.add_option("defaultDevice", optarg);
			break;
		case 'X':
			{
				const char* value = parse_assignment(optarg);
				tcl.add_option(optarg, value);
			}
			break;
		}
	}

	const char* dst;
	if (optind < argc && argc > 1) {
		dst = argv[optind];
		if (argc - optind > 1) {
			fprintf(stderr,
				"vat: extra arguments (starting with `%s')\n",
				argv[optind + 1]);
			exit(1);
		}
	} else if ((dst = tcl.attr("defaultHostSpec")) == 0) {
		fprintf(stderr, "vat: destination address required\n");
		exit(1);
	}
	tcl.add_option("defaultHostSpec", dst);

	tcl.evalc("vat_main");
	initcatchers();
	/* win32 needs the following to get the initial window painted */
	tcl.evalc("update idletasks");
	Tk_MainLoop();
	adios();

	/* NOTREACHED*/
	return (0);
}
