#include "CSocket.h"

#include <cstring>

#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip.h>
#include <sys/epoll.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>

CSocket::CSocket() : terminated_(false), sock_fd_(-1)
{
}

CSocket::~CSocket()
{
}

void *CSocket::ThreadFunc(void *p)
{
	CSocket *this_ptr = reinterpret_cast<CSocket *>(p);
	return reinterpret_cast<void *>(this_ptr->Receive());
}

bool CSocket::Open(void *p)
{
	CONNECTION connetion = *(CONNECTION *)p;
	// Set the IP address and the port number of the server
	struct sockaddr_in sockaddr = {};
	sockaddr.sin_family = AF_INET;							// Internet
	sockaddr.sin_addr.s_addr = inet_addr(connetion.ipAddr); // IP address
	sockaddr.sin_port = htons(connetion.nPort);				// Port number

	// Create the TCP socket
	sock_fd_ = socket(AF_INET, SOCK_STREAM, 0);
	if (sock_fd_ == -1)
	{
		return false;
	}

	// Establishes a connection to the TCP socket
	if (connect(sock_fd_, (struct sockaddr *)&sockaddr, sizeof(sockaddr)) != 0)
	{
		// Closes an existing socket
		close(sock_fd_);
		sock_fd_ = -1;
		return false;
	}

	// Create a recieve thread
	terminated_ = false;
	if (pthread_create(&thread_, NULL, CSocket::ThreadFunc, this) != 0)
	{
		// Closes an existing socket
		close(sock_fd_);
		sock_fd_ = -1;
		return false;
	}

	return true;
}

void CSocket::Close()
{
	terminated_ = true;

	// Disables sends or receives on a socket
	shutdown(sock_fd_, 2);

	// Wait until the thread is over
	pthread_join(thread_, NULL);

	// Closes an existing socket
	if (sock_fd_ > -1)
	{
		close(sock_fd_);
		sock_fd_ = -1;
	}
}

error_t CSocket::Receive()
{
	uint8_t buffer[RECV_BUFF_SIZE];
	size_t receive_size;

	int epfd = epoll_create1(0);
	if (epfd < 0)
	{
		return errno;
	}

	epoll_event event = {};
	event.events = EPOLLIN;
	event.data.fd = sock_fd_;
	epoll_ctl(epfd, EPOLL_CTL_ADD, event.data.fd, &event);

	while (!terminated_)
	{
		// Wait for data receive event to occur
		if (epoll_wait(epfd, &event, 1, 1000) < 0)
		{
			return errno;
		}

		if (event.data.fd == sock_fd_)
		{
			// Read receive data
			receive_size = recv(sock_fd_, buffer, sizeof(buffer), 0);
			if (receive_size < 0)
			{
				return errno;
			}
			else
			{
				WriteBuffer(buffer, receive_size);
			}
		}
	}

	return 0;
}

size_t CSocket::Transmit(const uint8_t *buf, size_t size)
{
	size_t w, index = 0;

	while (size > 0)
	{
		w = send(sock_fd_, buf + index, size, 0);
		index += w;
		if (!w)
		{
			return 0;
		}
		size -= w;
	}

	return w;
}