/*
 * @(#)Socket.cpp
 *
 * This file is part of webCDwriter - Network CD Writing.
 *
 * Copyright (C) 2002-2004 Jörg P. M. Haeger
 *
 * webCDwriter is free software. See CDWserver.cpp for details.
 *
 * Jörg Haeger, 19.04.2002
 */

#include <arpa/inet.h>
#include <errno.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <unistd.h>
#include "BufferedReader.h"
#include "InputStream.h"
#include "Support.h"
#include "Socket.h"

Socket::Socket() {
	fd = -1;
	inputStream = NULL;
	inputStream2 = NULL;
}

Socket::Socket(int fd): inetAddress("unknown") {
	this->fd = fd;
	inputStream = new BufferedReader(fd);
	inputStream2 = new InputStream(fd);
}

Socket::Socket(const char *host, int port) {
	struct hostent *hostinfo = gethostbyname(host);
	if (hostinfo == NULL)
		throw new Exception("Socket: hostinfo == NULL");

	fd = ::socket(PF_INET, SOCK_STREAM, 0);
	if (fd < 0)
		throw new Exception("Socket: fd < 0");

	struct sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_port = htons(port);
	addr.sin_addr = *(struct in_addr *)hostinfo->h_addr;

	int res = ::connect(fd, (struct sockaddr *)&addr, sizeof addr);
	if (res != 0)
		throw new Exception("Socket: cannot connect");

	inputStream = new BufferedReader(fd);
	inputStream2 = new InputStream(fd);
}

Socket::~Socket() {
	if (inputStream2 != NULL)
		delete inputStream2;
	if (inputStream != NULL)
		delete inputStream;

	if (fd < 0)
		return;

	shutdown(fd, 1);

	while (1) {
		fd_set fdSet;
		FD_ZERO(&fdSet);
		FD_SET(fd, &fdSet);
		struct timeval timeout;
		timeout.tv_sec = 10;
		timeout.tv_usec = 0;

		int res;
		while (1) {
			res = select(FD_SETSIZE, &fdSet, NULL, NULL, &timeout);
			if (res != -1 || errno != EINTR)
				break;
		}
		if (res == 0)
			break;

		char buf[4 * 1024];
		if (read(fd, buf, sizeof buf) <= 0)
			break;
	}

	close(fd);
}

String &Socket::getInetAddress() {
	struct sockaddr_in name;
	socklen_t len = sizeof name;
	if (getpeername(fd, (struct sockaddr *)&name, &len) == 0) {
		char str[256];
		if (inet_ntop(name.sin_family, &name.sin_addr,
			str, sizeof str) != NULL) {
			inetAddress = new String(str);
			return inetAddress;
		}
	}

	return inetAddress;
}

BufferedReader &Socket::getInputStream() {
	return *inputStream;
}

InputStream &Socket::getInputStream2() {
	return *inputStream2;
}

int Socket::getOutputStream() {
	return fd;
}

int Socket::getPort() {
	struct sockaddr_in name;
	socklen_t len = sizeof name;
	if (getpeername(fd, (struct sockaddr *)&name, &len) == 0)
		return name.sin_port;

	return 0;
}

void Socket::setKeepAlive(boolean on) {
	int onoff = on;
	if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE,
		(void *)&onoff, sizeof onoff) < 0)
			throw new Exception(S.e + "setsockopt SO_KEEPALIVE: "
				+ strerror(errno));
}

void Socket::setSoLinger(boolean on, int linger) {
	struct linger li;
	li.l_onoff = on;
	li.l_linger = linger;
	if (setsockopt(fd, SOL_SOCKET, SO_LINGER,
		(void *)&li, sizeof li) < 0)
			throw new Exception(S.e + "setsockopt SO_LINGER: "
				+ strerror(errno));
}

int Socket::write(const char *buf, int len) {
	int p = 0;
	while (true) {
		int n = ::write(fd, &buf[p], len - p);
		if (n < 0) {
			if (errno == EINTR)
				continue;
			return p;
		}
		p += n;
		if (p >= len)
			break;
	}
}
