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

#define _FILE_OFFSET_BITS 64

#include <dirent.h>
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include "Error.h"
#include "Client2.h"

typedef unsigned long long uint64;

Client2::Client2() {
	force = 0;
	strcpy(prefix, "");
	numOfBytes = numOfCannotRead = numOfDoesNotExist = numOfFiles = 0;
}

void Client2::handle(const char *format, ...) {
	va_list ap;
	va_start(ap, format);
	char str[1024];
	vsprintf(str, format, ap);
	va_end(ap);

	if (force)
		printf("%s\n", str);
	else
		throw new Error(str);
}

void Client2::putFile(const char *path) {
	struct stat statBuf;
	if (stat(path, &statBuf) != 0) {
		if (errno == ENOENT) {
			handle("%s does not exist", path);
			numOfDoesNotExist++;
			return;
		}
		throw new Error("Cannot stat file \"%s\".", path);
	}

	char tmpName[8 * 1024];
	sprintf(tmpName, "%s/%s", prefix, path);
	wchar_t name[8 * 1024];
	if (mbstowcs(name, tmpName, 8 * 1024) == -1) {
		handle("Invalid character code in \"%s\".", tmpName);
		numOfCannotRead++;
		return;
	}

	if (S_ISDIR(statBuf.st_mode)) {
		DIR *dir = opendir(path);
		if (dir == NULL) {
			handle("Cannot open directory \"%s\".", path);
			numOfCannotRead++;
			return;
		}

		struct dirent *dirEntry;
		while ((dirEntry = readdir(dir)) != NULL) {
			if (strcmp(dirEntry->d_name, ".") == 0
				|| strcmp(dirEntry->d_name, "..") == 0)
				continue;

			char *tmpPathName = new char[strlen(path) + 1
				+ strlen(dirEntry->d_name) + 1];
			sprintf(tmpPathName, "%s/%s",
				path, dirEntry->d_name);
			putFile(tmpPathName);
			delete[] tmpPathName;
		}

		closedir(dir);

		// make empty dir and/or set the modification time
		mkdir(name, statBuf.st_mode, statBuf.st_mtime);
		return;
	}
	else
	if (!S_ISREG(statBuf.st_mode)) {
		handle("%s is not a regular file", path);
		numOfCannotRead++;
		return;
	}

	FILE *inStream = fopen(path, "rb");
	if (inStream == NULL) {
		handle("Cannot open file \"%s\".", path);
		numOfCannotRead++;
		return;
	}

	printf("%s %6.2f%%", path, 0.0);
	fflush(stdout);
	Client::putFile(name,
		statBuf.st_size, statBuf.st_mode, statBuf.st_mtime,
		inStream);
	fclose(inStream);
	printf("\n");

	numOfFiles++;
	numOfBytes += statBuf.st_size;
}

void Client2::putTrack(const char *type, const char *path) {
	uint64 length = 0;
	FILE *inStream;
	if (path == NULL
		|| strcmp(path, "-") == 0
		|| strcmp(path, "--stdin") == 0) {
		path = "stdin";
		inStream = stdin;
	}
	else {
		struct stat statBuf;
		if (stat(path, &statBuf) != 0) {
			if (errno == ENOENT) {
				handle("%s does not exist", path);
				numOfDoesNotExist++;
				return;
			}
			throw new Error("Cannot stat file \"%s\".", path);
		}

		if (S_ISDIR(statBuf.st_mode))
			throw new Error("%s is a directory", path);

		if (S_ISREG(statBuf.st_mode))
			length = statBuf.st_size;

		inStream = fopen(path, "rb");
		if (inStream == NULL) {
			handle("Cannot open track \"%s\".", path);
			numOfCannotRead++;
			return;
		}
	}

	printf("Track (%s) %s ", type, path);
	if (length > 0)
		printf("%9.2f%%", 0.0);
	else
		printf("%8d KB", 0);
	fflush(stdout);

	uint64 bytesTransmitted = 0;
	while (1) {
		static char buf[64 * 1024];
		int n = fread(buf, 1, sizeof buf, inStream);
		if (n == 0)
			break;
		Client::putTrack(type, n, buf);
		bytesTransmitted += n;
		if (length > 0) {
			printf("\b\b\b\b\b\b\b\b\b\b%9.2f%%",
				100.0 * bytesTransmitted / length);
			fflush(stdout);
		}
		else {
			printf("\b\b\b\b\b\b\b\b\b\b\b%8d KB",
				(int) (bytesTransmitted / 1024));
			fflush(stdout);
		}
		type = "continue";
	}
	printf("\n");

	if (inStream != stdin)
		fclose(inStream);

	if (bytesTransmitted == 0)
		throw new Error("Track \"%s\" seems to be empty.", path);

	numOfFiles++;
	numOfBytes += bytesTransmitted;
}

void Client2::setForce() {
	force = 1;
}

void Client2::setPrefix(const char *path) {
	strcpy(prefix, path);
}
