#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <curl/curl.h>

#define MAX_LINE_LENGTH 512

typedef struct _curl_private {
	char put_url[MAX_LINE_LENGTH]; /* upload-url for streaming server */
	CURL *easy;
	CURLM *multi;
	struct curl_slist *headers;
	
	CURLcode res;
	CURLMcode mres;
} curl_private;

typedef struct _curlputtest {
	curl_private *curl;
	int source_fd;
	int response_code;
} curlputtest;
  
void cleanup(curlputtest *sp)
{
	if (sp->curl)
	{
		if (sp->curl->headers)
			curl_slist_free_all (sp->curl->headers);
		if (sp->curl->multi)
			curl_multi_cleanup(sp->curl->multi);
		if (sp->curl->easy)
			curl_easy_cleanup(sp->curl->easy);
		curl_global_cleanup();
		free(sp->curl);
	}
	sp->curl = NULL;	
}

static size_t read_callback(void *ptr, size_t size, size_t nmemb, void *userp)
{
	curlputtest *sp = (curlputtest *)userp;
	size_t nread;
	
	if ( sp->source_fd > 0 )
		nread = read(sp->source_fd, ptr, size*nmemb);
	else
	{
		nread = 100;
		memset (ptr, 'X', 100);
	}
	if (nread == -1) 
		return CURL_READFUNC_ABORT;

	fprintf(stdout, "curl read_callback nread=%d\n", (int)nread);fflush(stdout); 
	return nread;
}

static size_t write_callback(char *d, size_t n, size_t l, void *userp)
{
	curlputtest *sp = (curlputtest *)userp;
	
	long respcode;
	curl_easy_getinfo(sp->curl->easy,CURLINFO_RESPONSE_CODE, &respcode);
	
	if (respcode == 201) {
		fprintf(stdout, "QOS: peer finished <body>%s</body>\n", d);fflush(stdout);
	}
	else {
		fprintf(stdout, "QOS: peer error %lu, <body>%s</body>", respcode, d);fflush(stdout); 
	}

	return n*l;
}

int main(int argc, char **argv)
{
	char request[MAX_LINE_LENGTH], upstream_request[256];
	char *c;
	
	curlputtest sp;
	sp.curl = malloc(sizeof(curl_private));
	sp.curl->easy = NULL;
	sp.curl->multi = NULL;
	sp.curl->headers = NULL;
	sp.curl->put_url[0] = '\0';
	sp.source_fd = -1;

	if (!fgets(request, MAX_LINE_LENGTH - 1, stdin))
		goto bad_request;

	if (strncmp(request, "GET /", 5))
		goto bad_request;
	
	c = strchr(request + 5, ' ');
	if (!c || strncmp(c, " HTTP/1.", 7))
		goto bad_request;
	
	*c++ = 0;
	
	c = request + 4;
	
	while (1)
	{
		char option[MAX_LINE_LENGTH];
		if (!fgets(option, MAX_LINE_LENGTH - 1, stdin))
			break;
		
		if (!strncasecmp(option, "X-DESTINATION: ", 15))
		{
			strncpy(sp.curl->put_url, option+15, strlen(option)-16);
			fprintf(stdout, "curl put mode on!! source path '%s' destination url '%s'\n", c, sp.curl->put_url);fflush(stdout);
		}
		
		if (option[1] && option[strlen(option)-2] == '\r')
			option[strlen(option)-2] = 0;
		else
			option[strlen(option)-1] = 0;

		if (!*option)
			break;
	}
	
	if (sp.curl->put_url[0] == '\0')
		goto bad_request;
	
	curl_global_init(CURL_GLOBAL_ALL);
	sp.curl->easy = curl_easy_init();
	off_t filesize = 0L;

	{
		char *p = c;
		while ( *p != '\0') {
			if (*p == '+')
				*p = ' ';
			p++;
		}
		char *path = curl_easy_unescape(sp.curl->easy, c, strlen(c), 0);
		{
			struct stat stInfo;
			sp.response_code = 404;
			if ( stat(path, &stInfo) == 0 ) {
				filesize = stInfo.st_size;
				sp.source_fd = open(path, O_RDONLY);
				if ( sp.source_fd > 0 ) {
					snprintf(upstream_request, sizeof(upstream_request), "HTTP/1.0 200 OK\r\nConnection: Close\r\nContent-Length: %ld\r\nContent-Type: video/mpeg\r\n\r\n", filesize);
					write(1, upstream_request, strlen(upstream_request));
				}
			}
		}
	}
	
	{
		sp.curl->multi = curl_multi_init();
		
		if (sp.curl->easy && sp.curl->multi) {
			curl_easy_setopt(sp.curl->easy, CURLOPT_READFUNCTION, read_callback);
			curl_easy_setopt(sp.curl->easy, CURLOPT_READDATA, &sp);
			curl_easy_setopt(sp.curl->easy, CURLOPT_WRITEFUNCTION, write_callback);
			curl_easy_setopt(sp.curl->easy, CURLOPT_WRITEDATA, &sp);
			curl_easy_setopt(sp.curl->easy, CURLOPT_UPLOAD, 1L);
			curl_easy_setopt(sp.curl->easy, CURLOPT_PUT, 1L);
			curl_easy_setopt(sp.curl->easy, CURLOPT_URL, sp.curl->put_url);
// 			sp.curl->headers = curl_slist_append(sp.curl->headers, "Transfer-Encoding: chunked");
// 			curl_easy_setopt(sp.curl->easy, CURLOPT_HTTPHEADER, sp.curl->headers);
// 			sp.curl->headers = curl_slist_append(sp.curl->headers, "Expect:");
// 			curl_easy_setopt(sp.curl->easy, CURLOPT_HTTPHEADER, sp.curl->headers);
			if (filesize)
				curl_easy_setopt(sp.curl->easy, CURLOPT_INFILESIZE_LARGE, (curl_off_t)filesize);
			curl_easy_setopt(sp.curl->easy, CURLOPT_VERBOSE, 1L);
			curl_easy_setopt(sp.curl->easy, CURLOPT_STDERR, stdout);
			sp.curl->mres = curl_multi_add_handle(sp.curl->multi, sp.curl->easy);
			if ( sp.curl->mres != CURLM_OK )
				goto curl_error;
			sp.curl->res = curl_easy_pause(sp.curl->easy, CURLPAUSE_SEND);
			if ( sp.curl->res != CURLE_OK )
				goto curl_error;
			else
				fprintf(stdout, "PAUSED OK");
		}
		else
			goto curl_error;
	}
	
	while (1)
	{
		struct timeval timeout;
		timeout.tv_sec = 0;
		timeout.tv_usec = 100000;
		
		fd_set r;
		FD_ZERO(&r);

		FD_SET(0, &r);
		
		fd_set w;
		FD_ZERO(&w);
		int maxfd = 5, curl_maxfd;
		
		if (sp.source_fd != -1)
		{
			maxfd = sp.source_fd;
			FD_SET(sp.source_fd, &r);
		}

		{
			fd_set fdwrite, fdexcep;
			curl_multi_fdset(sp.curl->multi, &r, &fdwrite, &fdexcep, &curl_maxfd);
// 			fprintf(stdout, "curl_multi_fdset curl_maxfd=%d\n", curl_maxfd);fflush(stdout); 
			if ( curl_maxfd > sp.source_fd )
				maxfd = curl_maxfd;
		}
		
		if (select(maxfd+1, &r, &w, 0, &timeout) < 0) {
			break;
		}

		if (FD_ISSET(0, &r)) /* check for client disconnect */
			if (read(0, request, sizeof(request)) <= 0)
				break;
		
// 		if (sp.source_fd > 0 && FD_ISSET(sp.source_fd, &r))
		{
			{
// 				curl_easy_pause(sp.curl->easy, CURLPAUSE_CONT );

				int curl_still_running;
				sp.curl->mres = curl_multi_perform(sp.curl->multi, &curl_still_running);
				
				long respcode;
				curl_easy_getinfo(sp.curl->easy,CURLINFO_RESPONSE_CODE, &respcode);
// 				fprintf(stdout, "#### curl_easy_getinfo CURLINFO_RESPONSE_CODE %ld\n", respcode);fflush(stdout); 
				
				if ( sp.curl->mres != CURLM_OK ) 
					goto curl_error;
				
// 				fprintf(stdout, "curl_still_running=%d\n", curl_still_running);fflush(stdout); 
				if ( curl_still_running == 0 )
				{
					CURLMsg *msg;
					int msgs_left;
					while ((msg = curl_multi_info_read(sp.curl->multi, &msgs_left))) {
// 						fprintf(stdout, "curl_multi_info_read msg %d data.result %d\n", msg->msg, msg->data.result);fflush(stdout); 
						if ( msg->msg == CURLMSG_DONE )
						{
							if ( msg->data.result != CURLE_OK )
							{
								goto destination_error;
							}
						}
					}
					break;
				}
			}
		}
	}
	
	cleanup(&sp);
	return 0;
	
bad_request:
	printf("HTTP/1.0 400 Bad Request\r\n\r\n");
	cleanup(&sp);
	return 1;
curl_error:
	printf("HTTP/1.0 500 Internal Server Error\r\n\r\n");
	printf("curl perform failed: %s %s\n",
		curl_easy_strerror(sp.curl->res), curl_multi_strerror(sp.curl->mres));
	cleanup(&sp);
	return 1;
destination_error:
	printf("HTTP/1.0 462 Destination Unreachable\r\ncurl error code %d\r\n", sp.response_code);
	cleanup(&sp);
	return 1;
}

