#include "../logger.hpp"
#include "configuration.hpp"
#include "server.hpp"
#include <set>
#include "socket.hpp"
#include <sstream>
#include <stdexcept>
#include <sys/epoll.h>
#include <unistd.h>

namespace Server
{
	void HttpServer::RegisterNewConnection(int const listeningSocketFd)
	{
		unsigned sockaddrSize = sizeof(sockaddr_in);
		int const clientFd = accept(
			listeningSocketFd,
			reinterpret_cast<sockaddr *>(&socketAddress),
			&sockaddrSize);
		
		if (clientFd < 0)
		{
			logger.Info("Connection dropped on accepting it");
			return;
		}

		scheduleHelper.ScheduleReadFileDescriptor(clientFd);
		fileDescriptorEventTypeMap[clientFd] = EventType::ReadRequest;
	}

	void HttpServer::ReadRequest(int const socketFd)
	{
		auto bytes = Socket::ReadBytes(socketFd, 512);
		if (bytes.size() < 1)
		{
			logger.Info("Could not read anything from socket");
			return;
		}

		auto & request = socketRequestMap[socketFd];
		request.Deserialize(bytes, logger);

		auto & response = socketResponseMap[socketFd];

		auto middlewareResult = middleware.Start(socketFd, scheduleHelper, request, response);
		HandleMiddlewareResponse(middlewareResult);	
	}

	void HttpServer::HandleMiddlewareEvent(int const eventFd)
	{
		auto middlewareResult = middleware.Continue(eventFd, scheduleHelper);
		HandleMiddlewareResponse(middlewareResult);
	}

	void HttpServer::HandleMiddlewareResponse(Middleware::MiddlewareResult const & middlewareResult)
	{
		if (middlewareResult.writeResult)
		{
			fileDescriptorEventTypeMap[middlewareResult.socketFd] = EventType::WriteResponse;
			scheduleHelper.ScheduleWriteFileDescriptor(middlewareResult.socketFd, true, true);
		}
	}

	void HttpServer::WriteResponse(int const socketFd)
	{
		auto requestResult = socketRequestMap.find(socketFd);
		if (requestResult != socketRequestMap.end())
		{
			socketRequestMap.erase(requestResult);
		}

		auto responseResult = socketResponseMap.find(socketFd);
		Socket::WriteBytes(socketFd, responseResult->second.Serialize());
		if (responseResult != socketResponseMap.end())
		{
			socketResponseMap.erase(responseResult);
		}
	}

	void HttpServer::Execute()
	{
		while(isOpen)
		{
			auto events = scheduler.WaitForEvents();
			for (size_t i = 0; i < events.size(); ++i)
			{
				int const fd = events[i];

				auto eventTypeMapIter = fileDescriptorEventTypeMap.find(fd);
				if (eventTypeMapIter == fileDescriptorEventTypeMap.end())
				{
					HandleMiddlewareEvent(fd);
					continue;
				}

				if (eventTypeMapIter->second != EventType::NewConnection)
				{
					fileDescriptorEventTypeMap.erase(eventTypeMapIter);
				}

				switch(eventTypeMapIter->second)
				{
					case EventType::NewConnection:
					RegisterNewConnection(fd);
					break;

					case EventType::ReadRequest:
					ReadRequest(fd);
					break;

					case EventType::WriteResponse:
					WriteResponse(fd);
					close(fd);
					break;

					case EventType::Unknown:
					default:
					close(fd);
					logger.Error("FD with unknown EventType fired and was closed");
					break;
				}
			}
		}
	}
		
	HttpServer::HttpServer(Logger & _logger, Configuration const & serverConfiguration)
		: logger(_logger),
		scheduler(_logger),
		scheduleHelper(scheduler),
		listeningSocketFileDescriptor(-1),
		middleware(Middleware::Middleware::CreateWithStaticFile(_logger, serverConfiguration.GetWwwRoot())),
		isOpen(true)
	{
		socketAddress.sin_family = AF_INET;
		socketAddress.sin_addr.s_addr = INADDR_ANY;
		socketAddress.sin_port = htons(serverConfiguration.GetPort());

		listeningSocketFileDescriptor = ListeningSocket::Create(socketAddress, 1000);
		if (listeningSocketFileDescriptor < 0)
		{
			throw std::runtime_error("Error creating listening socket");
		}

		scheduleHelper.ScheduleReadFileDescriptor(listeningSocketFileDescriptor, false);
		fileDescriptorEventTypeMap[listeningSocketFileDescriptor] = EventType::NewConnection;

		std::stringstream ss;
		ss << "Listening on port " << serverConfiguration.GetPort();
		logger.Info(ss.str());
	}

	HttpServer::~HttpServer()
	{
		if (listeningSocketFileDescriptor >= 0)
		{
			close(listeningSocketFileDescriptor);
		}

		for (auto & item : fileDescriptorEventTypeMap)
		{
			close(item.first);
		}
	}
}
