#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include "xwrap.h"
#include "socktool.h"

#include <arpa/inet.h>
#include <errno.h>
#include <inttypes.h>
#include <locale.h>
#include <netdb.h>
#include <netinet/in.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
#include <time.h>
#include <unistd.h>

static int client_rw_cb (int fd[2], RasBuffer buf[2]) {
	return true;
}

int main (int argc, char* argv[]) {
	setlocale (LC_ALL, "");
	tzset ();

	const char* logincmd = isatty (STDIN_FILENO) ? "LOGINTTY\n" : "LOGIN\n";
	const char* conn[2] = { NULL, NULL };
	bool allowv4 = true;
	bool allowv6 = true;
	int conncnt = 0;
	for (int i = 1; i < argc; i++) {
		if (argv[i][0] == '-') {
			for (int j = 1; argv[i][j] != '\0'; j++) {
				switch (argv[i][j]) {
					case 't':
						logincmd = "LOGINTTY\n";
						break;
					case 'T':
						logincmd = "LOGIN\n";
						break;
					case 'I':
						allowv4 = true;
						allowv6 = true;
						break;
					case 'U':
						allowv4 = false;
						allowv6 = false;
						break;
					case '4':
						allowv4 = true;
						allowv6 = false;
						break;
					case '6':
						allowv4 = false;
						allowv6 = true;
						break;
					case 'h':
					case '?':
					case '-':
						printf (
							"Usage: %s [-46hItTU] host|file [port]\n"
							"  -h  View this help message\n"
							"  -t  Force pseudo terminal allocation\n"
							"  -T  Disable pseudo terminal allocation\n"
							"  -U  Connect to a UNIX-domain socket file\n"
							"  -I  Connect to the Internet\n"
							"  -4  Try IPv4 only\n"
							"  -6  Try IPv6 only\n",
							argv[0]);
						return 0;
						break;
					default:
						fprintf (stderr, "%s: -%c: unknown option\n", argv[0], argv[i][j]);
						return 1;
				}
			}
		} else {
			if (conncnt >= 2) {
				fprintf (stderr, "%s: %s: unknown argument\n", argv[0], argv[i]);
				return 1;
			}
			conn[conncnt++] = argv[i];
		}
	}

	if (allowv4 || allowv6) {
		if (conn[0] == NULL) {
			fprintf (stderr, "%s: host name is required\n", argv[0]);
			return 1;
		}
		if (conn[1] == NULL) {
			fprintf (stderr, "%s: port number is requried\n", argv[0]);
			return 1;
		}
	} else {
		if (conn[0] == NULL) {
			fprintf (stderr, "%s: socket file name is required\n", argv[0]);
			return 1;
		}
	}

	int sockfd = -1;
	if (allowv4 || allowv6) {
		printf ("Getting information for host %s (service %s) ... ",
			conn[0], conn[1]);
		fflush (stdout);

		struct addrinfo* iaddr;
		struct addrinfo* result;
		struct addrinfo  hints;
		memset (&hints, 0, sizeof (hints));

		hints.ai_family = AF_UNSPEC;
		hints.ai_socktype = SOCK_STREAM;
		hints.ai_flags = AI_ADDRCONFIG;

		int gaierr = getaddrinfo (conn[0], conn[1], &hints, &result);
		if (gaierr != 0) {
			puts (gai_strerror (gaierr));
			return 2;
		} else {
			puts ("OK");
		}

		bool connected = false;
		for (iaddr = result; iaddr != NULL; iaddr = iaddr->ai_next) {
			sockfd = socket (iaddr->ai_family, SOCK_STREAM, 0);
			if (sockfd < 0) {
				perror ("socket");
				continue;
			}

			const size_t ipstrlen = xmax (INET_ADDRSTRLEN, INET6_ADDRSTRLEN);
			char* ipstr = xmalloc (ipstrlen);
			uint16_t ipport = ntohs (
				iaddr->ai_family == AF_INET ?
				SOCKADDR_IN (iaddr->ai_addr)->sin_port :
				SOCKADDR_IN6 (iaddr->ai_addr)->sin6_port);

			fputs ("Connecting to host ", stdout);

			if (inet_ntop (
				iaddr->ai_family,
				iaddr->ai_family == AF_INET ?
				(void*) &SOCKADDR_IN (iaddr->ai_addr)->sin_addr :
				(void*) &SOCKADDR_IN6 (iaddr->ai_addr)->sin6_addr,
				ipstr, ipstrlen) == NULL)
			{
				printf ("unknown, port %" PRIu16 " ... ", ipport);
			} else {
				printf ("%s, port %" PRIu16 " ... ", ipstr, ipport);
				free (ipstr);
			}

			fflush (stdout);

			if (connect (sockfd, iaddr->ai_addr, iaddr->ai_addrlen) < 0) {
				puts (strerror (errno));
			} else {
				puts ("OK");
				connected = true;
				break;
			}
		}

		if (result != NULL) {
			freeaddrinfo (result);
		}

		if (!connected) {
			return 3;
		}

	} else {
		struct sockaddr_un uaddr;
		socklen_t uaddrlen;

		sockfd = socket (AF_UNIX, SOCK_STREAM, 0);
		if (sockfd < 0) {
			perror ("socket");
			return 3;
		}

		printf ("Try to connect to UNIX-domain socket %s ... ", conn[0]);
		fflush (stdout);

		uaddrlen = sizeof (struct sockaddr_un);
		SOCKADDR_UN (&uaddr)->sun_family = AF_UNIX;
		strncpy (SOCKADDR_UN (&uaddr)->sun_path, conn[0],
			uaddrlen - offsetof (struct sockaddr_un, sun_path) - 1);

		if (connect (sockfd, SOCKADDR (&uaddr), uaddrlen) < 0) {
			puts (strerror (errno));
			return 3;
		} else {
			puts ("OK");
		}
	}

	if (sockfd < 0) {
		return 3;
	}

	ras_socktool_write_string (sockfd, logincmd, 0);

	int fd[2] = { STDIN_FILENO, sockfd };
	ras_socktool_exchange_data (fd, client_rw_cb);

	shutdown (sockfd, SHUT_RDWR);
	close (sockfd);

	return 0;
}