/* Copyright (c) 2002
 *	Marko Boomstra (m.boomstra@chello.nl).  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.
 *
 * 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.
 */
#include <conf.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#ifdef HAVE_POLL 
  #include <sys/poll.h>
#endif
#include <netinet/in.h>
#include <arpa/telnet.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <fcntl.h>
#include <netdb.h>
#include <unistd.h>
#include <ctype.h>
#include <errno.h>
#include <ncurses.h>
#include <panel.h>
#include "mudix.h"

/*
 * Locals
 */
bool read_desc		(int desc);
void exit_mudix		(int status);
void new_term_set	(void);
void res_term_set	(void);
void do_connect		(int sock, struct sockaddr *server, int size);
int  init_socket	(struct sockaddr_in *server);
void init_handler	(void);

time_t			current_time;

struct  hostent        *host;

char	connect_name	[MAX_STRING];
char	connect_addr 	[MAX_STRING];

#ifdef HAVE_POLL
#define POLL_NR		2

enum pfds_types {
    PFDS_SOCK, PFDS_INPUT
};
#endif

/*
 * Globals, visible in all sources
 */
char  outbuf            [OUTBUF_LENGTH];
char  inbuf             [MAX_STRING];
char  send_buffer       [MAX_STRING];
char *pOut              = outbuf;
char *pEndIn            = inbuf;
char *pCursor           = inbuf;
bool  fProcessData	= FALSE;
bool  fDown 		= FALSE;
bool  fEchoIn 		= TRUE;
bool  fEchoOut 		= TRUE;
bool  fReconnect	= FALSE;
bool  fPrintTime        = TRUE;
bool  fKeypadWalk       = FALSE;
bool  fStatusReport     = TRUE;
bool  fPanelHidden      = 0;
int   statusTimer       = 0;
long  total_sent	= 0;
long  total_recv	= 0;

int   len_col;
int   len_row;

WINDOW *wTerm;
WINDOW *wMain;
WINDOW *wBanner;
WINDOW *wInput;
WINDOW *wMsg;
WINDOW *wStatus;
WINDOW *wScroll;
WINDOW *wDialog;

PANEL  *pMain;
PANEL  *pBanner;
PANEL  *pInput;
PANEL  *pMsg;
PANEL  *pStatus;
PANEL  *pScroll;
PANEL  *pDialog;

/*
 * Externals
 */
extern void init_history	(void);


/*
 * Main loop
 */
int main(int argc, char *argv[])
{
    	   int    	sock;
    struct sockaddr_in 	server;  
    struct timeval	tv;
    struct timeval 	last_time;
#ifdef HAVE_POLL 
    struct pollfd	pfds[POLL_NR];
#else
           fd_set       fd_read, fd_exc;
#endif
 
    if (argc != 3 && argc != 2) {
	fprintf(stdout, "Syntax: %s '<address> <port>' or '<file>'\n", argv[0]);
	exit(1);
    }
 
    init_handler();
    init_settings(argc, argv); 

    gettimeofday(&last_time, NULL);
    current_time = (time_t)last_time.tv_sec;

    sock = init_socket(&server);

    new_term_set();
    create_banner();

    do_connect(sock, (struct sockaddr *)&server, sizeof(server));

    init_history();
    create_banner();

    send_buffer[0] = '\0';
    tv.tv_sec = tv.tv_usec = 0;
#ifdef HAVE_POLL
    pfds[PFDS_SOCK].fd = sock;
    pfds[PFDS_SOCK].events = POLLIN | POLLHUP | POLLERR;
    pfds[PFDS_INPUT].fd = STDIN;
    pfds[PFDS_INPUT].events = POLLIN;
#endif
    while (!fDown) {
#ifdef HAVE_POLL
        if (poll(pfds, POLL_NR, 0) < 0) {
            do_status("Poll: fatal error, exiting...", 1);
            break;
        }
#else
        FD_ZERO(&fd_read);
        FD_ZERO(&fd_exc);
        FD_SET(sock, &fd_read);
        FD_SET(sock, &fd_exc);
        FD_SET(STDIN, &fd_read);

        if (select(sock+1, &fd_read, NULL, &fd_exc, &tv) < 0) {
            do_status("Select: fatal error, exiting...", 1);
	    break;
	}
#endif

#ifdef HAVE_POLL 
        if ((pfds[PFDS_SOCK].revents & POLLHUP) ||
            (pfds[PFDS_SOCK].revents & POLLERR)) {
#else
        if (FD_ISSET(sock, &fd_exc)) {
	    FD_CLR(sock, &fd_exc);
#endif
	    break;
        }

#ifdef HAVE_POLL 
        if ((pfds[PFDS_SOCK].revents & POLLIN)) {
#else
        if (FD_ISSET(sock, &fd_read)) {
	    FD_CLR(sock, &fd_read);
#endif
	    if (!read_desc(sock))
	        break;
	}

        if (fProcessData) 
	    process_buffer();

#ifdef HAVE_POLL 
        if ((pfds[PFDS_INPUT].revents & POLLIN)) {
#else
        if (FD_ISSET(STDIN, &fd_read)) {
	    FD_CLR(STDIN, &fd_read);
#endif
            read_input();
        }

	if (send_buffer[0]) {
	    if (!write_desc(sock)) {
	        do_status("Error writing data to socket! :(", 1);
	    	break;
	    }
	}

        touchwin(wInput);
	update_panels();
        doupdate(); 

	/*
	 * Sleep
	 */
        usleep(1);

        gettimeofday(&last_time, NULL);
	if (current_time != (time_t)last_time.tv_sec) {
	    if (statusTimer && --statusTimer == 0)
	        hide_panel(pStatus);
            current_time = (time_t)last_time.tv_sec;
	    create_banner();
	}

	if (fReconnect) {
	   fReconnect = FALSE;
	   init_settings(argc, argv);
	   close(sock);
	   sock = init_socket(&server);
    	   do_connect(sock, (struct sockaddr *)&server, sizeof(server));
	}
    }

    do_status("Connection closed.", 1);
    update_panels();
    doupdate();

    close_log();
    connect_name[0] = '\0';
    create_banner();
    exit_mudix(0);
    return 0;
}

void exit_mudix(int status)
{
    res_term_set();
    if (settings->sock)
        close(settings->sock);

    exit(status);
}

void init_handler(void)
{
    signal(SIGHUP, exit_mudix);
    signal(SIGINT, exit_mudix);
    signal(SIGQUIT, exit_mudix);
    signal(SIGILL, exit_mudix);
    signal(SIGTRAP, exit_mudix);
    signal(SIGABRT, exit_mudix);
    signal(SIGIOT, exit_mudix);
    signal(SIGFPE, exit_mudix);
    signal(SIGKILL, exit_mudix);
    signal(SIGUSR1, exit_mudix);
    signal(SIGSEGV, exit_mudix);
    signal(SIGUSR2, exit_mudix);
    signal(SIGPIPE, exit_mudix);
    signal(SIGTERM, exit_mudix);
#ifdef SIGSTKFLT
    signal(SIGSTKFLT, exit_mudix);
#endif
    signal(SIGCHLD, exit_mudix);
    signal(SIGCONT, exit_mudix);
    signal(SIGSTOP, exit_mudix);
    signal(SIGTSTP, exit_mudix);
    signal(SIGTTIN, exit_mudix);
    signal(SIGTTOU, exit_mudix);
}

bool read_desc(int desc)
{
    int nRead;

    nRead = read(desc, pOut, OUTBUF_LENGTH - (pOut - outbuf));
    if (nRead > 0) {
    	pOut += nRead;
    	total_recv += nRead;
    	fProcessData = TRUE;
    	return TRUE;
    }
    else if (nRead == 0 || errno == EWOULDBLOCK || errno == EAGAIN)
        return TRUE;
    else
        do_status("Read: fatal error encountered, exiting...", 1);

    return FALSE;
}

bool write_desc(int desc)
{
    static int cntWrite;
           int nrWrite;
           int toWrite = strlen(send_buffer);

    if ((nrWrite = write(desc, send_buffer+cntWrite, toWrite-cntWrite)) < 0)
	return FALSE;

    total_sent += nrWrite;
    if ((nrWrite+cntWrite) != toWrite)
 	cntWrite += nrWrite;
    else {
	cntWrite = 0;
        send_buffer[0] = '\0';	/* clear the buffer */
    }

    return TRUE;
}

void new_term_set(void)
{
    wTerm = initscr();
    start_color();
    cbreak();
    noecho();
    nonl();
    intrflush(stdscr, FALSE);
    keypad(stdscr, TRUE);
    getmaxyx(wTerm, LEN_ROW, LEN_COL);
    scroll_setup(MAX_LINES);

    wMain   = newwin(LEN_ROW-2, LEN_COL, 0, 0);
    wBanner = newwin(1, LEN_COL, LEN_ROW-2, 0);
    wInput  = newwin(1, LEN_COL, LEN_ROW-1, 0);
    wMsg    = newwin(0, 0, 2, 7);
    wStatus = newwin(8, LEN_COL, 0, 0);
    wScroll = newwin(SCROLL_SIZE+1, LEN_COL, 0, 0);
    wDialog = newwin(3, LEN_COL, 0, 0);

    if (!wMain || !wBanner
     	       || !wInput
     	       || !wMsg
     	       || !wStatus
     	       || !wDialog
     	       || !wScroll ) {
	endwin();
	fprintf(stdout, "Failed to create windows! :(\n");
	exit(1);
    }
     
    pMain   = new_panel(wMain);
    pBanner = new_panel(wBanner);
    pInput  = new_panel(wInput);
    pMsg    = new_panel(wMsg);
    pStatus = new_panel(wStatus);
    pScroll = new_panel(wScroll);
    pDialog = new_panel(wDialog);

    if (!pMain || !pBanner
     	       || !pInput
     	       || !pMsg
     	       || !pStatus
     	       || !pDialog
     	       || !pScroll ) {
	endwin();
	fprintf(stdout, "Failed to create panels! :(\n");
	exit(1);
    }

    idlok(wMain, TRUE);
    scrollok(wMain, TRUE);
    idlok(wStatus, TRUE);
    scrollok(wStatus, TRUE);
    idlok(wScroll, TRUE);
    scrollok(wScroll, TRUE);
    idlok(wMsg, TRUE);
    scrollok(wMsg, TRUE);

    init_pair(COL_BLACK, COLOR_BLACK, COLOR_BLACK);
    init_pair(COL_RED, COLOR_RED, COLOR_BLACK);
    init_pair(COL_GREEN, COLOR_GREEN, COLOR_BLACK);
    init_pair(COL_YELLOW, COLOR_YELLOW, COLOR_BLACK);
    init_pair(COL_BLUE, COLOR_BLUE, COLOR_BLACK);
    init_pair(COL_MAGENTA, COLOR_MAGENTA, COLOR_BLACK);
    init_pair(COL_CYAN, COLOR_CYAN, COLOR_BLACK);
    init_pair(COL_WHITE, COLOR_WHITE, COLOR_BLACK);
    init_pair(COL_BANNER, COLOR_YELLOW, COLOR_BLUE);
    init_pair(COL_MSG, COLOR_WHITE, COLOR_RED);
    init_pair(COL_STATUS, COLOR_WHITE, COLOR_BLUE);
    init_pair(COL_SCRL_BANNER, COLOR_WHITE, COLOR_CYAN);
    init_pair(COL_DIALOG, COLOR_WHITE, COLOR_GREEN);

    wattrset(wBanner, A_BOLD|COLOR_PAIR(COL_BANNER)); 
    wattrset(wStatus, A_BOLD|COLOR_PAIR(COL_STATUS)); 
    wattrset(wMsg, A_BOLD|COLOR_PAIR(COL_MSG)); 
    wattrset(wDialog, A_BOLD|COLOR_PAIR(COL_DIALOG)); 

    wbkgdset(wStatus, getbkgd(wStatus)|A_BOLD|COLOR_PAIR(COL_STATUS));
    wbkgdset(wMsg, getbkgd(wMsg)|A_BOLD|COLOR_PAIR(COL_MSG));
    wbkgdset(wDialog, getbkgd(wDialog)|A_BOLD|COLOR_PAIR(COL_DIALOG));

    wclear(wMain);
    wclear(wInput);
    wclear(wStatus);
    wclear(wScroll);
    wclear(wMsg);
    wclear(wDialog);

    hide_panel(pMsg);
    hide_panel(pStatus);
    hide_panel(pScroll);
    hide_panel(pDialog);

    /* Somehow this was changed in the panel library */
    if (panel_hidden(pMsg) != PANEL_HIDDEN)
    {
        PANEL_HIDDEN ^= 1;	/* toggle */
    }

    update_panels();
    doupdate();
}

void res_term_set(void)
{
    endwin();
    return;
}

void do_connect(int sock, struct sockaddr *server, int size)
{
    char buf[MAX_STRING];

    sprintf(buf, "Trying: %s (%d).", connect_addr, settings->port);
    do_status(buf, 4);
    update_panels();
    doupdate();

    if (connect(sock, server, size) == -1) {
	perror("%% Connect");
	exit_mudix(1);
    }
	
    sprintf(buf, "Connected to: %s.", host->h_name);
    do_status(buf, 4);
    update_panels();
    doupdate();

    sprintf(connect_name, "%s", host->h_name);

    if (fcntl(sock, F_SETFL, O_NDELAY) == -1) {
        perror("fcntl: O_NDELAY");
	exit_mudix(1);
    }

    return;
}

int init_socket(struct sockaddr_in *server) 
{
    int sock, addr;

    if (!(host = gethostbyname(settings->site))) {
	perror(settings->site);
	exit(1);
    } 

    if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
        perror("socket creation");
        exit(1);
    }
    
    memcpy(&server->sin_addr, host->h_addr, host->h_length);
    server->sin_family = host->h_addrtype;
    server->sin_port   = htons(settings->port);

    addr = ntohl(server->sin_addr.s_addr);
    sprintf(connect_addr, "%d.%d.%d.%d", 
				(addr >> 24) & 0xFF, (addr >> 16) & 0xFF,
            			(addr >>  8) & 0xFF, (addr      ) & 0xFF);
    connect_name[0] = '\0';

    setbuf(stdin, NULL);
    setbuf(stdout, NULL);

    if (fcntl(STDIN, F_SETFL, O_NDELAY) == -1) {
        perror("fcntl: O_NDELAY");
	exit(1);
    }

    settings->sock = sock;

    return sock;
}


syntax highlighted by Code2HTML, v. 0.9.1