// Server side C/C++ program to demonstrate Socket
// programming
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#include <thread>
#include <atomic>
#include <chrono>
#include <iostream>
#include <fstream>
#include <sstream>
#include <algorithm>

std::atomic<uint64_t> bytes_transfered;
std::ofstream* timing_log;

double get_server_time(){
	return std::chrono::system_clock::now().time_since_epoch() / std::chrono::microseconds(1) / 1000000.0;
}

void spawn_socket_thd(int socket){
	auto thread = std::thread([=]{
		char buffer[1024]{0};

		read(socket, buffer, 1024);
		std::string name = buffer;
		std::cout << std::to_string(get_server_time()) << " - " << name << " connected\n";

		while(true){
			int valread = read(socket, buffer, 1024);
			double server_time = get_server_time();

			timing_log->flush();
			if(valread == 0) break;
			bytes_transfered += valread;
			buffer[valread] = 0;
			std::string message{buffer};
			auto space = std::find(message.begin(), message.end(), ' ');
			std::string time{message.begin(), space};
			std::string data{space + 1, message.end()};

			(*timing_log) << std::to_string(server_time) + "," + time + "," + std::to_string(bytes_transfered) + '\n';
			std::cout << time << ' ' << name << ' ' << data << '\n';
		}
		std::cout << std::to_string(get_server_time()) << " - " << name << " disconnected\n";

		close(socket);
	});
	thread.detach();
}

int main(int argc, char const* argv[])
{
    //argv[1] = port
    if(argc != 2){
        std::cerr << "bad usage\n";
        return 1;
    }

    int port = atoi(argv[1]);

	auto log = std::ofstream("timing_log.csv");
	log << "send_time,recv_time,bandwidth\n";
	timing_log = &log;
	int server_fd, new_socket, valread;
	struct sockaddr_in address;
	int opt = 1;
	int addrlen = sizeof(address);
	char buffer[1024] = { 0 };

	// Creating socket file descriptor
	if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		perror("socket failed");
		exit(EXIT_FAILURE);
	}

	// Forcefully attaching socket to the port 8080
	if (setsockopt(server_fd, SOL_SOCKET,
				SO_REUSEADDR | SO_REUSEPORT, &opt,
				sizeof(opt))) {
		perror("setsockopt");
		exit(EXIT_FAILURE);
	}
	address.sin_family = AF_INET;
	address.sin_addr.s_addr = INADDR_ANY;
	address.sin_port = htons(port);

	// Forcefully attaching socket to the port 8080
	if (bind(server_fd, (struct sockaddr*)&address,
			sizeof(address))
		< 0) {
		perror("bind failed");
		exit(EXIT_FAILURE);
	}
	if (listen(server_fd, 3) < 0) {
		perror("listen");
		exit(EXIT_FAILURE);
	}
	while(true){
		if ((new_socket
			= accept(server_fd, (struct sockaddr*)&address,
					(socklen_t*)&addrlen))
			< 0) {
			perror("accept");
			exit(EXIT_FAILURE);
		}
		spawn_socket_thd(new_socket);
	}
	
	// closing the connected socket
	// closing the listening socket
	shutdown(server_fd, SHUT_RDWR);
	return 0;
}