#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <fcntl.h>
#include <netinet/in.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#include "jsockets.h"

int servidor(short puerto);
int cliente(const char *host, short puerto, const char *archivo);
ssize_t reeead(int socket, void *buf, size_t n);
uint32_t transmitir(int fd_from, int fd_to, uint32_t total);
void entierra_hijos(int senal);
int atender_cliente(int socket_cliente);

int main(int argc, char *argv[]) {
	if (argc > 1) {
		if (!strcmp("servidor", argv[1])) {
			return servidor((short)atoi(argv[2]));
		} else if (!strcmp("cliente", argv[1])) {
			return cliente(argv[2], (short)atoi(argv[3]), argv[4]);
		}
	} else {
		printf("Uso: %s {servidor PUERTO | cliente SERVIDOR PUERTO ARCHIVO}\n", argv[0]);
		return EXIT_FAILURE;
	}
	return EXIT_SUCCESS;
}

int cliente(const char *host, short puerto, const char *archivo) {
	struct stat st; int socket, fd, ok; uint32_t size;
	if (stat(archivo, &st)) {
		perror(NULL);
		return 2;
	}
	socket = j_socket();/* ! */
	if (j_connect(socket, host, puerto)) {
		printf("No se pudo conectar a %s:%i\n", host, puerto);
		return 3;
	}
	printf("Transmitiendo nombre de archivo: %s\n", archivo);
	write(socket, archivo, sizeof(*archivo) * (strlen(archivo) + 1));/* ! */
	printf("Transmitiendo tamaño: %u\n", (unsigned int)st.st_size);
	size = htonl(st.st_size);
	write(socket, &size, sizeof(size));/* ! */
	fd = open(archivo, O_RDONLY);/* ! */
	ok = EXIT_SUCCESS;
	if (transmitir(fd, socket, st.st_size) != st.st_size) {
		printf("Error en la transmisión.\n");
		ok = EXIT_FAILURE;
	}
	close(fd);/* ! */
	close(socket);/* ! */
	return ok;
}

void entierra_hijos(int senal) {
	int status;
	wait(&status);
}

int servidor(short puerto) {
	int socket, socket_cliente;
	socket = j_socket();/* ! */
	if (j_bind(socket, puerto)) {
		printf("No se pudo bind al puerto %i\n", puerto);
		return 2;
	}
	signal(SIGCHLD, entierra_hijos);
	printf("Escuchando en el puerto %i...\n", puerto);
	do {
		while ((socket_cliente = j_accept(socket)) >= 0) {
			printf("Conexión aceptada.\n");
			switch (fork()) {
				case 0: {/* Hijo */
					int ok;
					close(socket);
					ok = atender_cliente(socket_cliente);
					close(socket_cliente);
					exit(ok);
				}
				case -1:
					break;
				default:/* Padre */
					close(socket_cliente);
			}
		}
	} while (errno == EINTR);
	return EXIT_FAILURE;
}

uint32_t transmitir(int fd_from, int fd_to, uint32_t total) {
	char buf[1024];
	uint32_t transmitido = 0;
	/* fprintf(stderr, "transmitir %i %i %u\n", fd_from, fd_to, total); */
	while (transmitido < total) {
		size_t leido, leer = total - transmitido;
		if (leer > sizeof(buf) / sizeof(*buf)) {
			leer = sizeof(buf) / sizeof(*buf);
		}
		/* fprintf(stderr, "@"); */
		leido = reeead(fd_from, buf, leer);
		if (leido != leer) {
			return transmitido;
		}
		/* fprintf(stderr, "$"); */
		if (write(fd_to, buf, leido) != leido) {
			perror(NULL);
			return transmitido;
		}
		transmitido += leido;
		printf("#");
	}
	printf("\n");
	return transmitido;
}

ssize_t reeead(int socket, void *buf, size_t n) {
	size_t bcount = 0, br;
	/* fprintf(stderr, "reeead %u\n", n); */
	while(bcount < n) {
		/* fprintf(stderr, "."); */
		if((br = read(socket, buf, n - bcount)) > 0) {
			/* fprintf(stderr, ","); */
			bcount += br;
			buf = ((char *)buf) + br;
		} else if (br < 0) {
			break;
		}
	}
	if (br < 0)
		return -1;
	return bcount;
}

int atender_cliente(int socket_cliente) {
	int ok, fd_archivo;
	char nombre_archivo[512 + 1];
	uint32_t largo_nombre_archivo = 0, size;

	while (
		largo_nombre_archivo < sizeof(nombre_archivo) / sizeof(*nombre_archivo) - 1 &&
		(largo_nombre_archivo == 0 || nombre_archivo[largo_nombre_archivo - 1] != '\0')
	) {
		/* ! */
		reeead(socket_cliente, nombre_archivo + largo_nombre_archivo, sizeof(*nombre_archivo));
		++largo_nombre_archivo;
	}
	nombre_archivo[largo_nombre_archivo] = '\0';
	printf("Recibiendo archivo: %s\n", nombre_archivo);
	reeead(socket_cliente, &size, sizeof(size));/* ! */
	size = ntohl(size);
	printf("Recibiendo Tamaño: %u\n", (unsigned int)size);
	fd_archivo = open(nombre_archivo, O_WRONLY | O_CREAT);
	if (fd_archivo < 0) {
		perror(NULL);
		return EXIT_FAILURE;
	}
	ok = EXIT_SUCCESS;
	if (transmitir(socket_cliente, fd_archivo, size) != size) {
		printf("Error en la recepción.\n");
		ok = EXIT_FAILURE;
	}
	close(fd_archivo);/* ! */
	return ok;
}