/*
 *  $Id: AudioScrobblerClient.m 869 2007-06-18 15:51:33Z stephen_booth $
 *
 *  Copyright (C) 2006 - 2007 Stephen F. Booth <me@sbooth.org>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/*
 * This is a port of the BlockingClient client class from
 * the Last.fm ScrobSub library by sharevari
 */

#import "AudioScrobblerClient.h"

#include <arpa/inet.h>

#import "Logging.h"

#define kBufferSize 1024
#define kPortsToStep 5

static in_addr_t
addressForHost(NSString *hostname) {
	NSCParameterAssert(nil != hostname);

	in_addr_t address;
	struct hostent *hostinfo;

	address = inet_addr([hostname cStringUsingEncoding:NSASCIIStringEncoding]);

	if(INADDR_NONE == address) {
		hostinfo = gethostbyname([hostname cStringUsingEncoding:NSASCIIStringEncoding]);
		if(NULL == hostinfo) {
			ALog(@"AudioScrobblerClient error: Unable to resolve address for \"%@\".", hostname);
			return INADDR_NONE;
		}

		address = *((in_addr_t *)hostinfo->h_addr_list[0]);
	}

	return address;
}

@interface AudioScrobblerClient (Private)
- (BOOL)connectToSocket:(in_addr_t)remoteAddress port:(in_port_t)port;
@end

@implementation AudioScrobblerClient

- (id)init {
	if((self = [super init])) {
		_socket = -1;
		_doPortStepping = YES;
	}
	return self;
}

- (BOOL)connectToHost:(NSString *)hostname port:(in_port_t)port {
	NSParameterAssert(nil != hostname);

	in_addr_t remoteAddress = addressForHost(hostname);

	if(INADDR_NONE != remoteAddress)
		return [self connectToSocket:remoteAddress port:port];

	return NO;
}

- (BOOL)isConnected {
	return (-1 != _socket);
}

- (in_port_t)connectedPort {
	return _port;
}

- (void)send:(NSString *)data {
	const char *utf8data = [data UTF8String];
	unsigned len = (unsigned int)strlen(utf8data);
	unsigned bytesToSend = len;
	unsigned totalBytesSent = 0;
	ssize_t bytesSent = 0;

	if(NO == [self isConnected]) {
		ALog(@"AudioScrobblerClient error: Can't send data, client not connected");
		return;
	}

	while(totalBytesSent < bytesToSend && -1 != bytesSent) {
		bytesSent = send(_socket, utf8data + totalBytesSent, bytesToSend - totalBytesSent, 0);

		if(-1 == bytesSent || 0 == bytesSent)
			ALog(@"AudioScrobblerClient error: Unable to send data through socket: %s", strerror(errno));

		totalBytesSent += bytesSent;
	}
}

- (NSString *)receive {
	char buffer[kBufferSize];
	int readSize = kBufferSize - 1;
	ssize_t bytesRead = 0;
	BOOL keepGoing = YES;
	NSString *result = nil;

	if(NO == [self isConnected]) {
		ALog(@"AudioScrobblerClient error: Can't receive data, client not connected");
		return nil;
	}

	do {
		bytesRead = recv(_socket, buffer, readSize, 0);
		if(-1 == bytesRead || 0 == bytesRead) {
			ALog(@"AudioScrobblerClient error: Unable to receive data through socket: %s", strerror(errno));
			break;
		}

		if('\n' == buffer[bytesRead - 1]) {
			--bytesRead;
			keepGoing = NO;
		}

		buffer[bytesRead] = '\0';
		result = [[NSString alloc] initWithUTF8String:buffer];

	} while(keepGoing);

	return result;
}

- (void)shutdown {
	int result;
	char buffer[kBufferSize];
	ssize_t bytesRead;

	if(NO == [self isConnected]) {
		return;
	}

	result = shutdown(_socket, SHUT_WR);
	if(-1 == result)
		ALog(@"AudioScrobblerClient error: Socket shutdown failed: %s", strerror(errno));

	for(;;) {
		bytesRead = recv(_socket, buffer, kBufferSize, 0);
		if(-1 == bytesRead)
			ALog(@"AudioScrobblerClient error: Waiting for shutdown confirmation failed: %s", strerror(errno));

		if(0 != bytesRead) {
			NSString *received = [[NSString alloc] initWithBytes:buffer length:bytesRead encoding:NSUTF8StringEncoding];
			ALog(@"Received unexpected bytes during shutdown: %@", received);
		} else
			break;
	}

	result = close(_socket);
	if(-1 == result)
		ALog(@"Couldn't close socket (%s)", strerror(errno));

	_socket = -1;
	_port = 0;
}

@end

@implementation AudioScrobblerClient (Private)

- (BOOL)connectToSocket:(in_addr_t)remoteAddress port:(in_port_t)port {
	NSParameterAssert(INADDR_NONE != remoteAddress);

	_port = port;

	int result;
	do {
		struct sockaddr_in socketAddress;

		_socket = socket(AF_INET, SOCK_STREAM, 0);
		if(-1 == _socket) {
			ALog(@"Unable to create socket (%s)", strerror(errno));
			return NO;
		}

		socketAddress.sin_family = AF_INET;
		socketAddress.sin_addr.s_addr = remoteAddress;
		socketAddress.sin_port = htons(_port);

		result = connect(_socket, (const struct sockaddr *)&socketAddress, sizeof(struct sockaddr_in));
		if(-1 == result) {
			close(_socket);
			_socket = -1;

			_port++;
		}
	} while(YES == _doPortStepping && -1 == result && _port < (port + kPortsToStep));

	return (-1 != result);
}

@end
