#include <chrono>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <sstream>
#include <string>
#include <endian.hpp>
#include <network.hpp>
#include <array>
#include <thread>
#include <algorithm>

using namespace std::chrono_literals;

int main(int, char** argv){
	int64_t offset=0;
	std::array<int64_t, 4096> offsets;
	std::array<int64_t, 4096> offsets_smooth;
	std::array<int64_t, 4096> offset_variations;
	size_t pos = 0;
	int64_t max_offset_variation;
	int64_t max_oscillation = 0;
	int64_t min_ever = std::numeric_limits<int64_t>::max();
	int64_t max_ever = 0;

	std::stringstream ip_stream{argv[1]};
	std::array<uint8_t, 4> ip;
	std::string token;
	std::getline(ip_stream, token, '.');
	ip[0] = std::stoi(token);
	std::getline(ip_stream, token, '.');
	ip[1] = std::stoi(token);
	std::getline(ip_stream, token, '.');
	ip[2] = std::stoi(token);
	std::getline(ip_stream, token, '.');
	ip[3] = std::stoi(token);

	uint16_t port = std::stoi(argv[2]);


	auto soc = socket(AF_INET, SOCK_DGRAM, 0);
	struct sockaddr_in server;
	server.sin_family = AF_INET;
	server.sin_addr.s_addr = *(in_addr_t*)ip.data();
	server.sin_port = htons(port);


	struct timeval tv;
	tv.tv_sec = 5;
	tv.tv_usec = 0;
	setsockopt(soc, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof tv);

	std::cerr << "Connecting..." <<std::endl;
	connect(soc, (struct sockaddr*)&server, sizeof(server));

	
	req_rep buffer;

	uint64_t cnt = 0;
	uint64_t old_time = 0;

	while(true)
	{
		uint64_t tmp;
		buffer.id = 16;
		
		buffer.client_ts = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::high_resolution_clock::now().time_since_epoch()).count();
		send(soc, (void*)&buffer, (socklen_t)sizeof(req_rep), 0);
		recv(soc, (void*)&buffer, (socklen_t)sizeof(req_rep), 0);
		if(buffer.server_ts != old_time)
		{
			tmp = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::high_resolution_clock::now().time_since_epoch()).count() - buffer.client_ts;
			int64_t n_offset = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::high_resolution_clock::now().time_since_epoch()).count() - buffer.server_ts;
			n_offset /= 2;
			offset_variations[pos] = ((long long)(offset-n_offset));
			if(cnt != 0)
			{
				offsets[pos] = n_offset;
			} else {
				offsets.fill(n_offset);
				offsets_smooth.fill(n_offset);
			}
			max_offset_variation = std::max(*std::max_element(offset_variations.begin(), offset_variations.end()), std::abs((int64_t)*std::min_element(offset_variations.begin(), offset_variations.end())));
			offset = (
					7 * (std::accumulate(offsets_smooth.begin(), offsets_smooth.end(), 0)/offsets_smooth.size())
					+ std::accumulate(offsets.begin(), offsets.end(), 0)/offsets.size()
				) / 8;
			offsets_smooth[pos] = offset;
			pos=(pos+1)%offset_variations.size();
			if(cnt > offsets.size()*2)
			{
				for(auto i : offsets_smooth)
					for(auto j : offsets_smooth)
					{
						max_oscillation = std::max(
							std::abs(i-j),
							max_oscillation
						);
					}
				min_ever = std::min(min_ever, offset);
				max_ever = std::max(max_ever, offset);
			}
			else if (cnt < offsets.size()) {
				offsets_smooth[pos] = n_offset;
			}
			old_time = buffer.server_ts;
			std::cout << cnt << "\t"<< min_ever << "\t" << offset << "\t" << max_ever << "\t" << max_oscillation << "\t" << offset-n_offset << "\t" << max_offset_variation <<std::endl;
		}
		cnt++;
		//std::this_thread::sleep_for(6ms);
	}
}