//#include "Logger.h"
#include "CurlHandler.h"
//#include "CommunicationSequencerMsg.h"


namespace Sonic {
	namespace Communication {
		/**
		 * Create a LogEntry using this file's TAG and the specified event string.
		 *
		 * @param The event string for this @c LogEntry.
		 */
//#define LX(event) Sonic::Common::logger::LogEntry(TAG, event)
		//static const std::string TAG("CURL");

		size_t CurlHandler::headerCallback(char *contents, size_t size, size_t nitems, void *userdata){
			size_t realsize = nitems * size;

			struct HTTPHeader * header = (struct HTTPHeader *)userdata;
			header->memory = (char*)realloc(header->memory, header->size + realsize);
			if (header->memory == NULL) {
				// out of memory!
				return 0;
			}

			memcpy(&(header->memory[header->size]), contents, realsize);
			header->size += realsize;
			//header->memory[header->size] = 0;
			return nitems * size;
		}


		size_t CurlHandler::bodyCallback(void *contents, size_t size, size_t nmemb, void *userp) {
			size_t realsize = size * nmemb;
			struct HTTPBody *body = (struct HTTPBody *) userp;
			//std::cout << "size of body callback received data: " << realsize << "\n";
			body->memory = (char*)realloc(body->memory, body->size + realsize);
			if (body->memory == NULL) {
				// out of memory! 
				return 0;
			}

			//for (int i=0; i<realsize; i++) printf("%c", *(char*)contents[i]);

			memcpy(&(body->memory[body->size]), contents, realsize);
			body->size += realsize;
			//body->memory[body->size] = 0;
			return realsize;
		}

		int CurlHandler::serverPushCallback(CURL *parent,
				CURL *easy,
				size_t num_headers,
				struct curl_pushheaders *headers,
				void *userp)
		{
			char *headp;
			size_t i;
			struct ServerPushData *server_push_data_ = (struct ServerPushData *)userp;
			char filename[128];
			FILE *out;
			static unsigned int count = 0;
			struct HTTPHeader header_;
			struct HTTPBody body_;
			header_.memory = "";//(char*)malloc(1);  // will be grown as needed by the realloc above 
			header_.size = 0;    // no data at this point 
			body_.memory = "";//(char*)malloc(1);
			body_.size = 0;
			server_push_data_->all_streams_headers.push_back(header_);
			server_push_data_->all_streams_bodies.push_back(body_);
			std::cout << "All Headers Size:" << server_push_data_->all_streams_headers.size() << "\n";
			std::cout << "All Bodies Size:" << server_push_data_->all_streams_bodies.size() << "\n";
			(void)parent; /* we have no use for this */ 

			snprintf(filename, 128, "push%u", count++);

			/* here's a new stream, save it in a new file for each new push */ 
			out = fopen(filename, "wb");
			
			/* write to this file */ 
			std::cout << "                ********** server push data streams in the PUSH CALLBACK :" << server_push_data_->streams << "\n";
			curl_easy_setopt(easy, CURLOPT_HEADERFUNCTION, headerCallback);
			curl_easy_setopt(easy, CURLOPT_HEADERDATA, (void*) &(server_push_data_->all_streams_headers.data()[server_push_data_->streams-1]) );
			curl_easy_setopt(easy, CURLOPT_WRITEFUNCTION, bodyCallback);
			curl_easy_setopt(easy, CURLOPT_WRITEDATA, (void*) &(server_push_data_->all_streams_bodies.data()[server_push_data_->streams-1]) );
			//curl_easy_setopt(easy, CURLOPT_WRITEDATA, out);
			fprintf(stderr, "**** push callback approves stream %u, got %lu headers!\n",
					count, (unsigned long)num_headers);

			for(i = 0; i<num_headers; i++) {
				headp = curl_pushheader_bynum(headers, i);
				fprintf(stderr, "**** header %lu: %s\n", (unsigned long)i, headp);
			}

			headp = curl_pushheader_byname(headers, ":path");
			if(headp) {
				fprintf(stderr, "**** The PATH is %s\n", headp /* skip :path + colon */ );
			}

			(server_push_data_->streams)++; /* one more */ 
			return CURL_PUSH_OK;
			std::cout << "at the end of curl server push cb.\n";
		}



		int CurlHandler::sendEvent(const char* json, unsigned int json_length, char* audio, unsigned int audio_length,
				std::string* received_header, std::string* received_body) 
		{
			still_running = -1;

			struct ServerPushData server_push_data_;
			server_push_data_.streams = 1;
			/* init a multi stack */ 
			multi_handle = curl_multi_init();

			CURL* easy = curl_easy_init();

			/* set options */ 
			FILE *out = fopen("dl", "wb");

			/* write to this file */ 
			curl_easy_setopt(easy, CURLOPT_WRITEDATA, out);

			/* set the same URL */ 
			//curl_easy_setopt(easy, CURLOPT_URL, "HTTPS://sonicserver1.com:8681/Servlet4Push/sonicServer");
			//curl_easy_setopt(easy, CURLOPT_PORT, 8681);
			//curl_easy_setopt(easy, CURLOPT_URL, "https://nghttp2.org");
			curl_easy_setopt(easy, CURLOPT_URL, "https://http2-push.io");
			//curl_easy_setopt(easy, CURLOPT_URL, "https://localhost:8327/index.html");
			/* please be verbose */ 
			curl_easy_setopt(easy, CURLOPT_VERBOSE, 1L);
			//curl_easy_setopt(easy, CURLOPT_DEBUGFUNCTION, my_trace);

			/* HTTP/2 please */ 
			//curl_easy_setopt(easy, CURLOPT_TCP_KEEPALIVE, 1L); 
			//curl_easy_setopt(easy, CURLOPT_TCP_KEEPIDLE, 3600L);
			//curl_easy_setopt(easy, CURLOPT_TCP_KEEPINTVL, 60L);
			curl_easy_setopt(easy, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);

			/* we use a self-signed test server, skip verification during debugging */ 
			curl_easy_setopt(easy, CURLOPT_SSL_VERIFYPEER, 0L);
			curl_easy_setopt(easy, CURLOPT_SSL_VERIFYHOST, 0L);

#if (CURLPIPE_MULTIPLEX > 0)
			/* wait for pipe connection to confirm */ 
			curl_easy_setopt(easy, CURLOPT_PIPEWAIT, 1L);
#endif

			/* add the easy transfer */ 
			curl_multi_add_handle(multi_handle, easy);

			curl_multi_setopt(multi_handle, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX);
			curl_multi_setopt(multi_handle, CURLMOPT_PUSHFUNCTION, serverPushCallback);
			curl_multi_setopt(multi_handle, CURLMOPT_PUSHDATA, &server_push_data_);

			/* we start some action by calling perform right away */ 
			curl_multi_perform(multi_handle, &still_running);
				
			int push_count = 0;
			do {
				struct timeval timeout;
				int rc; /* select() return code */ 
				CURLMcode mc; /* curl_multi_fdset() return code */ 

				fd_set fdread;
				fd_set fdwrite;
				fd_set fdexcep;
				int maxfd = -1;

				long curl_timeo = -1;

				FD_ZERO(&fdread);
				FD_ZERO(&fdwrite);
				FD_ZERO(&fdexcep);

				/* set a suitable timeout to play around with */ 
				timeout.tv_sec = 1;
				timeout.tv_usec = 0;

				curl_multi_timeout(multi_handle, &curl_timeo);
				if(curl_timeo >= 0) {
					timeout.tv_sec = curl_timeo / 1000;
					if(timeout.tv_sec > 1)
						timeout.tv_sec = 1;
					else
						timeout.tv_usec = (curl_timeo % 1000) * 1000;
				}

				/* get file descriptors from the transfers */ 
				mc = curl_multi_fdset(multi_handle, &fdread, &fdwrite, &fdexcep, &maxfd);

				if(mc != CURLM_OK) {
					fprintf(stderr, "curl_multi_fdset() failed, code %d.\n", mc);
					break;
				}

				/* On success the value of maxfd is guaranteed to be >= -1. We call
				   select(maxfd + 1, ...); specially in case of (maxfd == -1) there are
				   no fds ready yet so we call select(0, ...) --or Sleep() on Windows--
				   to sleep 100ms, which is the minimum suggested value in the
				   curl_multi_fdset() doc. */ 

				if(maxfd == -1) {
#ifdef _WIN32
					Sleep(100);
					rc = 0;
#else
					/* Portable sleep for platforms other than Windows. */ 
					struct timeval wait = { 0, 100 * 1000 }; /* 100ms */ 
					rc = select(0, NULL, NULL, NULL, &wait);
#endif
				}
				else {
					/* Note that on some platforms 'timeout' may be modified by select().
					   If you need access to the original value save a copy beforehand. */ 
					rc = select(maxfd + 1, &fdread, &fdwrite, &fdexcep, &timeout);
				}

				switch(rc) {
					case -1:
						/* select error */ 
						break;
					case 0:
					default:
						/* timeout or readable/writable sockets */ 
						curl_multi_perform(multi_handle, &still_running);
						break;
				}

				/*
				 * A little caution when doing server push is that libcurl itself has
				 * created and added one or more easy handles but we need to clean them up
				 * when we are done.
				 */ 
				do {
					int msgq = 0;;
					m = curl_multi_info_read(multi_handle, &msgq);
					if(m && (m->msg == CURLMSG_DONE)) {
						CURL *e = m->easy_handle;
						//std::cout << "we reach here!.\n";
						if (server_push_data_.streams > 1) {//skipping for the main stream of the request
							push_count++;
							std::string push_file_name = "push" + std::to_string(push_count);
							std::cout << "Push File Name: " << push_file_name << "\n";
							/*DIR *dir;
							struct dirent *ent;
							if ((dir = opendir ("/home/Administrator/test/cURLUtils/Transfertest")) != NULL) {
								while ((ent = readdir (dir)) != NULL) {
									printf ("%s\n", ent->d_name);
								}
								closedir (dir);
							}*/
							
							std::string tmp_header_str(server_push_data_.all_streams_headers.data()[server_push_data_.streams-2].memory, server_push_data_.all_streams_headers.data()[server_push_data_.streams-2].size);
							std::string tmp_body_str(server_push_data_.all_streams_bodies.data()[server_push_data_.streams-2].memory, server_push_data_.all_streams_bodies.data()[server_push_data_.streams-2].size);
							//std::cout << " TMP HEADER STR SIZE"
							std::cout << "\n###### \n Recieved Header: " << tmp_header_str << "\n#####\n";
							std::cout << "\n###### \n Received Body: " << tmp_body_str << "\n#####\n";
						}
						server_push_data_.streams--;
						std::cout << "after reducing streams.\n";
						curl_multi_remove_handle(multi_handle, e);
						curl_easy_cleanup(e);

					}

				} while(m);
			} while(server_push_data_.streams); /* as long as we have transfers going */ 

			curl_multi_cleanup(multi_handle);


			return 0;
		}// sendEvent


		CurlHandler::CurlHandler(std::string ip_, int port_) {
			//-----CURL SETUP-----/

			auth_port = port_;
			events_port = port_;
			headers = NULL;
			formpost = NULL;
			lastptr = NULL;

			std::cout << "before curl global init.\n";
			curl_global_init(CURL_GLOBAL_ALL);
			std::cout << "after curl global init\n";
			std::string* received_header_, *received_body_;
			sendEvent(json_, (unsigned int)11, (char*)audio_.c_str(), (unsigned int) 10,
					received_header_, received_body_);

		}

		CurlHandler::~CurlHandler() {
		}
	}
}
