#define DEBUG 1
#include <stdbool.h>
#include <unistd.h>
#include <netdb.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <stdio.h>

#include "cryanc.c"

// get proper sockaddr, ipv4 or ipv6
void *get_in_addr(struct sockaddr *sa) {
	if (sa->sa_family == AF_INET) {
		return &(((struct sockaddr_in*)sa)->sin_addr);
	}

	return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

// returns an addrinfo linked list. needs to be freed
// with freeaddrinfo later!
struct addrinfo *get_addr_info(const char *host, const char *port) {
	struct addrinfo hints, *res;
	int status;

	memset(&hints, 0, sizeof hints);
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;

	if ((status = getaddrinfo(host, port, &hints, &res)) != 0) {
		fprintf(stderr, "[net] getaddrinfo failed: %s\n", gai_strerror(status));
		return NULL;
	}

	return res;
}

int getsock(struct addrinfo *info) {
	struct addrinfo *p;
	int sockfd;

	char ipstr[INET6_ADDRSTRLEN];

	for (p = info; p != NULL; p = p->ai_next) {
		if ((sockfd = socket(p->ai_family, p->ai_socktype,
						p->ai_protocol)) == -1) {
			continue;
		}

		if (connect(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
			close(sockfd);
			continue;
		}

		break;
	}

	if (p == NULL) {
		fprintf(stderr, "[net] client failed to connect.\n");
		exit(8);
	}

	inet_ntop(p->ai_family, get_in_addr((struct sockaddr *)p->ai_addr),
			ipstr, sizeof ipstr);
	printf("[net\tinfo] connecting to %s...\n", ipstr);
	printf("[net\tinfo] connected.\n");

	return sockfd;
}

int send_pending(int sock, struct TLSContext* ctx)
{
    unsigned int out_buf_len = 0;
    unsigned int out_buf_idx = 0;
    const unsigned char* out_buf;
    unsigned int send_res, res;

    out_buf = tls_get_write_buffer(ctx, &out_buf_len);

    while ((out_buf) && (out_buf_len > 0)) {
        res = send(sock, &out_buf[out_buf_idx], out_buf_len, 0);
        if (res <= 0)
        {
            send_res = res;
            break;
        }
        out_buf_len -= res;
        out_buf_idx += res;
    }
    tls_buffer_clear(ctx);
    return send_res;
}

int do_validate_cert(struct TLSContext* c, struct TLSCertificate** chain, int len)
{
    return no_error;
}

int main()
{
    struct addrinfo* info = get_addr_info("thrig.me", "1965");
    int fd = getsock(info);
    fcntl(fd, F_SETFL, O_NONBLOCK);

    freeaddrinfo(info);

    struct TLSContext *context;

    context = tls_create_context(0, TLS_V13);
    tls_sni_set(context, "thrig.me");
    tls_client_connect(context);
    send_pending(fd, context);

    fd_set fds;
    unsigned char client_message[8192];
    unsigned char read_buffer[0xFFFF];

    bool sent = false;
    char text[] = "gemini://thrig.me/\r\n";

    while (true)
    {
        FD_ZERO(&fds);
        FD_SET(fd, &fds);

        select(fd + 1, &fds, NULL, NULL, NULL);

        if (FD_ISSET(fd, &fds))
        {
            size_t nread = recv(fd, client_message, sizeof(client_message), 0);
            if (nread > 0)
            {
                int i = tls_consume_stream(context, client_message, nread, do_validate_cert);
                if (i < 0)
                {
                    printf("*** error in tls_consume_stream: %d\n", i);
                    break;
                }
                send_pending(fd, context);
                if (!tls_established(context)) continue;

                if (!sent)
                {
                    tls_write(context, text, strlen(text));
                    send_pending(fd, context);
                    sent = true;
                }
                
                while (nread = tls_read(context, read_buffer, sizeof(read_buffer))) {
                    fwrite(read_buffer, nread, 1, stdout);
                    fflush(stdout);
                }

            }
        }
    }

}
