/*  $Id: socket.cpp,v 1.24 2003/11/11 10:13:25 grumbel Exp $
**
**  ClanLib Game SDK
**  Copyright (C) 2003  The ClanLib Team
**  For a total list of contributers see the file CREDITS.
**
**  This library is free software; you can redistribute it and/or
**  modify it under the terms of the GNU Lesser General Public
**  License as published by the Free Software Foundation; either
**  version 2.1 of the License, or (at your option) any later version.
**
**  This library is distributed in the hope that it will be useful,
**  but WITHOUT ANY WARRANTY; without even the implied warranty of
**  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
**  Lesser General Public License for more details.
**
**  You should have received a copy of the GNU Lesser General Public
**  License along with this library; if not, write to the Free Software
**  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
**
*/

#ifdef _MSC_VER
#pragma warning (disable:4786)
#endif

#ifndef WIN32
#include <unistd.h>
#include <fcntl.h>
#include <netinet/tcp.h>
#include <net/if.h>    // needed for struct ifreq and struct ifconf
#include <sys/ioctl.h> // needed for ioctl ( on linux and bsd )
#include <sys/types.h>
#include <sys/socket.h>
#define INVALID_SOCKET -1
#endif

#include "socket_generic.h"
#include "API/Core/System/error.h"
#include "API/Core/System/log.h"

// No socklen_t on MacOSX
#ifdef __APPLE__
#  define socklen_t int
#endif

/////////////////////////////////////////////////////////////////////////////
// CL_Socket construction:


// This is really bad to use.  It doesn't, and can't, properly set the domain variable
// which is used by GetAddr.  
CL_Socket::CL_Socket()
: impl(0)
{
}


CL_Socket::CL_Socket(int socket)
: impl(new CL_Socket_Generic)
{
	impl->add_ref();
	impl->sock = socket;

	input.socket = impl;
	output.socket = impl;
}

CL_Socket::CL_Socket(CL_Socket::Type type, Protocol protocol)
: impl(new CL_Socket_Generic)
{
	impl->add_ref();

	int domain;
	int in_type = SOCK_STREAM, in_proto = 0;

	switch (type)
	{
	case tcp: in_type = SOCK_STREAM; break;
	case udp: in_type = SOCK_DGRAM; break;
	}

	switch(protocol)
	{
	case ipv4:
		domain = PF_INET;
		break;
	case ipv6: 
		domain = PF_INET6; 
		break;
	default:
		throw CL_Error("CL_Socket::CL_Socket failed: Invalid protocol specified");
		domain = 0;
		break;
	}
	
	impl->sock = socket(domain, in_type, in_proto);
	impl->domain = domain;
	impl->protocol = in_proto;
	proto = type;
	if (impl->sock < 0) throw CL_Error("CL_Socket::CL_Socket failed: " + CL_Socket_Generic::get_error_message());

	input.socket = impl;
	output.socket = impl;
}

CL_Socket::CL_Socket(const CL_Socket &copy)
: impl(copy.impl)
{
	if (impl) impl->add_ref();
	input.socket = impl;
	output.socket = impl;
}

CL_Socket::~CL_Socket()
{
	if (impl) impl->release_ref();
}

/////////////////////////////////////////////////////////////////////////////
// CL_Socket attributes:

int CL_Socket::get_socket() const
{
	return impl->sock;
}

int CL_Socket::get_max_msg_size() const
{
/*
	From RFC 879 ( http://www.faqs.org/rfcs/rfc879.html ):

	"HOSTS MUST NOT SEND DATAGRAMS LARGER THAN 576 OCTETS UNLESS THEY
	HAVE SPECIFIC KNOWLEDGE THAT THE DESTINATION HOST IS PREPARED TO
	ACCEPT LARGER DATAGRAMS.

	This is a long established rule."
*/         
	return 4608;
}

CL_EventTrigger *CL_Socket::get_read_trigger() const
{
	if (impl->read == NULL) impl->read = impl->create_read_trigger();
	return impl->read;
}

CL_EventTrigger *CL_Socket::get_write_trigger() const
{
	if (impl->write == NULL) impl->write = impl->create_write_trigger();
	return impl->write;
}

CL_EventTrigger *CL_Socket::get_exception_trigger() const
{
	if (impl->exception == NULL) impl->exception = impl->create_exception_trigger();
	return impl->exception;
}

CL_IPAddress CL_Socket::get_source_address() const
{
//TODO:
// Fix this garbage
// IPv4 specific hack

	sockaddr_in addr_in;
	memset(&addr_in, 0, sizeof(addr_in));
	addr_in.sin_family = AF_INET;

	int size = sizeof(sockaddr_in);

	int result = getsockname(impl->sock, (sockaddr *) &addr_in, (socklen_t *)&size);
	if (result != 0) throw CL_Error("CL_Socket::get_source_address() failed.");

	return CL_Socket_Generic::create_ip_address(addr_in);
}

CL_IPAddress CL_Socket::get_dest_address() const
{
//TODO:
// Fix this garbage
// IPv4 specific hack

	sockaddr_in addr_in;
	memset(&addr_in, 0, sizeof(addr_in));
	addr_in.sin_family = AF_INET;

	int size = sizeof(sockaddr_in);

	int result = getpeername(impl->sock, (sockaddr *) &addr_in, (socklen_t *)&size);
	if (result != 0) throw CL_Error("CL_Socket::get_dest_address() failed.");

	return CL_Socket_Generic::create_ip_address(addr_in);
}

std::list<CL_IPAddress> CL_Socket::get_broadcast_addresses(const std::string &port) const
{
// Should be IPv6 safe

	std::list<CL_IPAddress> broadcast_addresses;

	// if not a IPv4 socket or not a UDP socket
	if(impl->domain != PF_INET)
		throw CL_Error("CL_Socket::get_broadcast_addresses() failed. Broadcasting is for IPv4 only");
	if(proto != udp)
		throw CL_Error("CL_Socket::get_broadcast_addresses() failed. Broadcasting is for UDP only");

#ifdef WIN32
	// win32 seems to always use 255.255.255.255 as its broadcast address
	broadcast_addresses.push_back(CL_IPAddress("255.255.255.255", port));
#else
	// UNIX
#ifdef SIOCGIFBRDADDR
	// get interfaces
	struct ifconf ifc;
	int len = 100 * sizeof(struct ifreq), lastlen = 0,status;
	char *buf = 0;
	for (;;)
	{
		buf = new char[len];
		ifc.ifc_len = len;
		ifc.ifc_buf = buf;
		status = ioctl(impl->sock, SIOCGIFCONF, &ifc);
		if (0 == status)
		{
			if(lastlen == ifc.ifc_len) {
				if(lastlen == 0) delete [] buf;
				break;
			}
			lastlen = ifc.ifc_len;
		}
		else
		{
			CL_Log::log("debug", "CL_Socket::get_broadcast_addresses(): ioctl error trying to get network interfaces");
			lastlen = 0;
			break;
		}
		len += 10 * sizeof(struct ifreq);
		delete [] buf;
	}

	if (lastlen != 0)
	{
		// found interfaces!
		char *ptr = buf;
		char *end_ptr = buf + ifc.ifc_len;
		std::string lastname;
		while (ptr < end_ptr)
		{
			struct ifreq *ifr = (struct ifreq *) ptr;

			// point to next item
			ptr += sizeof (ifr->ifr_name) + sizeof (struct sockaddr);

			// if it isn't IPv4, skip
			if (ifr->ifr_addr.sa_family != AF_INET) continue;

			// make aliases show up as the same as the interface
			char *cptr;
			if ((cptr = strchr(ifr->ifr_name, ':')) != 0) *cptr = '\0';

			// if we have processed this interface before, skip
			if (lastname == ifr->ifr_name) continue;
			// save the interface name
			lastname = ifr->ifr_name;

			// get interface flags
			struct ifreq ifrcopy = *ifr;
			status = ioctl(impl->sock, SIOCGIFFLAGS, &ifrcopy);
			int flags = ifrcopy.ifr_flags;

			// if could not get flags or interface not up or no broadcast address, skip interface
			if (0 != status || !(flags & IFF_UP) || !(flags & IFF_BROADCAST)) continue;

			// get interface broadcast address
			status = ioctl(impl->sock, SIOCGIFBRDADDR, &ifrcopy);
			if (0 == status)
			{
				broadcast_addresses.push_back(CL_IPAddress(inet_ntoa(((sockaddr_in*)&ifrcopy.ifr_broadaddr)->sin_addr),port));
			}
			else
			{
				CL_Log::log("debug", "CL_Socket::get_broadcast_addresses(): Can't get broadcast address for interface %1", lastname);
			}
		}
		delete [] buf;
	}

	if(broadcast_addresses.empty())
	{
		CL_Log::log("debug", "CL_Socket::get_broadcast_addresses(): Can't get broadcast address. Using 255.255.255.255");
		broadcast_addresses.push_back(CL_IPAddress("255.255.255.255", port));
	}
#else // SIOCGIFBRDADDR
	// default to 255.255.255.255 if we do not support getting broadcast addresses
	broadcast_addresses.push_back(CL_IPAddress("255.255.255.255", port));
#endif // SIOCGIFBRDADDR
#endif // WIN32
	return broadcast_addresses;
}

/////////////////////////////////////////////////////////////////////////////
// CL_Socket operations:

CL_Socket &CL_Socket::operator =(const CL_Socket &other)
{
	if (impl) impl->release_ref();
	impl = other.impl;
	if (impl) impl->add_ref();
	input.socket = impl;
	output.socket = impl;
	return *this;
}

void CL_Socket::set_nonblocking(bool nonblocking)
{
#ifdef WIN32
	u_long argp = nonblocking ? 1 : 0;
	ioctlsocket(impl->sock, FIONBIO, &argp);
#else
	fcntl(impl->sock, F_SETFL, O_NONBLOCK, nonblocking ? 1 : 0);
#endif
}

void CL_Socket::set_nodelay(bool nodelay)
{
	static int sol_tcp = -1;
	if (sol_tcp == -1) sol_tcp = getprotobyname("tcp")->p_proto;

	int value = nodelay ? 1 : 0;
	setsockopt(impl->sock, sol_tcp, TCP_NODELAY, (const char *) &value, sizeof(int));
}

void CL_Socket::set_broadcasting(bool broadcasting)
{
	// if not a IPv4 socket or not a UDP socket
	if(impl->domain != PF_INET)
		throw CL_Error("CL_Socket::set_broadcasting() failed. Broadcasting is for IPv4 only");
	if(proto != udp)
		throw CL_Error("CL_Socket::set_broadcasting() failed. Broadcasting is for UDP only");

	int on = broadcasting ? 1 : 0;
	setsockopt(impl->sock, SOL_SOCKET, SO_BROADCAST, (const char *) &on, sizeof(on));
}

int CL_Socket::send(const void *data, int size)
{
	int result = ::send(
		impl->sock,
		(const char *) data,
		size,
		0);
	if (result < 0) throw CL_Error("CL_Socket::send failed");

	if (impl->write) impl->write->start_listen();
	return result;
}

int CL_Socket::send(const void *data, int size, const CL_IPAddress &dest)
{
	sockaddr addr;
	int len;

	dest.get_addrinfo(proto, addr, len, impl->domain);
	
	int result = ::sendto(
		impl->sock,
		(const char *) data,
		size,
		0,
		&addr,
		len);
	if (result < 0) throw CL_Error("CL_Socket::send failed");

	if (impl->write) impl->write->start_listen();
	return result;
}

int CL_Socket::send(const std::string &string)
{
	return send(string.c_str(), string.size());
}

int CL_Socket::recv(void *data, int size)
{
	int result = ::recv(impl->sock, (char *) data, size, 0);
	if (result < 0) throw CL_Error("CL_Socket::recv failed");

	if (impl->read) impl->read->start_listen();
	return result;
}

void CL_Socket::push(const std::string &string)
{
	impl->push_stack.push(string);
}

int CL_Socket::recv(void *data, int size, CL_IPAddress &from)
{
	char *_data = (char *) data;
	int total = 0;

	// Start filling data with pushed data (if any):
	while (!impl->push_stack.empty())
	{
		std::string &str = impl->push_stack.top();
		if (str.size() < (unsigned int)size)
		{
			memcpy(_data, str.data(), str.size());
			_data += str.size();
			total += str.size();
			size -= str.size();
			impl->push_stack.pop();
		}
		else
		{
			memcpy(_data, str.data(), size);
			str = str.substr(size);
			total += size;
			return total;
		}
	}

	// Fill with data from network socket:
	sockaddr_in addr_in;
	memset(&addr_in, 0, sizeof(addr_in));
	addr_in.sin_family = AF_INET;

	socklen_t addr_size = sizeof(addr_in);

	int result = ::recvfrom(
		impl->sock,
		_data,
		size,
		0,
		(sockaddr *) &addr_in,
		&addr_size);
	if (result < 0) throw CL_Error("CL_Socket::recv failed");
	total += result;

	if (impl->read) impl->read->start_listen();
	from = CL_Socket_Generic::create_ip_address(addr_in);
	return total;
}

void CL_Socket::connect(const CL_IPAddress &address)
{
	sockaddr addr;
	int len;

	address.get_addrinfo(proto, addr, len, impl->domain);

	int result = ::connect(impl->sock, &addr, len);
	if (result < 0) 
	{
		throw CL_Error("CL_Socket::connect failed: " + CL_Socket_Generic::get_error_message());
	}
	
	if (impl->read) impl->read->start_listen();
	if (impl->exception) impl->exception->start_listen();
}

void CL_Socket::shutdown(ShutdownHow how)
{
	int in_how = 0;
	switch (how)
	{
	case shutdown_receive: in_how = 0; break;
	case shutdown_send:    in_how = 1; break;
	}

	int result = ::shutdown(impl->sock, in_how);
	if (result < 0) throw CL_Error("CL_Socket::shutdown failed");
}

void CL_Socket::bind(const CL_IPAddress &address)
{
	sockaddr addr;
	int len;

	memset(&addr, 0, sizeof(addr));
	address.get_addrinfo(proto, addr, len, impl->domain);

// Unixes are bit more picky about when a port can be rebound. Override this behavior to match Windows.
// (not to mention spare us of every linux user asking why they cannot bind after they closed previous socket.)
// -- mbn 22. june 2003
#ifndef WIN32
	int value = 1;
	setsockopt(impl->sock, SOL_SOCKET, SO_REUSEADDR, (const char *) &value, sizeof(int));
#endif

	int result = ::bind(impl->sock, &addr, len);
	if (result < 0) throw CL_Error("CL_Socket::bind failed: " + CL_Socket_Generic::get_error_message());
}

void CL_Socket::listen(int backlog)
{
	int result = ::listen(impl->sock, backlog);
	if (result < 0) throw CL_Error("CL_Socket::listen failed");
}

CL_Socket CL_Socket::accept()
{
	int new_sock = ::accept(impl->sock, NULL, NULL);
	if (new_sock == INVALID_SOCKET) throw CL_Error("CL_Socket::accept failed");
	if (impl->read) impl->read->start_listen();
	return CL_Socket(new_sock);
}

/////////////////////////////////////////////////////////////////////////////
// CL_Socket signals:

CL_Signal_v0 &CL_Socket::sig_read_triggered()
{
	if (impl->keep_alive_helper == 0) impl->keep_alive_helper = new CL_Socket_KeepAliveHelper(impl);
	return impl->keep_alive_helper->sig_read_triggered;
}

CL_Signal_v0 &CL_Socket::sig_write_triggered()
{
	if (impl->keep_alive_helper == 0) impl->keep_alive_helper = new CL_Socket_KeepAliveHelper(impl);
	return impl->keep_alive_helper->sig_write_triggered;
}

CL_Signal_v0 &CL_Socket::sig_exception_triggered()
{
	if (impl->keep_alive_helper == 0) impl->keep_alive_helper = new CL_Socket_KeepAliveHelper(impl);
	return impl->keep_alive_helper->sig_exception_triggered;
}

CL_Signal_v0 &CL_Socket::sig_disconnected()
{
	if (impl->keep_alive_helper == 0) impl->keep_alive_helper = new CL_Socket_KeepAliveHelper(impl);
	return impl->keep_alive_helper->sig_disconnected;
}

/////////////////////////////////////////////////////////////////////////////
// CL_Socket implementation:

CL_Socket::CL_Socket(CL_Socket_Generic *impl)
: impl(impl)
{
	impl->add_ref();
	input.socket = impl;
	output.socket = impl;
}
