#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <netdb.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>

extern int h_errno;

#define MAXBUF 80

short int gotOOB = 0;

void child_func(int childnum, struct in_addr servIP, int port);

void blad(char * message)
{
	fprintf(stderr, "%s\n", message);
	exit(1);
}

struct in_addr inet_addr_from_any(char * addr)
{
	struct hostent *host;
	struct in_addr in;

	in.s_addr = inet_addr(addr);

	if (in.s_addr == -1)
	{
		if ((host = gethostbyname(addr)))
		{
			in = *((struct in_addr *)(host->h_addr_list[0]));
			return in;
		} else {
			blad("nieprawidlowy adres");
		}
	}
	return in;
}

int isNumber(char * string)
{
	int i = 0;
	for (i = 0; string[i] != 0; ++i)
	{
		if (string[i] < '0' || string[i] > '9')
		{
			return 0;
		}
	}
	return 1;
}

int anyPort_to_int(char * string)
{
	FILE * potok;
	char buffer[MAXBUF];
	int readed;
	char * portStart;
	char * cp;

	if (isNumber(string))
	{
		portStart = string;
	} else {
		snprintf(buffer, sizeof(buffer), "getent services %s", string);
		potok = popen(buffer, "r");
		if (potok == NULL) blad("popen() error");
		if (feof(potok)) blad("can't find given port number");
		readed = fread(buffer, sizeof(char), sizeof(buffer), potok);
		if (ferror(potok)) blad("fread() error");
		if (readed == 0) blad("can't find given port number");
		buffer[readed - 1] = 0;

		for (cp = buffer; *cp != '/'; cp += sizeof(char))
		{
			if (*(cp - sizeof(char)) == ' ' && *cp >= '0' && *cp <= '9')
				portStart = cp;
		}
		cp = 0;
	}
	return atoi(portStart);
}

void handleSigUrg(int s)
{
	printf("[!] Otrzymalem sygnal URG\n");
	gotOOB = 1;
	return;
}

void handleSigAlarm(int s)
{
	printf("[!] Otrzymalem sygnal Alarm\n");
	return;
}

void handleSigPipe(int s)
{
	printf("[!] Otrzymalem sygnal PIPE\n");
	return;
}

int main(int argc, char *argv[])
{
	struct in_addr servIP;
	int port;
	int sock;
	struct sockaddr_in sAddr;
	char buffer2[MAXBUF];
	int ret;
	struct sigaction sigactionPipe;
	fd_set listaSelect, listaSelectOOB;
	struct timeval selectTimeout;
	long timeout = 3;
	char c;
	struct sigaction sigactionUrg;
	struct sigaction sigactionAlarm;
	struct sigaction sigactionDefault;
	int wyslaneOOB = 0;
	int limitOOB = 3;

	sigactionDefault.sa_handler = SIG_DFL;

	sigactionPipe.sa_handler = handleSigPipe;
	sigaction(SIGPIPE, &sigactionPipe, NULL);

	if (argc < 3) {
		fprintf(stderr, "Uzycie: %s <serwer> <port>\n",
		        argv[0]);
		exit(1);
	}
	servIP = inet_addr_from_any(argv[1]);
	port = anyPort_to_int(argv[2]);
	printf("adres IP: %s\n", inet_ntoa(servIP)); /* DEBUG */
	printf("    port: %i\n", port); /* DEBUG */

	sock = socket(PF_INET, SOCK_STREAM, 0);
	sAddr.sin_family = AF_INET;
	sAddr.sin_addr.s_addr = servIP.s_addr;
	sAddr.sin_port = htons(port);

	if (connect(sock, (const struct sockaddr *) &sAddr, sizeof(sAddr)) != 0) {
		perror("klient");
		exit(1);
	}

	sigactionUrg.sa_handler = handleSigUrg;
	sigaction(SIGURG, &sigactionUrg, NULL);

	sigactionAlarm.sa_handler = handleSigAlarm;
	sigaction(SIGALRM, &sigactionAlarm, NULL);

	errno = (int) NULL;
	ret = fcntl(sock, F_SETOWN, (long) getpid());
	if (ret == -1)
	{
		perror("fcntl()");
		exit(EXIT_FAILURE);
	}


	while (1)
	{
		printf("Wprowadz tekst do wyslania: ");
		bzero(buffer2, sizeof(char) * sizeof(buffer2));
		ret = read(0, buffer2, sizeof(buffer2));
		printf("DEBUG: z klawiatury: read(): %i\n", ret);
		if (ret < 2)
		{
			printf("DEBUG: uzytkownik zazyczyl sobie wyjscie\n");
			break;
		}
		errno = (int) NULL;
		ret = write(sock, buffer2, strlen(buffer2));
		printf("DEBUG: write(): %i\n", ret); 
		if (ret == -1)
		{
			perror("write()");
		}

		bzero(buffer2, sizeof(char) * sizeof(buffer2));
		while (1)
		{
			if (gotOOB == 1)
			{
				gotOOB = 0;
				recv(sock, &c, 1, MSG_OOB);
				if (c == 'I')
				{
					printf("DEBUG: otzymalem ping request i odpowiadam na niego\n");
					c = 'O'; /* pOng */
					send(sock, &c, 1, MSG_OOB);
				} else if (c == 'O') {
					printf("DEBUG: otrzymalem odpowiedz na ping\n");
					wyslaneOOB = 0;
				} else {
					printf("DEBUG: otrzymalem nieznany rodzaj OOB\n");
				}
			}
			selectTimeout.tv_sec = timeout;
			selectTimeout.tv_usec = 0;
			FD_ZERO(&listaSelect);
			FD_SET(sock, &listaSelect);
			FD_ZERO(&listaSelectOOB);
			FD_SET(sock, &listaSelectOOB);
			sigaction(SIGURG, &sigactionDefault, NULL);
			/* Jesli w czasie wywolania select() program otrzyma SIGURG, to
			 * nastepuje blad segmentacji. Nie wiem czy tak powinno byc, ale
			 * obchodze to wylaczajac przechwytywanie na ten czas SIGURG i
			 * uzywajac select() do sprawdzenia czy nie dostalismy OOB.
			 */
			errno = (int) NULL;
			ret = select(FD_SETSIZE, &listaSelect, NULL, &listaSelectOOB, &selectTimeout);
			sigaction(SIGURG, &sigactionUrg, NULL);
			printf("DEBUG: select(): %i\n", ret);
			if (ret == -1)
			{
				perror("select()");
			}
			if (FD_ISSET(sock, &listaSelectOOB)) {
				printf("DEBUG: select() otrzymal OOB\n");
				gotOOB = 1;
			} else if (FD_ISSET(sock, &listaSelect))
			{
				printf("DEBUG: socket jest gotowy do read()\n");
				break;
			} else {
				if (wyslaneOOB == limitOOB)
				{
					printf("Serwer nie odpowiada. Wychodze z programu.\n");
					close(sock);
					exit(EXIT_FAILURE);
				}
				printf("DEBUG: wysylam OOB\n");
				c = 'I'; /* pIng */
				errno = (int) NULL;
				ret = send(sock, &c, 1, MSG_OOB);
				if (ret == -1)
				{
					perror("send() OOB: ping");
				}
				wyslaneOOB++;
			}
		}

		errno = (int) NULL;
		ret = read(sock, buffer2, MAXBUF);
		printf("DEBUG: read(): %i\n", ret); 
		if (ret == -1)
		{
			perror("read()");
		}
		printf("Odczytano: %s", buffer2);
	}
	close(sock);
	return 0;
}
