#pragma once

#include "blitz/common/DataBuffer.h"
#include "blitz/misc/ClassEntity.h"

#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <arpa/inet.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>

#define closesocket close
#endif

#ifndef INVALID_SOCKET
#define INVALID_SOCKET -1
#endif

/**
 * \file TCPSocket.h
 * \brief File containing the blitz::network::TCPSocket class
 */

namespace blitz {
namespace network {

/**
 * \typedef SocketHandle
 * \brief Represents a native socket
 */
typedef int SocketHandle;

/**
 * \class TCPSocket
 * \brief Cross platform abstraction of a TCP socket
 */
class TCPSocket {
  public:
	/**
	 * \enum Status
	 * \brief Describes the state of a socket
	 */
	enum class Status {
		/** The socket is connected */
		Connected,
		/** The socket is not connected */
		Disconnected,
		/** Something bad happened */
		Error,
	};

  private:
	bool m_Blocking;
	Status m_Status;
	SocketHandle m_Handle;

	std::uint16_t m_Port;
	sockaddr m_RemoteAddr;

  public:
	/**
	 * \brief Default constructor
	 */
	TCPSocket();

	/**
	 * \brief Default destructor
	 */
	virtual ~TCPSocket();

	/**
	 * \brief Move constructor
	 */
	TCPSocket(TCPSocket&&);

	/**
	 * \brief Sets the TCPSocket::Status of the socket
	 * \param status The TCPSocket::Status to apply
	 */
	void SetStatus(Status status);

	/**
	 * \brief Allows to set the socket in non blocking/blocking mode
	 * \param block If set to true, every call to Read will wait until the socket receives something
	 * \return true if the operation was successful
	 */
	bool SetBlocking(bool block);

	/**
	 * \brief Checks if the socket is in blocking mode
	 * \return Whether the socket is in blocking mode
	 */
	bool IsBlocking() const;

	/**
	 * \brief Getter of the m_Status member
	 * \return The TCPSocket::Status of this socket
	 */
	Status GetStatus() const;

	/**
	 * \brief Tries to connect to the host at the given port
	 * \param host The host to connect to. Can be a hostname or an ip address.
	 * \param port The port to connect to.
	 * \return Whether this action was successfull
	 */
	bool Connect(const std::string& host, std::uint16_t port);

	/**
	 * \brief Disconnects the socket from the remote
	 * \note Does nothing if the socket is not connected. \n
	 * 	This function is also called by the destructor.
	 */
	void Disconnect();

	/**
	 * \brief Sends some data
	 * \param buffer The data to send
	 * \note Simply returns 0 if the socket is not connected
	 * \return The amount of data sent. It should be the size of the buffer in theory.
	 */
	std::size_t Send(DataBuffer& buffer);

	/**
	 * \brief Sends some data
	 * \param data The data to send
	 * \param size The amount of data to send
	 * \note Simply returns 0 if the socket is not connected
	 * \return The amount of data sent. It should be equal to the size param in theory
	 */
	std::size_t Send(const std::uint8_t* data, std::size_t size);

	/**
	 * \brief Reads some data
	 * \param amount The amount of data to read
	 * \return The buffer containing the data that have been received
	 * \note If no data is available, an empty DataBuffer is returned
	 */
	DataBuffer Receive(std::size_t amount);

	/**
	 * \brief Reads some data
	 * \param buffer The buffer to write into
	 * \param amount The amount of data to read
	 * \return The amount of data read
	 * \note If no data is available, 0 is returned
	 */
	std::size_t Receive(DataBuffer& buffer, std::size_t amount);

	/**
	 * \brief Disable TCPSocket copy, as it disconnects when destroyed
	 */
	REMOVE_COPY(TCPSocket);

	friend class TCPListener;
};

/**
 * \brief Compress the data and send it to the socket
 * \param data The data to send
 * \param socket The socket to send to
 */
void SendPacket(const DataBuffer& data, network::TCPSocket& socket);

}  // namespace network
}  // namespace blitz