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

#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include "Config.h"
#include "Support.h"
#include "Log.h"
#include "MP3toWAV.h"
#include "Queue.h"
#include "Server.h"
#include "Session.h"
#include "Store.h"

const char
	CDWserverInfo[] = "CDWserver.info";

Session::Session(Server *server, const char *ID, const char *dir)
{
	this->server = server;
	this->ID = strDup(ID);
	this->dir = new File(dir);
	filesNum = tracksNum = sizeInKBytes = 0;
	currentTrack = NULL;
	currentTrackNo = 0;
	mp3towav = NULL;

	if (this->dir->exists())
		readInfo();
	else
		create();
}

Session::~Session()
{
	if (mp3towav != NULL)
		closeMP3Track();

	writeInfo();

	store->unregister(this);

	if (config.getRemoveSessions())
		store->removeSession(ID);

	if (currentTrack != NULL)
		delete currentTrack;

	delete dir;
	delete[] ID;
}

void Session::closeMP3Track()
{
	log.putv("Session::closeMP3Track()");
	mp3towav->end();
	FILE *outStream = fopen(currentTrack->getName(), "r+");
	if (outStream != NULL)
	{
		fseek(outStream, 0, SEEK_END);
		try {
			while (1)
			{
				const char *buf;
				int n = mp3towav->get(&buf);
				if (n == 0)
					break;
				copy(buf, outStream, n);
			}
		} catch (const char *str)
		{
		}
		mp3towav->updateHeader(outStream);
		fclose(outStream);
	}

	delete mp3towav;
	mp3towav = NULL;
}

void Session::copy(FILE *inStream, FILE *outStream, int length)
{
	try {
		copy2(inStream, outStream, length);
	}
	catch (const char *msg)
	{
		skipBytes(inStream, length);
		throw msg;
	}
}

void Session::copy(const char *buf, FILE *outStream, int length)
{
	int KBytes = sizeToKBytes(length);
	if (sizeInKBytes + KBytes > 1024 * config.getMaxMBytesPerSession())
		throw "session to big";

	int fd = fileno(outStream);
	if (fd == -1)
		throw "fileno";

	sizeInKBytes += sizeToKBytes(length);
	store->write(buf, length, fd);
}

void Session::copy2(FILE *inStream, FILE *outStream, int &length)
{
	int KBytes = sizeToKBytes(length);
	if (sizeInKBytes + KBytes > 1024 * config.getMaxMBytesPerSession())
		throw "session to big";

	int fd = fileno(outStream);
	if (fd == -1)
		throw "fileno";

	while (length > 0)
	{
		char buf[16 * 1024];
		int n = fread(buf, 1,
			min(length, sizeof buf), inStream);
		length -= n;
		if (feof(inStream))
			throw "unexpected end of inStream";
		sizeInKBytes += sizeToKBytes(n);
		store->write(buf, n, fd);
	}
}

void Session::countFile(const char *pathName)
{
	struct stat statBuf;
	if (lstat(pathName, &statBuf) != 0)
		return;

	if (!S_ISDIR(statBuf.st_mode))
	{
		if (!S_ISREG(statBuf.st_mode))
			return;
		filesNum++;
		sizeInKBytes += sizeToKBytes(statBuf.st_size);
		return;
	}

	DIR *dir = opendir(pathName);
	if (dir == NULL)
		return;

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

		char tmpPathName[strlen(pathName) + 1
			+ strlen(dirEntry->d_name) + 1];
		sprintf(tmpPathName, "%s/%s",
			pathName, dirEntry->d_name);
		countFile(tmpPathName);
	}

	closedir(dir);
}

void Session::create()
{
	File infoFile(*dir, CDWserverInfo);
	infoFile.mkdirs();
	FILE *outStream = fopen(infoFile.getName(), "w");
	if (outStream == NULL)
		throw "internal error: Cannot create infofile.";
	fclose(outStream);
}

File *Session::getFirstTrack()
{
	if (mp3towav != NULL)
		closeMP3Track();

	currentTrackNo = 0;
	return getNextTrack();
}

void Session::getInfo(const char *ID,
	int &numOfFiles, int &numOfTracks, int &size_KB)
{
	File *dir = store->getSessionDir(ID);
	log.putv("dir = %s", dir->getName());
	if (dir->exists())
		readInfo(dir, numOfFiles, numOfTracks, size_KB);
	else
		numOfFiles = numOfTracks = size_KB = 0;
	delete dir;
}

int Session::getSessionSize(File &dir)
{
	int sizeInKBytes = 0;
	File file(dir, CDWserverInfo);
	FILE *inStream = fopen(file.getName(), "r");
	if (inStream == NULL)
		throw "internal error: can't read sessionInfo";

	char lineStr[1024];
	while (fgets(lineStr, sizeof lineStr, inStream) != NULL)
	{
		char str1[1024], str2[1024];
		Config::parseLine(lineStr, str1, str2);

		if (strlen(str1) == 0 && strlen(str2) == 0)
			continue;
		else
		if (strcasecmp(str1, "sizeInKBytes") == 0)
			sscanf(str2, "%d", &sizeInKBytes);
	}
	fclose(inStream);
	return sizeInKBytes;
}

File *Session::getNextTrack()
{
	File *nextTrack = NULL;

	DIR *dir = opendir(this->dir->getName());
	if (dir == NULL)
		return NULL;

	struct dirent *dirEntry;
	int n;
	while ((dirEntry = readdir(dir)) != NULL)
		if (sscanf(dirEntry->d_name, "track%d", &n) == 1)
			if (n == currentTrackNo + 1)
			{
				nextTrack = new File(
					this->dir, dirEntry->d_name);
				break;
			}

	closedir(dir);

	if (nextTrack == NULL)
		return NULL;

	if (nextTrack->exists())
	{
		if (currentTrack != NULL)
			delete currentTrack;
		currentTrack = nextTrack;
		currentTrackNo++;
		return currentTrack;
	}
	else
	{
		delete nextTrack;
		return NULL;
	}
}

int Session::isSessionDir(File &dir)
{
	File file(dir, CDWserverInfo);
	return file.exists();
}

void Session::linkFile(const char *fileName, const char *linkName)
{
	if (currentTrack == NULL
		|| !currentTrack->isDirectory())
		newTrack("dir");

	File link(currentTrack, linkName);
	link.mkdirs();
	symlink(fileName, link.getName());
//	sizeInKBytes += sizeToKBytes(link.getSize());
}

void Session::linkTrack(const char *name, const char *type)
{
	newTrack(type);

	if (strcasecmp(type, "mp3") != 0)
	{
		symlink(name, currentTrack->getName());
//		sizeInKBytes += sizeToKBytes(File(name).getSize());
	}
	else
	{
		FILE *inStream = fopen(name, "r");
		if (inStream == NULL)
			throw new Error("cannot open %s", name);
		try {
			putTrack(inStream, File(name).getSize());
		}
		catch (const char *msg) {
			fclose(inStream);
			throw msg;
		}
		fclose(inStream);
	}
}

void Session::makeDir(const char *fileName, int mode, int time)
{
	if (currentTrack == NULL
		|| !currentTrack->isDirectory())
		newTrack("dir");

	File dir(currentTrack, fileName);
	File dir2(dir, ".");
	dir2.mkdirs();
	dir.setLastModified(time);
}

void Session::newTrack(const char *trackType)
{
	if (mp3towav != NULL)
		closeMP3Track();

	const char *extension = "";
	if (strcasecmp(trackType, "image") == 0
		|| strcasecmp(trackType, "iso") == 0)
		extension = ".iso";
	else
	if (strcasecmp(trackType, "wav") == 0)
		extension = ".wav";
	else
	if (strcasecmp(trackType, "mp3") == 0)
	{
		extension = ".wav";
		mp3towav = new MP3toWAV();
	}

	tracksNum++;
	char nextTrackName[20];
	sprintf(nextTrackName, "track%03d%s", tracksNum, extension);
	if (currentTrack != NULL) {
		delete currentTrack;
		currentTrack = NULL;
	}
	currentTrack = new File(dir, nextTrackName);
	currentTrackNo = tracksNum;
}

void Session::putFile(FILE *inStream,
	const char *fileName, int length, int mode, int time)
{
	if (currentTrack == NULL
		|| !currentTrack->isDirectory())
		newTrack("dir");

	File file(currentTrack, fileName);
	file.mkdirs();
	file.remove();

	FILE *outStream = fopen(file.getName(), "w");
	if (outStream == NULL)
	{
		skipBytes(inStream, length);
		log.put("Session::putFile: fopen");
		throw "cannot store file";
	}

	try
	{
		copy(inStream, outStream, length);
	}
	catch (const char *msg)
	{
		fclose(outStream);
		throw msg;
	}
	filesNum++;
	fclose(outStream);
	chmod(file.getName(), mode);
	file.setLastModified(time);
}

void Session::putTrack(FILE *inStream, int length)
{
	FILE *outStream = fopen(currentTrack->getName(), "a");
	if (outStream == NULL)
	{
		skipBytes(inStream, length);
		throw "cannot store track";
	}

	try {
		if (mp3towav != NULL)
		{
			mp3towav->setInStream(inStream);
			mp3towav->put(length);
			while (1)
			{
				const char *buf;
				int n = mp3towav->get(&buf);
				if (n == 0)
					break;
				try {
					copy(buf, outStream, n);
				}
				catch (const char *msg)
				{
					while (mp3towav->get(&buf) > 0);
					throw msg;
				}
			}
		}
		else
			copy(inStream, outStream, length);
	}
	catch (const char *msg) {
		fclose(outStream);
		throw msg;
	}
	fclose(outStream);
}

void Session::readInfo()
{
	readInfo(dir, filesNum, tracksNum, sizeInKBytes);
}

void Session::readInfo(File *dir,
	int &numOfFiles, int &numOfTracks, int &size_KB)
{
	File file(dir, CDWserverInfo);
	FILE *inStream = fopen(file.getName(), "r");
	if (inStream == NULL)
		throw "internal error: Cannot read sessionInfo.";

	char lineStr[1024];
	while (fgets(lineStr, sizeof lineStr, inStream) != NULL)
	{
		char str1[1024], str2[1024];
		Config::parseLine(lineStr, str1, str2);

		if (strlen(str1) == 0 && strlen(str2) == 0)
			continue;
		else
		if (strcasecmp(str1, "files") == 0)
			sscanf(str2, "%d", &numOfFiles);
		else
		if (strcasecmp(str1, "sizeInKBytes") == 0)
			sscanf(str2, "%d", &size_KB);
		else
		if (strcasecmp(str1, "tracks") == 0)
			sscanf(str2, "%d", &numOfTracks);
	}
	fclose(inStream);

	log.put("read previous session: "
		"files=%d, tracks=%d, size_KB=%d",
		numOfFiles, numOfTracks, size_KB);
}

int Session::sizeToKBytes(int size)
{
	if (size <= 0)
		return 0;
	else
		return (size - 1) / 1024 + 1;
}

void Session::writeInfo()
{
	File file(dir, CDWserverInfo);
	FILE *outStream = fopen(file.getName(), "w");
	if (outStream != NULL)
	{
		fprintf(outStream, "sizeInKBytes=%d\n", sizeInKBytes);
		fprintf(outStream, "files=%d\n", filesNum);
		fprintf(outStream, "tracks=%d\n", tracksNum);
		fclose(outStream);
	}
}
