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

extern int h_errno;
struct in_addr defaultServerAddr;
const int defaultPort = 6789;
short int debug = 0;

#define MAXBUF		1024

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

void usage(char * name)
{
	fprintf(stderr, "Usage: %s [-d] [-p port] [-s server] <filename> [dest filename]\n", name);
	fprintf(stderr, "-d\tturns debug output on\n\n");
	fprintf(stderr, "Default server is %s.\n", inet_ntoa(defaultServerAddr));
	fprintf(stderr, "Default port is %i.\n", defaultPort);
	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);
}

int main(int argc, char* argv[])
{
	int sockd;
	int outFile;
	int readed;
	struct sockaddr_in xferServer;
	char buffer[MAXBUF];
	char * bufPtr;
	int returnStatus;
	char * downloadName = 0;
	char * saveName = 0;
	int c;

	xferServer.sin_family = AF_INET;

	defaultServerAddr.s_addr = inet_addr("127.0.0.1");

	xferServer.sin_port = defaultPort;
	xferServer.sin_addr = defaultServerAddr;

	while((c = getopt(argc, argv, "adhp:s:")) != -1)
	{
		switch (c)
		{
			case 'd':
				debug = 1;
				break;
			case 'p':
				xferServer.sin_port = anyPort_to_int(optarg);
				break;
			case 's':
				xferServer.sin_addr = inet_addr_from_any(optarg);
				break;
			default:
				usage(argv[0]);
				break;
		}
	}

	if (optind < argc) {
		while (optind < argc)
		{
			if (downloadName == 0)
			{
				downloadName = calloc(strlen(argv[optind]), sizeof(char) + 1);
				strncpy(downloadName, argv[optind], strlen(argv[optind]));
				optind++;
				continue;
			}
			if (saveName == 0)
			{
				saveName = calloc(strlen(argv[optind]), sizeof(char) + 1);
				strncpy(saveName, argv[optind], strlen(argv[optind]));
				optind++;
				continue;
			}
		}
	}

	if (downloadName == 0) usage(argv[0]);

	/* if (saveName == 0) saveName = downloadName; */

	if (debug)
	{
		printf("server: %s\n", inet_ntoa(xferServer.sin_addr));
		printf("port: %i\n", xferServer.sin_port);
		printf("file name on server: %s\n", downloadName);
		printf("file name to save as: %s\n", saveName);
	}

	/* utworz gniazdo */
	errno = (int)NULL;
	sockd = socket(AF_INET, SOCK_STREAM, 0);

	if (sockd == -1)
	{
		perror("socket()");
		exit(1);
	}

	/* polacz z serwerem */
	errno = (int)NULL;
	returnStatus = connect(sockd, (struct sockaddr*)&xferServer, sizeof(xferServer));

	if (returnStatus == -1)
	{
		perror("connect()");
		exit(1);
	}

	errno = (int)NULL;
	/* wyslij nazwe pliku */
	returnStatus = write(sockd, downloadName, strlen(downloadName));

	if (returnStatus == -1)
	{
		perror("write()");
		fprintf(stderr, "Could not send filename to server!\n");
		exit(1);
	}

	/* zamknij polaczenie jednostronnie */
	errno = (int)NULL;
	returnStatus = shutdown(sockd, SHUT_WR);
	if (returnStatus == -1)
	{
		perror("shutdown()");
		exit(1);
	}

	bufPtr = buffer;
	while (bufPtr != buffer + sizeof(char) * sizeof(buffer) - 1)
	{
		errno = (int)NULL;
		returnStatus = read(sockd, bufPtr, 1);
		if (returnStatus == -1)
		{
			perror("blad podczas odczytywania odpowiedzi serwera: read():");
			exit(1);
		}
		if (*bufPtr == '\n') break;
		bufPtr += sizeof(char);
	}

	*bufPtr = 0;

	if (strncmp(buffer, "OK", 2) == 0)
	{
		fprintf(stderr, "Response from server: OK (File exists and will be transmitted).\n");
	} else if (strncmp(buffer, "DIR", 3) == 0) {
		fprintf(stderr, "Response from server: DIR (Given path is directory, sending it's listing).\n");
	} else if (strncmp(buffer, "ERROR", 5) == 0) {
		fprintf(stderr, "Response from server: ERROR (%s).\n", buffer + sizeof(char) * 6);
		exit(2);
	}

	if (saveName == 0)
	{
		outFile = 1;
	} else {
		/* otworz plik do zapisu */
		errno = (int)NULL;
		outFile = open(saveName, O_WRONLY|O_CREAT);

		if (outFile == -1)
		{
			perror("open()");
			fprintf(stderr, "Could not open destination file, using stdout.\n");
			outFile = 1;
		}
	}

	/* czytaj plik z serwera */
	while(1)
	{
		readed = read(sockd, buffer, sizeof(buffer));
		if (readed == -1)
		{
			perror("read() of file from server error");
			exit(1);
		} else if (readed == 0) {
			break;
		} else {
			buffer[readed] = 0;
			if (write(outFile, buffer, readed) == -1)
			{
				perror("write() of downloaded content to file");
				exit(1);
			}
		}
	}

	close(sockd);
	close(outFile);
	if (saveName != 0)
	{
		chmod(saveName, S_IRUSR|S_IWUSR);
	}
	return 0;
}
