/*
    Copyright (C) 2001 Paul Davis
    
    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., 675 Mass Ave, Cambridge, MA 02139, USA.

    $Id: remote_kbd.cc,v 1.8 2003/01/26 05:36:21 trutkin Exp $
*/

#include <ncurses.h>
#include "compat.h"

#include <iostream>

#include <errno.h>
#include <sys/poll.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <linux/keyboard.h>
#include <sigc++/signal_system.h>
#include <netdb.h>

#include <pbd/error.h>
#include <pbd/failed_constructor.h>
#include <pbd/textreceiver.h>

#include "physical_keyboard.h"

#include "i18n.h"

PhysicalKeyboard *kbd;
int server_socket;
int client_socket;
int keyboard;
bool have_client = false;

int
key_press_handler (int keyboard_state, int key)

{
	if ((keyboard_state & PhysicalKeyboard::Control) &&
	    (keyboard_state & PhysicalKeyboard::Shift) &&
	    (key == 28 || key == '\\')) { /* print screen/sys rq */
		
		/* we're done */
		
		return 1;
	}

	if (! have_client) {
                // JHALL: we can't put this in key_event_handler and get useful results because
                // JHALL: keyboard_state isn't set yet.
		kbd->print_key(key);
	}

	return 0;
}

int
key_event_handler (unsigned char key)

{
	if (! have_client) {
		return 0;
	}

	if (write (client_socket, &key, 1) != 1) {
		error << compose(_("cannot write key to socket (%1)"), strerror (errno)) << endmsg;
		return -1;
	}
	return 0;
}

int
make_socket ()
{
	int fd;
	struct sockaddr_in addr;
	struct servent *srv;

	if ((fd = socket (AF_INET, SOCK_STREAM, 6)) < 0) {
		error << _("cannot create server socket") << endmsg;
		return -1;
	}

	addr.sin_family = AF_INET;
	if ((srv = getservbyname("remote_kbd", "tcp")) != 0) {
		addr.sin_port = srv->s_port;
	} else {
		addr.sin_port = htons(9763);
	}
	addr.sin_addr.s_addr=htonl(INADDR_ANY);

	if (bind (fd, (struct sockaddr *) &addr, sizeof (addr)) < 0) {
		error << compose(_("cannot bind server to socket (%1"), strerror (errno)) << endmsg;
		close (fd);
		return -1;
	}

	if (listen (fd, 1) < 0) {
		error << _("cannot enable listen on server socket") << endmsg;
		close (fd);
		return -1;
	}

	return fd;
}

void
io_loop ()

{
	bool done = false;
	struct pollfd pfd[3];
	char buf[256];
	struct sockaddr_in client_addr;
	socklen_t client_addrlen;

	while (!done) {
		
		pfd[0].fd = server_socket;
		pfd[0].events = POLLIN | POLLERR;

		pfd[1].fd = keyboard;
		pfd[1].events = POLLIN | POLLERR;

		if (have_client) {
			pfd[2].fd = client_socket;
			pfd[2].events = POLLIN | POLLERR | POLLHUP;
		} else {
			pfd[2].revents = 0;
		}
		
		if (poll (pfd, have_client ? 3 : 2, 1000) < 0) {
			if (errno == EINTR) {
				cout << "poll interrupted\n";
				// this happens mostly when run
				// under gdb, or when exiting due to a signal
				continue;
			}

			error << compose(_("tty: poll call failed (%1)"), strerror (errno)) << endmsg;
			break;
		}
		
		if (pfd[0].revents & POLLERR) {
			error << _("socket: poll reports error.") << endmsg;
			break;
		}

		if (pfd[0].revents != 0) {
			memset (&client_addr, 0, sizeof (client_addr));
			client_addrlen = sizeof (client_addr);

			if ((client_socket = accept (server_socket, (struct sockaddr *) &client_addr, &client_addrlen)) < 0) {
				error << compose(_("cannot accept new connection (%1"), strerror (errno)) << endmsg;
				continue;
			} else {
				have_client = true;
			}

			if (fcntl (client_socket, F_SETFL, O_NONBLOCK) < 0) {
				error << _("cannot set non-block on client socket") << endmsg;
				close (client_socket);
				have_client = false;
			}
		}

		if (pfd[1].revents != 0) {
			if (kbd->handle_input ()) {
				break;
			}
		}

		if (pfd[2].revents & (POLLHUP|POLLERR)) {
			break;
		}

		if (pfd[2].revents != 0) {
			while (1) {
				int n;

				n = read (client_socket, buf, sizeof(buf));
				
				if (n == 0) {
					have_client = false;
					done = true;
					break;
				}

				if (n < 0) {
					if (errno != EAGAIN) {
						have_client = false;
						done = true;
						break;
					} else {
						break;
					}
				}

				if (write (1, buf, n) != n) {
					error << compose(_("could not forward %1 bytes to stdout (%2)"), n, strerror (errno)) << endmsg;
				}

			}
		}
	}

	return;
}

Transmitter  error (Transmitter::Error);
Transmitter  info (Transmitter::Info);
Transmitter  fatal (Transmitter::Fatal);
Transmitter  warning (Transmitter::Warning);
TextReceiver text_receiver ("ksi");

int
main (int argc, char *argv[])

{
	text_receiver.listen_to (error);
	text_receiver.listen_to (info);
	text_receiver.listen_to (fatal);
	text_receiver.listen_to (warning);

	if ((server_socket = make_socket()) < 0) {
		exit (1);
	}

	initscr();
	cbreak();
	noecho();
	nonl();
	werase (stdscr);

	try {
		kbd = new PhysicalKeyboard;
	}

	catch (failed_constructor& err) {
		error << _("could not create keyboard object") << endmsg;
		goto out;
	}

	/* connect the raw event handler that forwards key events */
	
	kbd->key_event.connect (SigC::slot (key_event_handler));
	
	/* allow a little bit of keystroke interpretation */

	kbd->key_press.connect (SigC::slot (key_press_handler));

	keyboard = kbd->selectable();

	io_loop ();

	delete kbd;
  out:
	endwin ();
}
