
#include <assert.h>
#include <netdb.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include "Client.h"
#include "Error.h"
#include "Hints.h"

Client::Client()
{
	verbose = 0;
	totalKB = oldFreeKB = newFreeKB = 0;
	CDwasClosed = mp3 = 0;
}

void Client::burnSession(int blankTOC, int closeCD, int simulate,
	int speed)
{
	if (blankTOC)
	{
		fprintf(outStream, "setBlankTOC\n");
		readResult();
	}
	if (closeCD)
	{
		fprintf(outStream, "setCloseCD\n");
		readResult();
	}
	if (simulate)
	{
		fprintf(outStream, "setLaserOff\n");
		readResult();
	}
	if (speed >= 1)
	{
		fprintf(outStream, "setSpeed %d\n", speed);
		readResult();
	}
	printf("Trying to reserve the CD-writer...\n");
	fprintf(outStream, "burnSession\n");
	readResult();
	printf("\n%d MB were successfully written.\n",
		(oldFreeKB - newFreeKB) / 1024);
	if (CDwasClosed)
		printf("The CD was closed. It's not possible to add "
			"further sessions.\n");
	else
		printf("Now there are %d of %d MB left.\n",
			newFreeKB / 1024, totalKB / 1024);
}

void Client::blank(const char *mode)
{
	printf("Trying to reserve the CD-writer...\n");
	fprintf(outStream, "blank %s\n", mode);
	readResult();
	printf("The CD-RW was successfully erased.\n");
}

void Client::client(const char *product, const char *version)
{
	fprintf(outStream, "client %s %s\n", product, version);
	readResult();
}

void Client::connect(const char *host, int port)
{
	struct hostent *hostinfo;
	hostinfo = gethostbyname(host);
	if (hostinfo == NULL)
		throw new Error("Cannot resolve hostname \"%s\".", host);

	socket = ::socket(PF_INET, SOCK_STREAM, 0);
	if (socket < 0)
		throw "Cannot get a socket.";

	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(socket, (struct sockaddr *)&addr, sizeof addr);
	if (res != 0)
		throw new Error("Cannot connect to \"%s:%d\".\n"
			"\tUse the dev option to set the CDWserver "
			"host (and port).", host, port);

	inStream = fdopen(socket, "r");
	assert(inStream != NULL);

	outStream = fdopen(socket, "w");
	assert(outStream != NULL);

	readResult();

	if (mp3)
		printf("CDWserver supports mp3 decoding.\n");
}

void Client::login(const char *id, const char *password)
{
	fprintf(outStream, "login %s %s\n", id, password);
	readResult();
}

void Client::mkdir(const char *name, int mode, int lastModified)
{
	fprintf(outStream, "mkdir %s %o %d\n", name, mode, lastModified);
	readResult();
}

void Client::newSession(const char *id)
{
	fprintf(outStream, "newSession %s\n", id);
	readResult();
}

void Client::noop()
{
	fprintf(outStream, "noop\n");
	readResult();
}

void Client::openSession(const char *id)
{
	fprintf(outStream, "openSession %s\n", id);
	readResult();
}

/**
 *	replace " " by "\_" and "\" by "\\"
*/
static char *spaceToEsc(const char *str, char *str2)
{
	int i, j;
	for (i = 0, j = 0; str[i] != 0; i++, j++)
	{
		if (str[i] == ' ')
		{
			str2[j++] = '\\';
			str2[j] = '_';
		}
		else
		if (str[i] == '\\')
		{
			str2[j++] = '\\';
			str2[j] = '\\';
		}
		else
			str2[j] = str[i];
	}
	str2[j] = 0;
	return str2;
}

void Client::putFile(const char *name,
	int length, int mode, int lastModified,
	FILE *inStream)
{
	char name2[2 * strlen(name) + 1];
	fprintf(outStream, "putFile %s %d %o %d\n",
		spaceToEsc(name, name2), length, mode, lastModified);

	int bytesTransmitted = 0;
	while (bytesTransmitted < length)
	{
		char buf[32 * 1024];
		int step = sizeof buf;
		if (step > length - bytesTransmitted)
			step = length - bytesTransmitted;
		int n = fread(buf, 1, step, inStream);
		if (n < step)
			break;
		int m = fwrite(buf, 1, n, outStream);
		if (m < n)
			throw "Cannot transmit block.";
		bytesTransmitted += n;
		if (length > 0)
		{
			printf("\b\b\b\b\b\b\b%6.2f%%",
				100.0 * bytesTransmitted / length);
			fflush(stdout);
		}
	}

	readResult();
}

void Client::putTrack(const char *type, int length, const void *bytes)
{
	fprintf(outStream, "putTrack %s %d\n", type, length);
	int n = fwrite(bytes, 1, length, outStream);
	if (n < length)
		throw "Cannot transmit block.";
	readResult();
}

void Client::quit()
{
	fprintf(outStream, "quit\n");
	readResult();
}

void Client::readResult()
{
	fflush(outStream);
	while (1)
	{
		char line[1024];
		if (fgets(line, sizeof line, inStream) == NULL)
			throw new Error("Unexpected end of inStream");

		char *nl = strchr(line, '\n');
		if (nl != NULL)
			*nl = 0;

		char type[1024], msg[1024];
		char *space = strchr(line, ' ');
		if (space == NULL)
		{
			strcpy(type, line);
			msg[0] = 0;
		}
		else
		{
			strncpy(type, line, space - line);
			type[space - line] = 0;
			strcpy(msg, space + 1);
		}
//		printf("type = <%s> msg = <%s>\n", type, msg);

		if (strcmp(type, "CDRECORD") == 0)
		{
			int no, a, b;
			if (strstr(msg, "Last chance to quit") == msg)
			{
				printf("\r%s", msg);
				fflush(stdout);
				if (strchr(msg, '1') != NULL)
					printf("\n");
			}
			else
			if (sscanf(msg, "Track %d: %d of %d",
				&no, &a, &b) == 3)
			{
				printf("\r%s", msg);
				fflush(stdout);
				if (a == b)
					printf("\n");
			}
			else
			if (msg[0] == 0)
				;
			else
			if (verbose
				|| strstr(msg, "Cdrecord ") == msg
				|| strstr(msg, "Blocks ") == msg
				|| strstr(msg, "Starting ") == msg
				|| strstr(msg, "Fixating...") == msg)
				printf("%s\n", msg);
		}
		else
		if (strcmp(type, "ERR") == 0)
			throw new Error("%s", msg);
		else
		if (strcmp(type, "HINT") == 0)
		{
			int a, b, c, n;
			if (sscanf(msg, "capacity %d %d %d", &a, &b, &c) == 3)
			{
				totalKB = a;
				oldFreeKB = b;
				newFreeKB = c;
			}
			else
			if (sscanf(msg, "defaultSpeed %d", &n) == 1)
				;
			else
			if (sscanf(msg, "files %d", &n) == 1)
				;
			else
			if (sscanf(msg, "maxSpeed %d", &n) == 1)
				;
			else
			if (strcmp(msg, "mp3") == 0)
				mp3 = 1;
			else
			if (sscanf(msg, "msinfo %d %d", &a, &b) == 2)
				;
			else
			if (strncmp(msg, "Track", 5) == 0)
				;
			else
			if (sscanf(msg, "rank %d", &n) == 1)
				printf("No %d in the waiting queue\n", n);
			else
			if (strcmp(msg, hintBlankingTOC) == 0)
				printf("\nBlanking the TOC...");
			else
			if (strcmp(msg, hintCDdetected) == 0)
				printf("detected\n");
			else
			if (strcmp(msg, hintCDrecordStarted) == 0)
				printf("\ncdrecord started ==>\n");
			else
			if (strcmp(msg, hintCDwasClosed) == 0)
				CDwasClosed = 1;
			else
			if (strcmp(msg, hintImageReady) == 0)
				;
			else
			if (strcmp(msg, hintMakingImage) == 0)
				printf("\nmkisofs started ==>\n");
			else
			if (strcmp(msg, hintWriterReserved) == 0)
				printf("Waiting for a CD-R(W)...%c", 7);
			else
				printf("%s\n", msg);
			fflush(stdout);
		}
		else
		if (strcmp(type, "INFO") == 0)
			printf("* %s\n", msg);
		else
		if (strcmp(type, "MKISOFS") == 0)
		{
			int newLine = 0;
			if (strstr(msg, "estimate finish") != NULL)
			{
				printf("\r%s", msg);
				fflush(stdout);
				newLine = 1;
			}
			else
			if (verbose
				|| strstr(msg, "mkisofs") == msg
				|| strstr(msg, "extents") != NULL)
			{
				if (newLine)
				{
					printf("\n");
					newLine = 0;
				}
				printf("%s\n", msg);
			}
		}
		else
		if (strcmp(type, "OK") == 0)
			break;
	}
}

void Client::setFilesystem(int joliet, int rockRidge, int HFS)
{
	if (joliet)
	{
		fprintf(outStream, "setJoliet\n");
		readResult();
	}
	if (HFS)
	{
		fprintf(outStream, "setHFS\n");
		readResult();
	}
	if (rockRidge)
	{
		fprintf(outStream, "setRockRidge\n");
		readResult();
	}
}

void Client::setProtocol(int version)
{
	fprintf(outStream, "setProtocol %d\n", version);
	readResult();
}

void Client::setVerbose()
{
	verbose = 1;
}

void Client::system(const char *name, const char *version)
{
	fprintf(outStream, "system %s %s\n", name, version);
	readResult();
}

void Client::user(int id, const char *name)
{
	fprintf(outStream, "user %d %s\n", id, name);
	readResult();
}
