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

#include <sys/types.h>
#include <sys/time.h>
#include <unistd.h>
#include "Config.h"
#include "HTTPRequest.h"
#include "HTTPResponse.h"
#include "HTTPServer.h"
#include "LineReader.h"
#include "PrintWriter.h"
#include "Server.h"
#include "ServerSocket.h"
#include "Session.h"
#include "Socket.h"
#include "Status.h"
#include "Store.h"
#include "String.h"
#include "Thread.h"
#include "Version.h"

template<class Item>
class RingBuffer {
	Item *items;
	int size, n, head;
	Item init;

public:
	RingBuffer(int size, Item init) {
		items = new Item[size];
		this->size = size;
		n = head = 0;
		this->init = init;
	}

	Item add(Item item) {
		Item old = items[(head + n) % size];
		items[(head + n) % size] = item;
		if (n < size) {
			n++;
			old = init;
		}
		else
			head++;
		return old;
	}

	Item get(int i) {
		if (i >= n)
			throw new Exception("i >= n");
		return items[(head + i) % size];
	}

	int length() const {
		return n;
	}
};

RingBuffer<String *> lastSessionResults(100, NULL);


class StatusServer: public Thread {
	void run() {
		log.debug("StatusServer", "started");

		ServerSocket serverSocket(config.getStatusPortNo());

		for (int i = 1; ; i++)
		try {
			Socket *socket = serverSocket.accept();
			log.debug("StatusServer",
				"Status-Request %d accepted", i);

			HTTPServer server(*socket);
			HTTPRequest request = server.nextRequest();
			String s = "http://";
			HTTPResponse response = HTTPResponse::movedPermanently(
				s + request.host + ":" + config.getPortNo()
				+ "/status.html");
			PrintWriter writer(socket->getOutputStream());
			response.write(writer);

			delete socket;
			log.debug("StatusServer",
				"Status-Request %d terminated", i);
		}
		catch (Exception *e) {
			log.debug("StatusServer", "Exception: %s",
				e->getMessage().getBytes());
			delete e;
		}
	}
};


Status::Status() {
	log.putv("Status::Status()");
}

int Status::infoNo = 0;
int Status::sessionsNum0 = 0;
int Status::sessionsNumNew = 0;
int Status::sessionsTotal = 0;
int Status::MBwritten0 = 0;
int Status::MBwrittenNew = 0;

String tdbgcolor = "\"<i18n>colorTableCellBackground</i18n>\"";
String td = "<td bgcolor=\"<i18n>colorTableCellBackground</i18n>\">";
String TD = "</td>";
String tr = "<tr>";
String TR = "</tr>";

void Status::addResult(String &date, String &time,
		String &user, int sizeKB, String &result,
//		String &from, String &to, String &label, String &contents) {
		Options::ProjectInfo &projectInfo) {
	if (result.indexOf("OK") >= 0) {
		sessionsNumNew++;
		MBwrittenNew += sizeKB / 1024;
	}

	String str = "";

	str = str
		+ td + ++sessionsTotal + TD
		+ td + date + TD
		+ td + time + TD
		+ "<td align=\"center\" bgcolor=" + tdbgcolor + ">";

	if (config.getShowUsers())
		str = str + user;
	else
		str = str + "<i18n>anonymous</i18n>";

	str = str
		+ TD
		+ "<td align=\"right\" bgcolor=" + tdbgcolor
			+ " nowrap=\"nowrap\">";
	if (sizeKB > 1024)
		str = str + (sizeKB / 1024) + " MB";
	else
		str = str + sizeKB + " KB";
	str = str
		+ TD
		+ td + result + TD;

	if (config.getActiveBoolean("askForProjectInfo"))
		str = str
			+ td + projectInfo.from + TD
			+ td + projectInfo.to + TD
			+ td + projectInfo.label + TD
			+ td + projectInfo.contents + TD;

	String *old = lastSessionResults.add(new String(str));
	if (old != NULL)
		str = old;
}

static int scanToken(const char *str, int &pos, int &length) {
	while (str[pos] == ' '
		|| str[pos] == '\t')
		pos++;

	int i = pos;
	while (str[i] != 0
		&& str[i] != ' '
		&& str[i] != '\t'
		&& str[i] != '\n')
		i++;

	return length = i-pos;
}

static String *td2(int colspan) {
	String td = "<td bgcolor=\"<i18n>colorTableCellBackground</i18n>\"";
	td = td + " colspan=\"" + colspan + "\">";
	return new String(td);
}

void Status::readLastResults() {
	File sessions = new File(config.getLogDir(), "sessions");
	FILE *file = fopen(sessions.getPath().getBytes(), "r");
	if (file == NULL)
		return;

	while (1) {
		char lineStr[4 * 1024];
		if (fgets(lineStr, sizeof lineStr, file) == NULL)
			break;

		String line = lineStr;
		String date, time, user, result;
		int sizeKB;

		int pos = 0, len;
		scanToken(lineStr, pos, len);
		date = line.substring(pos, pos + len);
		pos += len;

		scanToken(lineStr, pos, len);
		time = line.substring(pos, pos + len);
		pos += len;

		scanToken(lineStr, pos, len);
		user = line.substring(pos, pos + len);
		pos += len;

		scanToken(lineStr, pos, len); pos += len;
		scanToken(lineStr, pos, len); pos += len;

		scanToken(lineStr, pos, len);
		long long int size;
		if (sscanf(&lineStr[pos], "%lld", &size) != 1)
			continue;
		sizeKB = size / 1024;
		pos += len;

		scanToken(lineStr, pos, len); pos += len;

		scanToken(lineStr, pos, len);
		result = line.substring(pos, pos + len);
		result = result.replace('_', ' ');
		pos += len;

		Options::ProjectInfo projectInfo;
		scanToken(lineStr, pos, len);
		projectInfo.from = line.substring(pos, pos + len);
		projectInfo.from = projectInfo.from.replace('_', ' ');
		pos += len;

		scanToken(lineStr, pos, len);
		projectInfo.to = line.substring(pos, pos + len);
		projectInfo.to = projectInfo.to.replace('_', ' ');
		pos += len;

		scanToken(lineStr, pos, len);
		projectInfo.label = line.substring(pos, pos + len);
		projectInfo.label = projectInfo.label.replace('_', ' ');
		pos += len;

		scanToken(lineStr, pos, len);
		projectInfo.contents = line.substring(pos, pos + len);
		projectInfo.contents = projectInfo.contents.replace('_', ' ');
		pos += len;

		addResult(date, time, user, sizeKB, result, projectInfo);
	}

	fclose(file);

	sessionsNum0 = sessionsNumNew;
	sessionsNumNew = 0;

	MBwritten0 = MBwrittenNew;
	MBwrittenNew = 0;
}

void Status::start() {
	readLastResults();

	if (config.getStatusPortNo() > 0)
		(new StatusServer())->start();
}

String *Status::getAbout() {
	String str = "";

	str = str
		+ "<!DOCTYPE HTML PUBLIC"
			+ " \"-//W3C//DTD HTML 4.01 Transitional//EN\"\n"
		+ "        \"http://www.w3.org/TR/html4/loose.dtd\">\n"
		+ "<html>\n"
		+ "<head>\n"
		+ "<title>" + Version.product
			+ " - <i18n>About</i18n></title>\n"
		+ "</head>\n"
		+ "\n"
		+ "<body>\n";

	str = str
		+ "<table width=\"100%\">\n"
		+ "<tr><td height=\"100\" valign=\"middle\">\n"
		+ "<h2 align=\"center\">" + Version.product + Version.productPro
			+ " " + Version.version + "</h2>\n"
		+ "<h3 align=\"center\">" + Version.copyright
			+ " <a href=\"" + Version.authorHome + "\">"
			+ Version.author + "</a></h3>\n";

	// License box
	str = str
		+ "</td></tr><tr><td>\n\n"

		+ "<table bgcolor=\"<i18n>colorTableGrid</i18n>\" border=\"0\""
			+ " cellpadding=\"0\" cellspacing=\"0\" width=\"100%\">"
		+ "<tr><td>\n"
		+ "<table border=\"0\" cellpadding=\"5\" cellspacing=\"1\" width=\"100%\">\n"
		+ "<tr bgcolor=\"<i18n>colorTableHeaderBackground</i18n>\">\n"
		+ "<th align=\"left\" width=\"20%\">"
			+ "<i18n>License</i18n>" + "</th>"
			+ td2(5) + Server::getLicense() + TD + TR + "\n"
		+ tr + td + "<i18n>S/N</i18n>:" + TD
				+ td + Server::getSerialNo() + TD
			+ td + "<i18n>Servers</i18n>:" + TD
				+ td + Server::getMaxServers() + TD
			+ td + "<i18n>Users at the same time</i18n>:" + TD
				+ td + Server::getMaxUsers() + TD
			+ TR + "\n"
		+ tr + td + "<i18n>Features</i18n>:" + TD
			+ td2(5) + Server::getFeatures() + TD + TR + "\n"
		+ tr + td + "<i18n>Validity</i18n>:" + TD
			+ td2(5) + Server::getValidity() + TD + TR + "\n"
		+ "</table>\n"
		+ "</td></tr></table>\n\n";

	str = str
		+ "</td></tr>\n"
		+ "<tr><td align=\"center\">\n\n"
		+ "<table width=\"100%\"><tr><td valign=\"top\">\n\n"

		+ "<table bgcolor=\"<i18n>colorTableGrid</i18n>\" border=\"0\""
			+ " cellpadding=\"0\" cellspacing=\"0\">"
		+ "<tr><td>\n"
		+ "<table border=\"0\" cellpadding=\"5\" cellspacing=\"1\">\n"
		+ "<tr bgcolor=\"<i18n>colorTableHeaderBackground</i18n>\">\n"
		+ "<th align=\"left\">"
		+ "<i18n>Translation</i18n>" + "</th></tr>\n"
		+ "<tr>\n"
		+ td + "<i18n>Translator(s)</i18n>" + TD + "\n"
		+ "</tr>\n"
		+ "</table>\n"
		+ "</td></tr></table>\n\n"
		+ "</td><td valign=\"top\">\n\n"

		+ "<table bgcolor=\"<i18n>colorTableGrid</i18n>\" border=\"0\""
			+ " cellpadding=\"0\" cellspacing=\"0\" width=\"100%\">"
		+ "<tr><td>\n"
		+ "<table border=\"0\" cellpadding=\"5\" cellspacing=\"1\""
			+ " width=\"100%\">\n"
		+ "<tr bgcolor=\"<i18n>colorTableHeaderBackground</i18n>\">\n"
		+ "<th align=\"left\">"
		+ "<i18n>The local</i18n> " + Version.product
			+ " <i18n>uses</i18n>\n"
		+ "</th></tr>\n";

	if (config.cdrdaoInfo.length() > 0)
		str = str
			+ "<tr>" + td + "<a href=\"" + Version.cdrdaoHome
			+ "\">cdrdao</a> " + config.cdrdaoInfo
			+ TD
			+ "</tr>\n";

	String cdrecordHome = Version.cdrecordHome;
	if (config.cdrecordInfo.indexOf("dummy") >= 0)
		cdrecordHome = Version.projectHome;
	str = str
		+ "<tr>" + td + "<a href=\"" + cdrecordHome + "\">"
		+ config.cdrecordInfo + "</a>"
		+ TD
		+ "</tr>\n";

	if (config.growisofsInfo.length() > 0)
		str = str
			+ "<tr>" + td + "<a href=\"" + Version.growisofsHome
			+ "\">dvd+rw-tools</a> " + config.growisofsInfo
			+ TD
			+ "</tr>\n";

	if (config.mpg123Info.length() > 0)
		str = str
			+ "<tr>" + td + "<a href=\"" + mpg123Home
			+ "\">mpg123</a> " + config.mpg123Info
			+ TD
			+ "</tr>\n";

	if (config.ogg123Info.length() > 0)
		str = str
			+ "<tr>" + td + "<a href=\"" + ogg123Home
			+ "\">ogg123</a> " + config.ogg123Info
			+ TD
			+ "</tr>\n";

	if (config.soxInfo.length() > 0)
		str = str
			+ "<tr>" + td + "<a href=\"" + Version.soxHome
			+ "\">sox</a> " + config.soxInfo
			+ TD
			+ "</tr>\n";

	str = str
		+ "<tr>" + td + "<a href=\"" + mkisofsHome + "\">"
		+ config.mkisofsInfo + "</a>" + TD
		+ "</tr>\n"
		+ "</table>\n"
		+ "</td></tr></table>\n\n"

		+ "</td></tr></table>\n"
		+ "</td></tr>\n"
		+ "</table>\n";

	str = str
		+ "</body>\n"
		+ "</html>\n";

	return new String(str);
}

String *Status::getConnects() {
	String sessionsNum = "";
	if (store == NULL)
		sessionsNum = "?";
	else
		sessionsNum = sessionsNum + store->getSessionsNum();

	String str = "";
	str = str
		+ "<tr>\n"
		+ td + "<i18n>Connections</i18n>" + TD
		+ td + Server::getActiveServersNum() + TD
		+ td + config.getMaxOpenConnections() + TD
		+ "</tr><tr>\n"
		+ td + "<i18n>Projects</i18n>" + TD
		+ td + sessionsNum + TD
		+ td + config.getMaxOpenSessions() + TD
		+ "</tr>\n";
	return new String(str);
}

String *Status::getSessions() {
	String str = "";

	str = str
		+ "<tr bgcolor=\"<i18n>colorTableHeaderBackground</i18n>\">\n"
		+ "<th><i18n>No</i18n></th>"
		+ "<th><i18n>Date</i18n></th>"
		+ "<th><i18n>Time</i18n></th>"
		+ "<th><i18n>User</i18n></th>"
		+ "<th><i18n>Size</i18n></th>"
		+ "<th><i18n>Result</i18n></th>";

	if (config.getActiveBoolean("askForProjectInfo"))
		str = str
			+ "<th><i18n>Initiator</i18n></th>"
			+ "<th><i18n>Addressee</i18n></th>"
			+ "<th><i18n>Project</i18n></th>"
			+ "<th><i18n>Contents</i18n></th>";

	str = str
		+ "\n</tr>\n";

	int n = config.getActiveInt("historyLength");
	int last = lastSessionResults.length() - 1;
	for (int i = 0; i < n && i <= last; i++)
		try {
			String row = *lastSessionResults.get(last - i);
			str = str
				+ "<tr>" + row + "</tr>\n";
		} catch (Exception *e) {
			delete e;
		}

	return new String(str);
}

String *Status::getStatistics() {
	String str = "";

	str = str
		+ "<tr>"
		+ td + "<i18n>Sessions</i18n>" + TD
		+ td + sessionsNumNew + TD
		+ td + (sessionsNum0 + sessionsNumNew) + TD
		+ "</tr>\n"
		+ "<tr>"
		+ td + "<i18n>GB written</i18n>" + TD
		+ td + (MBwrittenNew / 1024) + TD
		+ td + ((MBwritten0 + MBwrittenNew) / 1024) + TD
		+ "</tr>\n";

	return new String(str);
}

String *Status::getStatus() {
	String str = "";
	if (config.getFirstError().length() > 0) {
		str = str
			+ "<h2>Server Configuration Error</h2>\n"
			+ "<h3>" + config.getFirstError() + "</h3>\n";
		char str2[16 * 1024];
		strcpy(str2, config.getFirstErrorHelp().getBytes());
		replace(str2, "[", "\n<table bgcolor=#E0E0E0 "
			"cellpadding=10 width=\"100%\"><tr><td>\n");
		replace(str2, "]", "\n</tr></td></table>\n");
		str = str
			+ str2 + "\n"
			+ "<h3>Reload this page to try again!</h3>\n";
		return new String(str);
	}

	if (store == NULL) {
		str = str
			+ "<h1>Problem: store == NULL</h1>\n"
			+ "<h2>Make sure you have "
			+ "glibc2/libc6 installed!</h2>\n";
		return new String(str);
	}

	return new String(str);
}

String *Status::getStore() {
	String maxSize, size, total;
	if (store == NULL) {
		maxSize = "?";
		size = "?";
		total = "?";
	}
	else {
		maxSize = maxSize + store->getMaxSizeInMB();
		size = size + store->getSizeInMB();
		total = total + store->getSessionsTotal();
	}

	String str = "";
	str = str
		+ "<tr>"
		+ td + total + " <i18n>projects</i18n>" + TD
		+ td + config.getMaxMBytesPerSession()
			+ " <i18n>MB</i18n> <i18n>per project</i18n>" + TD
		+ "</tr><tr>"
		+ td + size + " <i18n>MB</i18n>" + TD
		+ td + maxSize + " <i18n>MB</i18n>" + TD
		+ "</tr>\n";
	return new String(str);
}

String *Status::getWriters() {
	String str = "";

	for (int i = 0; i < config.getNumOfWriters(); i++) {
		str = str
			+ "<tr>"
			+ td + (i + 1) + TD
			+ td + config.getWriterInfo(i) + TD
			+ td + config.getWriterSupports(i) + TD
			+ td;

		int addColumn = 1, showPercent = 1;
		switch (Server::getWriterStatus(i)) {
			case idle:
				str = str + "<i18n>free</i18n>";
				showPercent = 0;
				break;
			case reserved: {
				str = str + "<i18n>reserved</i18n>";
				if (config.getShowUsers()) {
					Server *s = Server::getWriterOwner(i);
					str = str
						+ " <i18n>by</i18n> " + s->getClientUserName()
						+ "@" + s->getClientIP();
				}
				struct timeval t;
				gettimeofday(&t, NULL);
				t.tv_sec = min(t.tv_sec,
					Server::getWriterReservedUntil(i));
				char str2[1024];
				str = str
					+ TD + td
					+ secondsToString(
						Server::getWriterReservedUntil(i)
							- t.tv_sec, str2);
				addColumn = 0;
				showPercent = 0;
				break;
			}
			case reading:
				str = str + "<i18n>reading</i18n>";
				break;
			case blanking:
				str = str + "<i18n>blanking CD-RW</i18n> (cdrecord)";
				break;
			case mkisofs:
				str = str + "<i18n>creating image</i18n> (mkisofs)";
				break;
			case writing:
				str = str + "<i18n>writing</i18n> (cdrecord)";
				break;
			case fixating:
				str = str + "<i18n>fixating</i18n> (cdrecord)";
				break;
			case verifying:
				str = str + "<i18n>verifying</i18n>";
				break;
		}

		if (addColumn)
			str = str
				+ TD + td + "&nbsp;";

		if (showPercent)
			str = str
				+ Server::getWriterPercent(i) + "%";

		str = str + TD + "</tr>";
	}

	return new String(str);
}

Status status;
