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

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

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

void Client::burnSession(CommandArgs &args, int blankTOC) {
	if (blankTOC) {
		fprintf(outStream, "setBlankTOC\n");
		readResult();
	}
	if (!args.multi) {
		fprintf(outStream, "setCloseCD\n");
		readResult();
	}
	if (args.copies > 1) {
		fprintf(outStream, "setCopies %d\n", args.copies);
		readResult();
	}
	if (strstr(args.medium, "DVD") != NULL)
		exec("setMedium", args.medium);
	if (args.dummy) {
		fprintf(outStream, "setLaserOff\n");
		readResult();
	}
	if (args.speed >= 1) {
		fprintf(outStream, "setSpeed %d\n", args.speed);
		readResult();
	}
	if (args.verify) {
		fprintf(outStream, "setVerify\n");
		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) {
	exec("client", product, version);
}

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);

	exec("CDWP");

	char *strs[10];
	int count = 0;
	if (mp3)
		strs[count++] = "mp3 decoding for audio CDs";
	if (verify)
		strs[count++] = "verifying CDs";

	if (count > 0)
		printf("CDWserver supports ");
	for (int i = 0; i < count; i++)
		if (i + 2 < count)
			printf("%s, ", strs[i]);
		else
		if (i + 1 < count)
			printf("%s, and ", strs[i]);
		else
			printf("%s", strs[i]);
	if (count > 0)
		printf(".\n");
}

void Client::exec(const char *command,
		const char *arg1,
		const char *arg2,
		const char *arg3) {
	fprintf(outStream, "%s", command);

	if (arg1[0] != 0) {
		const char *str = spaceToEsc(arg1);
		fprintf(outStream, " %s", str);
		delete[] str;
	}

	if (arg2[0] != 0) {
		const char *str = spaceToEsc(arg2);
		fprintf(outStream, " %s", str);
		delete[] str;
	}

	if (arg3[0] != 0) {
		const char *str = spaceToEsc(arg3);
		fprintf(outStream, " %s", str);
		delete[] str;
	}

	fprintf(outStream, "\n");
	readResult();
}

void Client::listSessions() {
	exec("listSessions");
}

void Client::login(const char *id, const char *password) {
	exec("login", id, password);
}

void Client::mkdir(wchar_t *name, int mode, int lastModified) {
	char *name2 = spaceToEsc(name);
	fprintf(outStream, "mkdir %s %o %d\n",
		name2, mode, lastModified);
	delete[] name2;

	readResult();
}

void Client::newAccount(const char *userID, const char *eMail) {
	exec("newAccount", userID, eMail);
}

void Client::newSession(const char *id) {
	exec("newSession", id);
}

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

void Client::openSession(const char *id) {
	exec("openSession", id);
}

void Client::putFile(wchar_t *name,
		int length, int mode, int lastModified,
		FILE *inStream) {
	char *name2 = spaceToEsc(name);
	fprintf(outStream, "putFile %s %d %o %d\n",
		name2, length, mode, lastModified);
	delete[] name2;

	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, "CDWverify") == 0) {
			int n;
			if (sscanf(msg, "%d", &n) == 1) {
				printf("\r%s", msg);
				fflush(stdout);
			}
			else
			if (verbose
				|| strstr(msg, "CDWverify ") == 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 (strcmp(msg, "anonymousUse") == 0)
				anonymousUse = 1;
			else
			if (sscanf(msg, "capacity %d %d %d", &a, &b, &c) == 3) {
				totalKB = a;
				oldFreeKB = b;
				newFreeKB = c;
			}
			else
			if (strcmp(msg, "copySupport") == 0)
				copySupport = 1;
			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, "tracks %d", &n) == 1)
				;
			else
			if (sscanf(msg, "rank %d", &n) == 1)
				printf("No %d in the waiting queue\n", n);
			else
			if (strcmp(msg, "verify") == 0)
				verify = 1;
			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, hintFixating) == 0)
				;
			else
			if (strcmp(msg, hintImageReady) == 0)
				;
			else
			if (strcmp(msg, hintMakingImage) == 0)
				printf("\nmkisofs started ==>\n");
			else
			if (strstr(msg, hintPercentDone) == msg)
				;
			else
			if (strcmp(msg, hintVerifying) == 0)
				printf("\nCDWverify 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;
}

/**
 * replace 0x00 to 0x20 by %00 to %20
 */
char *Client::spaceToEsc(const char *str) {
	wchar_t *str2 = new wchar_t[strlen(str) + 1];
	assert(mbstowcs(str2, str, strlen(str) + 1) != -1);
	char *str3 = spaceToEsc(str2);
	delete[] str2;
	return str3;
}

/**
 * replace 0x00 to 0x20 by %00 to %20
 */
char *Client::spaceToEsc(wchar_t *str) {
	int len = wcslen(str);
	char *str2 = new char[6 * len + 1];

	int i, j;
	for (i = 0, j = 0; i < len; i++) {
		wchar_t code = str[i];
		if (code > 0x20 && code < 0x7F && code != '%')
			str2[j++] = code;
		else
		if (code < 0x100) {
			str2[j++] = '%';
			sprintf(&str2[j], "%02x", code);
			j += 2;
		}
		else {
			str2[j++] = '%';
			str2[j++] = 'u';
			sprintf(&str2[j], "%04x", code);
			j += 4;
		}
	}

	str2[j] = 0;
	return str2;
}

void Client::system(const char *name, const char *version) {
	exec("system", name, version);
}

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