/*
 * Socket.cpp
 *
 *  Created on: Mar 5, 2017
 *      Author: kolban
 */
#include <iostream>
#include <streambuf>
#include <cstdio>
#include <cstring>
#include <cctype>
#include <string>

#include <sstream>

#include <errno.h>
#include <esp_log.h>
#include <lwip/sockets.h>
#include <stdio.h>
#include <string.h>

#include <unistd.h>
#include "GeneralUtils.h"
#include "sdkconfig.h"
#include "Socket.h"

static const char* LOG_TAG = "Socket";

Socket::Socket() {
	m_sock = -1;
}

Socket::~Socket() {
	//close_cpp(); // When the class instance has ended, delete the socket.
}



Socket Socket::accept_cpp() {
	struct sockaddr addr;
	getBind_cpp(&addr);
	ESP_LOGD(LOG_TAG, "Accepting on %s", addressToString(&addr).c_str());
	struct sockaddr_in clientAddress;
	socklen_t clientAddressLength = sizeof(clientAddress);
	int clientSockFD = ::accept(m_sock, (struct sockaddr *)&clientAddress, &clientAddressLength);
	if (clientSockFD == -1) {
		ESP_LOGE(LOG_TAG, "accept(): %s, m_sock=%d", strerror(errno), m_sock);
		Socket newSocket;
		return newSocket;
	}
	Socket newSocket;
	newSocket.m_sock = clientSockFD;
	return newSocket;
}

/**
 * @brief Convert a socket address to a string representation.
 * @param [in] addr The address to parse.
 * @return A string representation of the address.
 */
std::string Socket::addressToString(struct sockaddr* addr) {
	struct sockaddr_in *pInAddr = (struct sockaddr_in *)addr;
	char temp[30];
	char ip[20];
	inet_ntop(AF_INET, &pInAddr->sin_addr, ip, sizeof(ip));
	sprintf(temp, "%s [%d]", ip, ntohs(pInAddr->sin_port));
	return std::string(temp);
} // addressToString


/**
 * @brief Bind an address/port to a socket.
 * Specify a port of 0 to have a local port allocated.
 * Specify an address of INADDR_ANY to use the local server IP.
 * @param [in] port Port number to bind.
 * @param [in] address Address to bind.
 * @return N/A
 */
void Socket::bind_cpp(uint16_t port, uint32_t address) {
	ESP_LOGD(LOG_TAG, "bind_cpp: port=%d, address=0x%x", port, address);
	if (m_sock == -1) {
		ESP_LOGE(LOG_TAG, "bind_cpp: Socket is not initialized.");
	}
	struct sockaddr_in serverAddress;
	serverAddress.sin_family = AF_INET;
	serverAddress.sin_addr.s_addr = htonl(address);
	serverAddress.sin_port   = htons(port);
	int rc = ::bind(m_sock, (struct sockaddr *)&serverAddress, sizeof(serverAddress));
	if (rc == -1) {
		ESP_LOGE(LOG_TAG, "bind_cpp: bind[socket=%d]: %d: %s", m_sock, errno, strerror(errno));
		return;
	}
} // bind_cpp


/**
 * @brief Close the socket.
 *
 * @return N/A.
 */
void Socket::close_cpp() {
	ESP_LOGD(LOG_TAG, "close_cpp: m_sock=%d", m_sock);
	if (m_sock != -1) {
		::close(m_sock);
	}
	m_sock = -1;
} // close_cpp


/**
 * @brief Connect to a partner.
 *
 * @param [in] address The IP address of the partner.
 * @param [in] port The port number of the partner.
 * @return Success or failure of the connection.
 */
int Socket::connect_cpp(struct in_addr address, uint16_t port) {
	struct sockaddr_in serverAddress;
	serverAddress.sin_family = AF_INET;
	serverAddress.sin_addr   = address;
	serverAddress.sin_port   = htons(port);
	char msg[50];
	inet_ntop(AF_INET, &address, msg, sizeof(msg));
	ESP_LOGD(LOG_TAG, "Connecting to %s:[%d]", msg, port);
	createSocket_cpp();
	int rc = ::connect(m_sock, (struct sockaddr *)&serverAddress, sizeof(struct sockaddr_in));
	if (rc == -1) {
		ESP_LOGE(LOG_TAG, "connect_cpp: Error: %s", strerror(errno));
		close_cpp();
		return -1;
	} else {
		ESP_LOGD(LOG_TAG, "Connected to partner");
		return 0;
	}
} // connect_cpp


/**
 * @brief Connect to a partner.
 *
 * @param [in] strAddress The string representation of the IP address of the partner.
 * @param [in] port The port number of the partner.
 * @return Success or failure of the connection.
 */
int Socket::connect_cpp(char* strAddress, uint16_t port) {
	struct in_addr address;
	inet_pton(AF_INET, (char *)strAddress, &address);
	return connect_cpp(address, port);
}


/**
 * @brief Create the socket.
 * @param [in] isDatagram Set to true to create a datagram socket.  Default is false.
 * @return The socket descriptor.
 */
int Socket::createSocket_cpp(bool isDatagram) {
	if (isDatagram) {
		m_sock = ::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	}
	else {
		m_sock = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	}
	if (m_sock == -1) {
		ESP_LOGE(LOG_TAG, "createSocket_cpp: socket: %d", errno);
		return m_sock;
	}
	return m_sock;
} // createSocket_cpp


/**
 * @brief Get the bound address.
 * @param [out] pAddr The storage to hold the address.
 * @return N/A.
 */
void Socket::getBind_cpp(struct sockaddr* pAddr) {
	if (m_sock == -1) {
		ESP_LOGE(LOG_TAG, "getBind_cpp: Socket is not initialized.");
	}
	socklen_t nameLen = sizeof(struct sockaddr);
	::getsockname(m_sock, pAddr, &nameLen);
} // getBind_cpp


/**
 * @brief Get the underlying socket file descriptor.
 * @return The underlying socket file descriptor.
 */
int Socket::getFD() const {
	return m_sock;
} // getFD


bool Socket::isValid() {
	return m_sock != -1;
} // isValid

/**
 * @brief Create a listening socket.
 * @param [in] port The port number to listen upon.
 * @param [in] isDatagram True if we are listening on a datagram.
 */
void Socket::listen_cpp(uint16_t port, bool isDatagram) {
	createSocket_cpp(isDatagram);
	bind_cpp(port, 0);
	int rc = ::listen(m_sock, 5);
	if (rc == -1) {
		ESP_LOGE(LOG_TAG, "listen_cpp: %s", strerror(errno));
	}
} // listen_cpp

bool Socket::operator <(const Socket& other) const {
	return m_sock < other.m_sock;
}


std::string Socket::readToDelim(std::string delim) {
	std::string ret;
	std::string part;
	auto it = delim.begin();
	while(1) {
		uint8_t val;
		int rc = receive_cpp(&val, 1);
		if (rc == -1) {
			return "";
		}
		if (rc == 0) {
			return ret+part;
		}
		if (*it == val) {
			part+= val;
			++it;
			if (it == delim.end()) {
				return ret;
			}
		} else {
			if (part.empty()) {
				ret += part;
				part.clear();
				it = delim.begin();
			}
			ret += val;
		}
	} // While
} // readToDelim



/**
 * @brief Receive data from the partner.
 * Receive data from the socket partner.  If exact = false, we read as much data as
 * is available without blocking up to length.  If exact = true, we will block until
 * we have received exactly length bytes or there are no more bytes to read.
 * @param [in] data The buffer into which the received data will be stored.
 * @param [in] length The size of the buffer.
 * @param [in] exact Read exactly this amount.
 * @return The length of the data received or -1 on an error.
 */
size_t Socket::receive_cpp(uint8_t* data, size_t length, bool exact) {
	//ESP_LOGD(LOG_TAG, ">> receive_cpp: length: %d, exact: %d", length, exact);
	if (exact == false) {
		int rc = ::recv(m_sock, data, length, 0);
		if (rc == -1) {
			ESP_LOGE(LOG_TAG, "receive_cpp: %s", strerror(errno));
		}
		//GeneralUtils::hexDump(data, rc);
		return rc;
	}
	size_t amountToRead = length;
	while(amountToRead > 0) {
		int rc = ::recv(m_sock, data, amountToRead, 0);
		if (rc == -1) {
			ESP_LOGE(LOG_TAG, "receive_cpp: %s", strerror(errno));
			return 0;
		}
		if (rc == 0) {
			break;
		}
		amountToRead -= rc;
		data+= rc;
	}
	//GeneralUtils::hexDump(data, length);
	return length;
} // receive_cpp


/**
 * @brief Receive data with the address.
 * @param [in] data The location where to store the data.
 * @param [in] length The size of the data buffer into which we can receive data.
 * @param [in] pAddr An area into which we can store the address of the partner.
 * @return The length of the data received.
 */
int Socket::receiveFrom_cpp(uint8_t* data, size_t length,	struct sockaddr *pAddr) {
	socklen_t addrLen = sizeof(struct sockaddr);
	int rc = ::recvfrom(m_sock, data, length, 0, pAddr, &addrLen);
	return rc;
} // receiveFrom_cpp


/**
 * @brief Send data to the partner.
 *
 * @param [in] data The buffer containing the data to send.
 * @param [in] length The length of data to be sent.
 * @return N/A.
 *
 */
int Socket::send_cpp(const uint8_t* data, size_t length) const {
	ESP_LOGD(LOG_TAG, "send_cpp: Raw binary of length: %d", length);
	//GeneralUtils::hexDump(data, length);
	int rc = ::send(m_sock, data, length, 0);
	if (rc == -1) {
		ESP_LOGE(LOG_TAG, "send: socket=%d, %s", m_sock, strerror(errno));
	}
	return rc;
} // send_cpp


/**
 * @brief Send a string to the partner.
 *
 * @param [in] value The string to send to the partner.
 * @return N/A.
 */
int Socket::send_cpp(std::string value) const {
	ESP_LOGD(LOG_TAG, "send_cpp: Binary of length: %d", value.length());
	return send_cpp((uint8_t *)value.data(), value.size());
} // send_cpp


int Socket::send_cpp(uint16_t value) {
	ESP_LOGD(LOG_TAG, "send_cpp: 16bit value: %.2x", value);
	return send_cpp((uint8_t *)&value, sizeof(value));
} // send_cpp

int  Socket::send_cpp(uint32_t value) {
	ESP_LOGD(LOG_TAG, "send_cpp: 32bit value: %.2x", value);
	return send_cpp((uint8_t *)&value, sizeof(value));
} // send_cpp


/**
 * @brief Send data to a specific address.
 * @param [in] data The data to send.
 * @param [in] length The length of the data to send/
 * @param [in] pAddr The address to send the data.
 */
void Socket::sendTo_cpp(const uint8_t* data, size_t length, struct sockaddr* pAddr) {
	int rc = ::sendto(m_sock, data, length, 0, pAddr, sizeof(struct sockaddr));
	if (rc == -1) {
		ESP_LOGE(LOG_TAG, "sendto_cpp: socket=%d %s", m_sock, strerror(errno));
	}
} // sendTo_cpp

/**
 * @brief Get the string representation of this socket
 * @return the string representation of the socket.
 */
std::string Socket::toString() {
	std::ostringstream oss;
	oss << "fd: " << m_sock;
	return oss.str();
} // toString


/**
 * @brief Create a socket input record streambuf
 * @param [in] socket The socket we will be reading from.
 * @param [in] dataLength The size of a record.
 * @param [in] bufferSize The size of the buffer we wish to allocate to hold data.
 */
SocketInputRecordStreambuf::SocketInputRecordStreambuf(
	Socket  socket,
	size_t  dataLength,
	size_t  bufferSize) {
	m_socket     = socket;    // The socket we will be reading from
	m_dataLength = dataLength; // The size of the record we wish to read.
	m_bufferSize = bufferSize; // The size of the buffer used to hold data
	m_sizeRead   = 0;          // The size of data read from the socket
	m_buffer = new char[bufferSize]; // Create the buffer used to hold the data read from the socket.

	setg(m_buffer, m_buffer, m_buffer); // Set the initial get buffer pointers to no data.
} // SocketInputRecordStreambuf

SocketInputRecordStreambuf::~SocketInputRecordStreambuf() {
	delete[] m_buffer;
} // ~SocketInputRecordStreambuf


/**
 * @brief Handle the request to read data from the stream but we need more data from the source.
 *
 */
SocketInputRecordStreambuf::int_type SocketInputRecordStreambuf::underflow() {
	if (m_sizeRead >= m_dataLength) {
		return EOF;
	}
	int bytesRead = m_socket.receive_cpp((uint8_t*)m_buffer, m_bufferSize, true);
	if (bytesRead == 0) {
		return EOF;
	}
	m_sizeRead += bytesRead;
	setg(m_buffer, m_buffer, m_buffer + bytesRead);
	return traits_type::to_int_type(*gptr());
} // underflow