/* ARISA - Shell, Standalone Executable to communicate with Arisa
 * Copyright (C) 2003, 2004 Carl Ritson
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
 */

#include "arisa-sh.h"

/** Macros **/
#define MAX(x,y)	((x) >= (y) ? (x) : (y))
#define MIN(x,y)	((x) <= (y) ? (x) : (y))

/** Global Variables **/
static int 	sok	= -1;
static int	batch	= 0;
static char	*prompt	= NULL;
static char	*sokbuf = NULL;
static int	sokpos	= 0;
static int	timestamp = 1;

/** Functions **/
static int has_pending_data(int s) {
	struct pollfd pfd;
	int ret;
	
	pfd.fd = s;
	pfd.events = POLLIN;
	ret = poll(&pfd,1,0);
	if(ret == -1)
		return -1;
	if(pfd.revents & (POLLERR | POLLHUP | POLLNVAL))
		return -1;
	else if(pfd.revents & POLLIN)
		return 1;
	else
		return 0;
}

static int recv_line(char *buffer, size_t bufsize) {
	int i,ret;
	
	if(sokpos != 0)
		strncpy(buffer,sokbuf,sokpos);
	
	ret = recv(sok,buffer+sokpos,bufsize-sokpos,MSG_PEEK);
	if(ret <= 0)
		return -1;
	for(i = 0; i < (ret+sokpos); ++i) {
		if(buffer[i] == '\n') {
			ret = recv(sok,buffer+sokpos,(i+1)-sokpos,0);
			if(ret <= 0)
				return -1;
			buffer[i] = '\0';
			if(i != 0)
				if(buffer[i-1] == '\r')
					buffer[i-1] = '\0';
			sokpos = 0;
			return strlen(buffer);
		}
	}
	ret = recv(sok,sokbuf+sokpos,ret,0);
	if(ret <= 0)
		return -1;
	sokpos += ret;
	if(sokpos >= 512)
		sokpos = 0; // clear
	return 0;
}

/** text based implementation **/
#include "sh-text.c"

/** (n)curses based implementation **/
#if defined(HAVE_NCURSES) || defined(HAVE_CURSES)
#include "sh-curses.c"
#else
static int ncurses_main(void) { 
	fprintf(stderr,"Curses support is not compiled in.");
	return 1; 
}
#endif /* defined(HAVE_NCURSES) || defined(HAVE_CURSES) */


static void usage(void) {
	fprintf(stderr,	"arisa-sh [-b] [-c] <socket path> | <host:port>\n"
			"  -b\t\tbatch mode, no output to console\n"
			"  -c\t\tuse curses\n"
			"  -t\t\tturn off timestamp\n"
			"  -?\t\tthis dialog\n"
			"\nReport Bugs to <bugreport@arisa-chan.org>.\n"
	);
	exit(0);
}

static void connect_dccserver(const char *path) {
	struct sockaddr_in addr;
	char buffer[512];
	struct hostent *hent;
	int ret,len;
	char *host,*port;
	
	host = strdup(path);
	port = strchr(host,':');
	if(port == NULL) {
		fprintf(stderr,"Invalid Address: %s\n",path);
		exit(1);
	}
	*(port++) = '\0';

	hent = gethostbyname(host);
	if(hent == NULL) {
		fprintf(stderr,"Unable to resolve host %s: %s\n",
				host,gai_strerror(ret));
		exit(1);
	}
	addr.sin_family = AF_INET;
	addr.sin_port = htons((unsigned short)atoi(port));
	memcpy(&(addr.sin_addr),hent->h_addr,sizeof(struct in_addr));
	
	ret = socket(PF_INET,SOCK_STREAM,0);
	if(ret == -1) {
		perror("Failed to get socket");
		exit(1);
	}
	sok = ret;

	ret = connect(sok,(struct sockaddr *)&addr,sizeof(addr));
	if(ret == -1) {
		perror("Failed to connect");
		exit(1);
	}
	free(host);

	len = sprintf(buffer,"100 Admin\r\n");
	ret = send(sok,buffer,len,0);
	if(ret == -1) {
		perror("Failed to negotiate");
		exit(1);
	}
	alarm(60);
	while((ret = recv_line(buffer,sizeof(buffer))) == 0) ;
	if(ret == -1) {
		perror("Failed to negotiate");
		exit(1);
	}
	host = strchr(buffer,' ');
	if(host != NULL)
		*host = '\0';
	if(atoi(buffer) != 101) {
		fprintf(stderr,"Failed to negotiate with dccserver: %s\n",buffer);
		exit(1);
	}
	ret = 1;
	setsockopt(sok,SOL_SOCKET,SO_KEEPALIVE,&ret,sizeof(ret));
	alarm(0);
}

#ifdef HAVE_SYS_UN_H
static void connect_unix(const char *path) {
	struct sockaddr_un addr;
	int ret;
	
	addr.sun_family = AF_UNIX;
	snprintf(addr.sun_path,sizeof(addr.sun_path),"%s",path);
	
	ret = socket(PF_UNIX,SOCK_STREAM,0);
	if(ret == -1) {
		perror("Failed to get socket");
		exit(1);
	}
	sok = ret;
	ret = connect(sok,(struct sockaddr *)&addr,sizeof(addr));
	if(ret == -1) {
		perror("Failed to connect");
		exit(1);
	}
	ret = 1;
	setsockopt(sok,SOL_SOCKET,SO_KEEPALIVE,&ret,sizeof(ret));
}
#else
static void connect_unix(const char *path) {
	fprintf(stderr,"Unix socket support is not compiled, unable to connect to %s.\n",path);
}
#endif /* HAVE_SYS_UN_H */

static void login(void) {
	char buffer[512],linebuf[96],*line;
	int ret,len;
	alarm(60);
	for(;;) {
		while((ret = recv_line(buffer,sizeof(buffer))) == 0) ;
		if(ret == -1) {
			perror("Socket Error");
			exit(1);
		} else if(buffer[0] == '[' && buffer[strlen(buffer)-1] == ']') {
			prompt = strdup(buffer);
			if(!batch)
				printf("%s\n",buffer);
			break;
		}
		if(!batch) {
			printf("%s\n",buffer);
			line = NULL;
#ifdef HAVE_TERMIOS_H
			if(strcasecmp(buffer,"Password:") == 0) {
				struct termios old,new;
				if(tcgetattr(fileno(stdin),&old) == 0) {
					new = old;
					new.c_lflag &= ~ECHO;
					tcsetattr(fileno(stdin),TCSAFLUSH,&new);
				}
				line = lreadline(stdin,linebuf,sizeof(linebuf));
				if(line == NULL)
					line = "";
				tcsetattr(fileno(stdin),TCSAFLUSH,&old);
			} else
				line = NULL;
#endif /* HAVE_TERMIOS_H */
		} else
			line = NULL;
		if(line == NULL)
			line = lreadline(stdin,linebuf,sizeof(linebuf));
		if(line == NULL)
			exit(1);
		len = sprintf(buffer,"%s\r\n",line);
		ret = send(sok,buffer,len,0);
		if(ret == -1) {
			perror("Socket Error");
			exit(1);
		}
	}
	alarm(0);
}

static void sigalarm(int sig) {
	// print in signal handler is bad, don't try this at home kids, mm'kay
	fprintf(stderr,"Timed Out\n");
	exit(1);
}

int main(int argc, char *argv[]) {
	const char *opts = "bcet";
	char *path = NULL;
	int c,curses = 0;
	
	for(;;) {
		c = getopt(argc,argv,opts);
		if(c == -1)
			break;
		switch(c) {
			case 'b':
				batch = 1;
				curses = 0;
				break;
			case 'e':
				timestamp = 2;
				break;
			case 'c':
				curses = 1;
				break;
			case 't':
				timestamp = 0;
				break;
			case '?':
				usage();
				break;
		}
	}
	if(optind < argc)
		path = argv[optind];
	if(path == NULL)
		usage();
	
	sokbuf = malloc(1024);
	
	signal(SIGHUP,SIG_IGN);
	signal(SIGALRM,sigalarm);
	if(strchr(path,':') != NULL)
		connect_dccserver(path);
	else
		connect_unix(path);
	
	login();

	if(curses == 0)
		return console_main();
	else
		return ncurses_main();
}
