/*
 * 2007+ Copyright (c) Evgeniy Polyakov <johnpol@2ka.mipt.ru>
 * All rights reserved.
 *
 * 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.
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/stat.h>

#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <stdint.h>
#include <netdb.h>

#include <netinet/in.h>
#include <arpa/inet.h>

#include <linux/types.h>

#include "rbtree.h"
#include "kpp.h"

#define uloga(f, a...) fprintf(stderr, f, ##a)
#define ulog_err(f, a...) uloga(f ": %s [%d].\n", ##a, strerror(errno), errno)

#define UPP_DEBUG

#ifdef UPP_DEBUG
#define ulog(f, a...) uloga(f, ##a)
#else
#define ulog(f, a...) do {} while (0)
#endif

static int upp_init_socket(char *addr, char *port)
{
	int s, err;
	struct addrinfo *ai, hint;

	s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (s < 0) {
		ulog_err("Failed to create TCP socket");
		return -1;
	}

	memset(&hint, 0, sizeof(struct addrinfo));

	hint.ai_flags = AI_NUMERICSERV;
	hint.ai_family = AF_INET;
	hint.ai_socktype = SOCK_STREAM;
	hint.ai_protocol = IPPROTO_TCP;

	err = getaddrinfo(addr, port, &hint, &ai);
	if (err) {
		ulog_err("Failed to get address info for %s:%s, err: %d", addr, port, err);
		goto err_out_close;
	}

	err = bind(s, ai->ai_addr, ai->ai_addrlen);
	if (err) {
		ulog_err("Failed to bind to %s:%s", addr, port);
		goto err_out_free;
	}

	err = listen(s, 1024);
	if (err) {
		ulog_err("Failed to listen at %s:%s", addr, port);
		goto err_out_free;
	}

	freeaddrinfo(ai);

	ulog("Server is now listening at %s:%s.\n", addr, port);

	return s;

err_out_free:
	freeaddrinfo(ai);
err_out_close:
	close(s);
	return -1;

}

static int upp_send_data(int s, void *buf, int size)
{
	int err;
	char *ptr = buf;

	while (size) {
		err = send(s, ptr, size, 0);
		if (err <= 0) {
			ulog_err("Failed to receive data");
			return -1;
		}

		ptr += err;
		size -= err;
	}

	return 0;
}

static int upp_recv_data(int s, void *buf, int size)
{
	int err;
	char *ptr = buf;

	while (size) {
		err = recv(s, ptr, size, 0);
		if (err <= 0) {
			ulog_err("Failed to receive data");
			return -1;
		}

		ptr += err;
		size -= err;
	}

	return 0;
}

static void upp_usage(char *p)
{
	fprintf(stderr, "Usage: %s -a addr -p port\n", p);
}

int main(int argc, char *argv[])
{
	int s, ch, err;
	char *addr, *port;
	struct rb_root upp_root = RB_ROOT;

	addr = "0.0.0.0";
	port = "1025";

	while ((ch = getopt(argc, argv, "a:p:")) != -1) {
		switch (ch) {
			case 'p':
				port = optarg;
				break;
			case 'a':
				addr = optarg;
				break;
			default:
				upp_usage(argv[0]);
				return -1;
		}
	}

	s = upp_init_socket(addr, port);
	if (s < 0)
		return -1;

	while (1) {
		struct sockaddr_in sa;
		socklen_t len = sizeof(struct sockaddr_in);
		int num, cs;
		struct kpp_struct *k;
		struct rb_node *rb_node;

		memset(&sa, 0, sizeof(struct sockaddr_in));

		cs = accept(s, (struct sockaddr *)&sa, &len);
		if (cs < 0) {
			ulog_err("Failed to accept new client");
			return -1;
		}

		k = malloc(sizeof(struct kpp_struct));
		if (!k)
			goto out;

		err = upp_recv_data(cs, &k->packet, sizeof(struct kpp_packet));
		if (err)
			goto out;

		num = ntohl(k->packet.id);

		printf("Client: %s:%d, num: %d.\n",
				inet_ntoa(sa.sin_addr), ntohs(sa.sin_port), num);

		while (num-- > 0) {
			char path[64];

			err = upp_recv_data(cs, &k->packet, sizeof(struct kpp_packet));
			if (err)
				break;

			k->packet.id = ntohl(k->packet.id);

			snprintf(path, sizeof(path), "/tmp/test/%08x", k->packet.id);
			//k->packet.error = open(path, O_RDWR | O_CREAT, 0644);

			err = kpp_insert(&upp_root, k);

			err = upp_send_data(cs, &k->packet, sizeof(struct kpp_packet));
			if (err)
				break;

			if (!num)
				break;

			k = malloc(sizeof(struct kpp_struct));
			if (!k)
				break;
		}

out:
		ulog("Exiting.\n");
		for (rb_node = rb_first(&upp_root); rb_node;) {

			k = rb_entry(rb_node, struct kpp_struct, rb_node);
			rb_node = rb_next(rb_node);

			//close(k->packet.error);
			rb_erase(&k->rb_node, &upp_root);
			free(k);
		}
		close(cs);
	}
}

