/*
 * @(#)Status.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.
 */

#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 "i >= n";
		return items[(head + i) % size];
	}
};

RingBuffer<char *> lastSessionResults(10, 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());
			PrintWriter writer(socket->getOutputStream());
			response.write(writer);

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


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

void Status::addResult(const char *date, const char *time,
	const char *user, int size, const char *result)
{
	if (strstr(result, "OK") != NULL)
	{
		sessionsNumNew++;
		MBwrittenNew += size / (1024 * 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 + ">"
			+ size + TD
		+ td + result + TD;

	char *str2 = lastSessionResults.add(strDup(str.getBytes()));
	if (str2 != NULL)
		delete[] str2;
}

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

void Status::readLastResults()
{
	char fileName[4*1024];
	sprintf(fileName, "%s/sessions", config.getLogDir());
	FILE *file = fopen(fileName, "r");
	if (file == NULL)
		return;

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

		char date[80], time[80], user[80];
		int size;

		int pos = 0, len;
		scanToken(lineStr, pos, len);
		strncpy(date, &lineStr[pos], len);
		date[len] = 0;
		pos += len;

		scanToken(lineStr, pos, len);
		strncpy(time, &lineStr[pos], len);
		time[len] = 0;
		pos += len;

		scanToken(lineStr, pos, len);
		strncpy(user, &lineStr[pos], len);
		user[len] = 0;
		pos += len;

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

		scanToken(lineStr, pos, len);
		if (sscanf(&lineStr[pos], "%d", &size) != 1)
			continue;
		pos += len;

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

		scanToken(lineStr, pos, len);
		addResult(date, time, user, size, &lineStr[pos]);
	}

	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=\"200\" valign=\"center\">\n"
		+ "<h1 align=\"center\">" + Version.product
			+ " " + Version.version + "</h1>\n"
		+ "<h2 align=\"center\">" + Version.copyright
			+ " <a href=\"" + Version.authorHome + "\">"
			+ Version.author + "</a></h2>\n"
		+ "</td></tr>\n"
		+ "<tr><td align=\"center\">\n\n"
		+ "<table><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\">"
		+ "<tr><td>\n"
		+ "<table border=\"0\" cellpadding=\"5\" cellspacing=\"1\">\n"
		+ "<tr bgcolor=\"<i18n>colorTableHeaderBackground</i18n>\">\n"
		+ "<th align=\"left\">"
		+ "<i18n>The local</i18n> " + Version.product
			+ " <i18n>uses</i18n>\n"
		+ "</th></tr>\n";

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

	String cdrecordHome = Version.cdrecordHome;
	String cdrecordInfo = config.getCdrecordInfo();
	if (cdrecordInfo.indexOf("Dummy") >= 0)
		cdrecordHome = Version.projectHome;

	str = str
		+ "<tr>" + td + "<a href=\"" + mkisofsHome + "\">"
		+ config.getMkisofsInfo() + "</a>" + TD
		+ "</tr>\n"
		+ "<tr>" + td + "<a href=\"" + cdrecordHome + "\">"
		+ cdrecordInfo + "</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::getCDROMs() {
	String str = "";

	if (config.getCopySupport()) {
		for (int i = 0; i < config.getNumOfReaders(); i++) {
			str = str
				+ "<tr>"
				+ td + (i + 1) + TD
				+ td + config.getReaderInfo(i) + TD
				+ td;

			switch (Server::getReaderStatus(i)) {
				case idle:
					str = str + "<i18n>free</i18n>";
					break;
				case reading:
					str = str + "<i18n>reading</i18n>";
					break;
				case reserved:
					str = str + "<i18n>reserved</i18n>";
					break;
			}

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

	return new String(str);
}

String *Status::getConnects() {
	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 + store->getSessionsNum() + 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>\n"
		+ "</tr>\n";

	for (int i = 9; i >= 0; i--)
		try {
			String row = lastSessionResults.get(i);
			str = str
				+ "<tr>" + row + "</tr>\n";
		} catch (const char *msg) {
		}

	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 = "";

	Error *firstError = config.getFirstError();
	if (firstError != NULL) {
		config.reload();
		firstError = config.getFirstError();
		if (firstError == NULL)
			Server::initStatic2();
	}

	if (firstError != NULL) {
		str = str
			+ "<h2>Server Configuration Error</h2>\n"
			+ "<h3>" + firstError->toString() + "</h3>\n";
		char str2[16 * 1024];
		strcpy(str2, firstError->getHelp());
		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 str = "";
	str = str
		+ "<tr>"
		+ td + store->getSessionsTotal()
			+ " <i18n>projects</i18n>" + TD
		+ td + config.getMaxMBytesPerSession()
			+ " <i18n>MB</i18n> <i18n>per project</i18n>" + TD
		+ "</tr><tr>"
		+ td + store->getSizeInMB() + " <i18n>MB</i18n>" + TD
		+ td + store->getMaxSizeInMB() + " <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;

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