/*
 * Copyright (C) 2001-2004 Peter J Jones (pjones@pmade.org)
 * All Rights Reserved
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name of the Author nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS''
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/** @file
 * This file contains the implementation for the Netxx::Socket class along
 * with some helper functions.
**/

// common header
#include "common.h"

// Netxx includes
#include "socket.h"
#include "timeout.h"

// standard includes
#include <algorithm>
#include <string>
#include <cstring>
#include <cstdio>

//####################################################################
namespace
{
    const Netxx::socket_type const_invalid_socket = -1;
    void get_domain_and_type (Netxx::Socket::Type stype, int &domain, int &type);
}
//####################################################################
Netxx::Socket::Socket (void)
    : socketfd_(const_invalid_socket), probe_ready_(false), type_ready_(false)
{
#   if defined(WIN32)
	WSADATA wsdata;

	if (WSAStartup(MAKEWORD(2,2), &wsdata) != 0) {
	    throw Exception("failed to load WinSock");
	}
#   endif
}
//####################################################################
Netxx::Socket::Socket (Type type)
    : socketfd_(const_invalid_socket), probe_ready_(false), type_ready_(true), type_(type)
{
#   if defined(WIN32)
	WSADATA wsdata;

	if (WSAStartup(MAKEWORD(2,2), &wsdata) != 0) {
	    throw Exception("failed to load WinSock");
	}
#   endif

    int socket_domain=0;
    socket_type socket_type=0, socket_fd=0;

    get_domain_and_type(type, socket_domain, socket_type);

    if ( (socket_fd = socket(socket_domain, socket_type, 0)) < 0) {
	std::string error("failure from socket(2): ");
	error += str_error(get_last_error());
	throw Exception(error);
    }

    socketfd_ = socket_fd;
}
//####################################################################
Netxx::Socket::Socket (int socketfd)
    : socketfd_(socketfd), probe_ready_(false), type_ready_(false)
{
#   if defined(WIN32)
	WSADATA wsdata;

	if (WSAStartup(MAKEWORD(2,2), &wsdata) != 0) {
	    throw Exception("failed to load WinSock");
	}
#   endif
}
//####################################################################
Netxx::Socket::Socket (const Socket &other)
    : probe_ready_(false), type_ready_(other.type_ready_), type_(other.type_)
{
    if (other.socketfd_ != const_invalid_socket) {
	socket_type dup_socket;

#	if defined(WIN32)
	    WSADATA wsdata;

	    if (WSAStartup(MAKEWORD(2,2), &wsdata) != 0)
		throw Exception("failed to load WinSock");

	    WSAPROTOCOL_INFO proto_info;
	    std::memset(&proto_info, 0, sizeof(proto_info));

	    if (WSADuplicateSocket(other.socketfd_, GetCurrentProcessId(), &proto_info)) {
		throw Exception("failed to duplicate socket");
	    }

	    if ( (dup_socket = WSASocket(FROM_PROTOCOL_INFO, FROM_PROTOCOL_INFO,
		    FROM_PROTOCOL_INFO, &proto_info, 0, WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET)
	    {
		throw Exception("failed to init duplicated socket");
	    }
#	else
	    dup_socket = dup(other.socketfd_);

	    if (dup_socket < 0) {
		std::string error("dup(2) call failed: ");
		error += str_error(get_last_error());
		throw Exception(error);
	    }
#	endif

	socketfd_ = dup_socket;
    } else {
	socketfd_ = const_invalid_socket;
    }
}
//####################################################################
Netxx::Socket& Netxx::Socket::operator= (const Socket &other)
{
    Socket tmp(other); swap(tmp);
    return *this;
}
//####################################################################
void Netxx::Socket::swap (Socket &other)
{
    std::swap(socketfd_, other.socketfd_);
    std::swap(type_ready_, other.type_ready_);
    std::swap(type_, other.type_);

    probe_ready_ = other.probe_ready_ = false;
    probe_.clear(); other.probe_.clear();
}
//####################################################################
Netxx::Socket::~Socket (void)
{
    close();

#   if defined(WIN32)
	WSACleanup();
#   endif
}
//####################################################################
Netxx::signed_size_type Netxx::Socket::write (const void *buffer, size_type length, const Timeout &timeout)
{
    const char *buffer_ptr = static_cast<const char*>(buffer);
    signed_size_type rc;

    for (;;) {
	if (timeout && !writable(timeout)) return -1;

	if ( (rc = send(socketfd_, buffer_ptr, length, 0)) < 0) {
	    error_type error_code = get_last_error();
	    if (error_code == EWOULDBLOCK) error_code = EAGAIN;

	    switch (error_code) {
		case EPIPE:
		case ECONNRESET:
#ifdef ETIMEDOUT
		case ETIMEDOUT:
#endif
		    return 0;

		case EINTR:
		    continue;

		case EAGAIN:
		    return -1;

#if defined(WIN32)
		case WSAENETRESET:
		case WSAESHUTDOWN:
		case WSAEHOSTUNREACH:
		case WSAECONNABORTED:
		case WSAETIMEDOUT:
		    return 0;
#endif

		default:
		{
		    std::string error("send failed: ");
		    error += str_error(error_code);
		    throw Exception(error);
		}
	    }
	}

	break;
    }

    return rc;
}
//####################################################################
Netxx::signed_size_type Netxx::Socket::read (void *buffer, size_type length, const Timeout &timeout)
{
    signed_size_type rc;

#   if defined(WIN32)
	char *buffer_ptr = static_cast<char*>(buffer);
#   else
	void *buffer_ptr(buffer);
#   endif

    for (;;) {
	if (timeout && !readable(timeout)) return -1;

	if ( (rc = recv(socketfd_, buffer_ptr, length, 0)) < 0) {
	    error_type error_code = get_last_error();
	    if (error_code == EWOULDBLOCK) error_code = EAGAIN;

	    switch (error_code) {
		case ECONNRESET:
#ifdef ETIMEDOUT
                case ETIMEDOUT:
#endif
		    return 0;

		case EINTR:
		    continue;

		case EAGAIN:
		    return -1;

#if defined(WIN32)

		case WSAEMSGSIZE:
		    return length;

		case WSAENETRESET:
		case WSAESHUTDOWN:
		case WSAECONNABORTED:
		case WSAETIMEDOUT: // FIXME should this be return -1?
		    return 0;

#endif

		default:
		{
		    std::string error("recv failure: ");
		    error += str_error(error_code);
		    throw Exception(error);
		}
	    }
	}

	break;
    }

    return rc;
}
//####################################################################
bool Netxx::Socket::readable (const Timeout &timeout)
{
    if (!probe_ready_) {
	probe_.add(socketfd_);
	probe_ready_ = true;
    }

    Probe_impl::probe_type pt = probe_.probe(timeout, Probe::ready_read);
    if (pt.empty()) return false;
    return true;
}
//####################################################################
bool Netxx::Socket::writable (const Timeout &timeout)
{
    if (!probe_ready_) {
	probe_.add(socketfd_);
	probe_ready_ = true;
    }

    Probe_impl::probe_type pt = probe_.probe(timeout, Probe::ready_write);
    if (pt.empty()) return false;
    return true;
}
//####################################################################
bool Netxx::Socket::readable_or_writable (const Timeout &timeout)
{
    if (!probe_ready_) {
	probe_.add(socketfd_);
	probe_ready_ = true;
    }

    Probe_impl::probe_type pt = probe_.probe(timeout, Probe::ready_read|Probe::ready_write);
    if (pt.empty()) return false;
    return true;
}
//####################################################################
void Netxx::Socket::close (void)
{
    if (socketfd_ != const_invalid_socket) {
#	if defined(WIN32)
	    closesocket(socketfd_);
#	else
	    ::close(socketfd_);
#	endif

	socketfd_ = const_invalid_socket;
    }
}
//####################################################################
void Netxx::Socket::release (void)
{
  socketfd_ = const_invalid_socket;
}
//####################################################################
Netxx::Socket::Type Netxx::Socket::get_type (void) const
{
    if (!type_ready_) throw Exception("Netxx bug: Socket::get_type called when !type_ready_");
    return type_;
}
//####################################################################
int Netxx::Socket::get_socketfd (void) const
{
    return socketfd_;
}
//####################################################################
void Netxx::Socket::set_socketfd (socket_type socketfd)
{
    close();
    probe_ready_ = false;
    socketfd_ = socketfd;
    probe_.clear();
}
//####################################################################
bool Netxx::Socket::operator! (void) const
{
    return socketfd_ == const_invalid_socket;
}
//####################################################################
bool Netxx::Socket::operator< (const Socket &other) const
{
    return socketfd_ < other.socketfd_;
}
//####################################################################
bool Netxx::Socket::operator< (socket_type socketfd) const
{
    return socketfd_ < socketfd;
}
//####################################################################
bool Netxx::operator== (const Netxx::Socket &first, const Netxx::Socket &second)
{
    return first.get_socketfd() == second.get_socketfd();
}
//####################################################################
bool Netxx::operator!= (const Netxx::Socket &first, const Netxx::Socket &second)
{
    return !(first == second);
}
//####################################################################
namespace
{
    //####################################################################
    void get_domain_and_type (Netxx::Socket::Type stype, int &domain, int &type)
    {
	switch (stype) {
	    case Netxx::Socket::TCP:
		domain = PF_INET;
		type	= SOCK_STREAM;
		break;

	    case Netxx::Socket::UDP:
		domain	= PF_INET;
		type	= SOCK_DGRAM;
		break;

#   ifndef NETXX_NO_INET6
	    case Netxx::Socket::TCP6:
		domain	= PF_INET6;
		type	= SOCK_STREAM;
		break;

	    case Netxx::Socket::UDP6:
		domain	= PF_INET6;
		type	= SOCK_DGRAM;
		break;
#   endif
#   ifndef WIN32
	    case Netxx::Socket::LOCALSTREAM:
		domain	= PF_LOCAL;
		type	= SOCK_STREAM;
		break;

	    case Netxx::Socket::LOCALDGRAM:
		domain	= PF_LOCAL;
		type	= SOCK_DGRAM;
		break;
#   endif
	    default:
		domain = PF_INET;
		type	= SOCK_STREAM;
		break;
	}
    }
    //####################################################################
} // end anonymous namespace
