#ifndef CONFIG_H
#define CONFIG_H

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

#include <stdio.h>
#include <string.h>
#include "Boolean.h"
#include "Error.h"
#include "File.h"
#include "Integer.h"
#include "Process.h"
#include "String.h"
#include "Support.h"
#include "Types.h"
#include "Log.h"

#include "config.h"

const int maxNumOfReaders = 10, maxNumOfWriters = 10;
const int PATH_COPY = 1, PATH_DUMMY = 2, PATH_ROOT = 4;

/**
 * This class delivers the values of the configuration file.
 *
 * @version 20030429
 * @author Jörg P. M. Haeger
 */
class Config {
	public: String
		cdrdaoInfo,
		cdrecordInfo,
		mkisofsInfo,
		mpg123Info,
		ogg123Info,
		soxInfo;

	private: const char *configDir;
	const char *fileName;
	Exception *firstError;
	int numOfReaders, numOfWriters;
	String text;
	boolean HTML;
	const char *readerInfos[maxNumOfReaders];
	const char *writerInfos[maxNumOfWriters];

	// values
	boolean accounts;
	const char *accountsFile;
	boolean anonymousUse;
	boolean burnProof;
	const char *cdrdaoArgs;
	const char *cdrecordArgs;
	const char *CDROMs[maxNumOfReaders];
	boolean copySupport;
	const char *debug;
	const char *defaultFilesDir;
	int defaultSpeed;
	int DVDplusRWSupport[maxNumOfWriters];
	boolean ejectOnDone;
	boolean ejectOnReservation;
	const char *eMailOnError;
	boolean exportArchive;
	gid_t gid;
	const char *group;
	const char *homeDir;
	const char *imageFile[maxNumOfWriters];
	boolean imageOnTheFly;
	const char *infoFile;
	boolean laserOn;
	const char *logDir;
	const char *logoURL;
	int logLevel;
	int maxCopies;
	int maxMBytesInSpoolDir;
	int maxMBytesPerSession;
	int maxOpenConnections;
	int maxOpenSessions;
	int maxSpeed;
	const char *mkisofsArgs;
	boolean MP3decoding;
	const char *organization;
	unsigned short int portNo;
	const char *readerRawDevices[maxNumOfReaders];
	boolean removeSessions;
	int reservedMBytes;
	boolean showUsers;
	const char *spoolDir;
	unsigned short int statusPortNo;
	uid_t uid;
	const char *user;
	boolean verify;
	int waitForCD;
	const char *waitForCDMsgFile;
	const char *writers[maxNumOfWriters];
	const char *writerRawDevices[maxNumOfWriters];

	struct Item {
		String key;
		Object &default0, &config, &active, &example;
		boolean isDefault;
		int lineInConfig;

		public: Item(const char *key, boolean value):
			key(key),
			default0(*new Boolean(value)),
			config(*new Boolean(value)),
			active(*new Boolean(value)),
			example(*new Boolean(value)) {
			isDefault = true;
			lineInConfig = -1;
		}

		public: Item(const char *key, int value):
			key(key),
			default0(*new Integer(value)),
			config(*new Integer(value)),
			active(*new Integer(value)),
			example(*new Integer(value)) {
			isDefault = true;
			lineInConfig = -1;
		}

		public: Item(const char *key, const char *value,
			const char *example = ""):
			key(key),
			default0(*new String(value)),
			config(*new String(value)),
			active(*new String(value)),
			example(*new String(example)) {
			isDefault = true;
			lineInConfig = -1;
		}

		private: void setConfig(boolean value) {
			*(Boolean *)&config = new Boolean(value);
			*(Boolean *)&active = new Boolean(value);
		}

		private: void setConfig(int value) {
			*(Integer *)&config = new Integer(value);
			*(Integer *)&active = new Integer(value);
		}

		private: void setConfig2(const char *value) {
			*(String *)&config = new String(value);
			*(String *)&active = new String(value);
		}

		public: void setConfig(const char *value) {
			if (active.instanceof("Boolean"))
				setConfig(Boolean::getBoolean(value));
			else
			if (active.instanceof("Integer"))
				setConfig(Integer::getInt(value));
			else
				setConfig2(value);

			isDefault = false;
		}
	};
	Item *items[100];
	int numOfItems;

	public: Config();

	private: int add(Item *item) {
		const char *key = item->key.getBytes();
		int index = search(key);
		if (index >= 0)
			throw new Exception(S.e + key + " already exists");

		for (index = 0; index < numOfItems; index++)
			if (items[index]->key.compareToIgnoreCase(key) > 0)
				break;

		for (int i = numOfItems; i > index; i--)
			items[i] = items[i - 1];

		numOfItems++;
		items[index] = item;
		items[index]->key = key;
		items[index]->isDefault = true;
		return index;
	}

	private: void add(const char *key, boolean value) {
		add(new Item(key, value));
	}

	private: void add(const char *key, const char *value) {
		add(new Item(key, value));
	}

	private: void add(const char *key, int value) {
		add(new Item(key, value));
	}

	private: void cdrecordInq(int writerNo);

	private: void cdrecordHelp() {
		Process p(getCdrecordPath());
		p.addArg("-help");
		p.start();

		while (1) {
			const char *lineStr = p.readLine();
			if (lineStr == NULL)
				break;

			if (strstr(lineStr, "gracetime=#") != NULL)
				set("gracetime", 2);
		}
	}

	private: void cdrecordScanbus();

	private: void cdrecordVersion() {
		Process p(getCdrecordPath());
		p.addArg("-version");
		p.start();
		const char *lineStr = p.readLine();
		if (lineStr == NULL)
			throw new Exception("can't run cdrecord");
		if (p.getResult() != 0)
			throw new Exception("error running cdrecord -version");
		char str1[strlen(lineStr) + 1], str2[strlen(lineStr) + 1];
		if (sscanf(lineStr, "%s %s", str1, str2) != 2)
			throw new Exception("can't get version of cdrecord");
		char *str3 = new char[strlen(str1) + strlen(str2) + 2];
		sprintf(str3, "%s %s", str1, str2);
		cdrecordInfo = str3;
		log.put(lineStr);
	}

	private: void checkExecAsRoot(const char *fileName);

	private: void checkRawDevices() {
		for (int i = 0; i < numOfWriters; i++) {
			if (getActiveBoolean("simulation"))
				continue;

			if (!File(writerRawDevices[i]).exists())
				throw (new Exception(S.e
					+ "The raw device "
					+ writerRawDevices[i] + " does not exist!"));

			if (access(writerRawDevices[i], R_OK | W_OK) == -1) {
				throw (new Exception(S.e
					+ "Cannot read and write the raw device "
					+ writerRawDevices[i] + "!"))
				->addHelp(S.e + "Try\n"
					+ createHTMLTable(S.e
						+ "$ chown " + user + " " + writerRawDevices[i]
						+ "\n$ chmod u+rw" + " " + writerRawDevices[i]));
			}
		}
	}

	public: static String *createHTMLTable(String &str) {
		String *tab = new String(
			"<table bgcolor=#E0E0E0 cellpadding=10 width=100%>"
			"<tr><td><pre>");
		tab = &(*tab + str + "</pre></td></tr></table>");
		String::free(&str);
		return tab;
	}

	private: void evaluate(
			int lineNo, const char *str1, const char *str2);

	public: boolean getAccounts() {
		return accounts;
	}

	const char *getAccountsFile() {
		return accountsFile;
	}

	boolean getActiveBoolean(const char *key) {
		int index = indexOf(key);
		Object &object = items[index]->active;
		if (!object.instanceof("Boolean"))
			throw new Exception("invalid item in getActiveBoolean");
		Boolean *b = (Boolean *)&object;
		return b->booleanValue();
	}

	int getActiveInt(const char *key) {
		int index = indexOf(key);
		Object &object = items[index]->active;
		if (!object.instanceof("Integer"))
			throw new Exception("invalid item in getActiveInt");
		Integer *i = (Integer *)&object;
		return i->intValue();
	}

	String &getActiveValue(const char *key) {
		int index = indexOf(key);
		Object &object = items[index]->active;
		if (!object.instanceof("String"))
			throw new Exception("invalid item in getActiveValue");
		String *s = (String *)&object;
		return *s;
	}

	boolean getAnonymousUse() {
		return anonymousUse;
	}

	boolean getBurnProof() {
		return burnProof;
	}

	boolean getCdrdao() {
		return getActiveBoolean("cdrdao");
	}

	const char *getCdrdaoArgs() {
		return cdrdaoArgs;
	}

	const char *getCdrdaoPath() {
		return getActiveValue("cdrdaoPath").getBytes();
	}

	const char *getCdrecordArgs() {
		return cdrecordArgs;
	}

	const char *getCdrecordPath() {
		return getActiveValue("cdrecordPath").getBytes();
	}

	const char *getConfigDir() {
		return configDir;
	}

	String &getConfigValue(const char *key) {
		int index = indexOf(key);
		Object &object = items[index]->config;
		if (!object.instanceof("String"))
			throw new Exception("invalid item in getConfigValue");
		String *s = (String *)&object;
		return *s;
	}

	boolean getCopySupport() {
		return copySupport && numOfReaders > 0;
	}

	const char *getDebug() {
		return debug;
	}

	const char *getDefaultFilesDir() {
		return defaultFilesDir;
	}

	int getDefaultSpeed() {
		return defaultSpeed;
	}

	String &getDefaultValue(const char *key) {
		int index = indexOf(key);
		Object &object = items[index]->default0;
		if (!object.instanceof("String"))
			throw new Exception("invalid item in getDefaultValue");
		String *s = (String *)&object;
		return *s;
	}

	int getDVDplusRWSupport(int writerNo) {
		return DVDplusRWSupport[writerNo];
	}

	boolean getEjectOnDone() {
		return ejectOnDone;
	}

	boolean getEjectOnReservation() {
		return ejectOnReservation;
	}

	const char *getEMailOnError() {
		return eMailOnError;
	}

	boolean getExportArchive() {
		return exportArchive;
	}

	const char *getFileName() {
		return fileName;
	}

	Exception *getFirstError() {
		return firstError;
	}

	uid_t getGid() {
		return gid;
	}

	const char *getGroup() {
		return group;
	}

	const char *getHomeDir() {
		return homeDir;
	}

	const char *getImageFile(int writerNo) {
		return imageFile[writerNo];
	}

	boolean getImageOnTheFly() {
		return imageOnTheFly;
	}

	const char *getInfoFile() {
		return infoFile;
	}

	int getLaserOn() {
		return laserOn;
	}

	const char *getLogDir() {
		return logDir;
	}

	const char *getLogoURL() {
		return logoURL;
	}

	int getLogLevel() {
		return logLevel;
	}

	int getMaxCopies() {
		return maxCopies;
	}

	int getMaxMBytesInSpoolDir() {
		return maxMBytesInSpoolDir;
	}

	int getMaxMBytesPerSession() {
		return maxMBytesPerSession;
	}

	int getMaxOpenConnections() {
		return maxOpenConnections;
	}

	int getMaxOpenSessions() {
		return maxOpenSessions;
	}

	int getMaxSpeed() {
		return maxSpeed;
	}

	const char *getMkisofsArgs() {
		return mkisofsArgs;
	}

	const char *getMkisofsPath() {
		return getActiveValue("mkisofsPath").getBytes();
	}

	boolean getMP3decoding() {
		return MP3decoding;
	}

	private: String *getNameOfCopy(File &binary) {
		File binDir = new File(homeDir, "bin");
		File copy = new File(binDir, binary.getName());
		return new String(copy.getPath());
	}

	public: int getNumOfReaders() {
		return numOfReaders;
	}

	int getNumOfWriters() {
		return numOfWriters;
	}

	const char *getOrganization() {
		return organization;
	}

	unsigned short int getPortNo() {
		return getActiveInt("portNo");
	}

	private: const char *getRawDevice(
		int bus, int ID, int LUN, int no);

	public: const char *getReader(int no) {
		return CDROMs[no];
	}

	const char *getReaderInfo(int no) {
		return readerInfos[no];
	}

	const char *getReaderRawDevice(int no) {
		return readerRawDevices[no];
	}

	boolean getRemoveSessions() {
		return removeSessions;
	}

	int getReservedMBytes() {
		return reservedMBytes;
	}

	const char *getSetSchedulerPath() {
		return getActiveValue("setSchedulerPath").getBytes();
	}

	boolean getShowUsers() {
		return showUsers;
	}

	const char *getSpoolDir() {
		return spoolDir;
	}

	unsigned short int getStatusPortNo() {
		return statusPortNo;
	}

	uid_t getUid() {
		return uid;
	}

	const char *getUser() {
		return user;
	}

	boolean getVerify() {
		return verify;
	}

	const char *getVerifyPath() {
		return getActiveValue("verifyPath").getBytes();
	}

	int getWaitForCD() {
		return waitForCD;
	}

	const char *getWaitForCDMsgFile() {
		return waitForCDMsgFile;
	}

	const char *getWriter(int no) {
		return writers[no];
	}

	const char *getWriterInfo(int writerNo) {
		return writerInfos[writerNo];
	}

	const char *getWriterRawDevice(int writerNo) {
		return writerRawDevices[writerNo];
	}

	private: int indexOf(const char *key) {
		int index = search(key);
		if (index < 0)
			throw new Exception(S.e + key + " does not exist");
		return index;
	}

	public: int isDefault(const char *key) {
		int index = search(key);
		if (index < 0)
			throw new Exception(S.e + key + " does not exist");
		return items[index]->isDefault;
	}

	private: void parseCDROMs(
		const char *str, const char **list, int &count);

	public: static void parseLine(
			const char *lineStr, char *str1, char *str2);

	public: void prepareBinaries() {
		prepareBinary("cdrdaoPath", getActiveBoolean("simulation"));
		prepareBinary("cdrecordPath", getActiveBoolean("simulation"));
		prepareBinary("mkisofsPath");
	}

	private: void prepareBinary(const char *command,
			boolean dummy = false) {
		String path = getActiveValue(command);
		if (dummy)
			path = path + "-dummy";
		File orig = new File(path);
		orig.complete();
		if (!orig.exists() || orig.isDirectory())
			return;

		String name = orig.getName();
		if (name.length() == 0)
			return;

		File copy = new File(getNameOfCopy(orig));
		copy.remove();
		copy.mkdirsParent();
		String cmd;
		cmd = cmd + "cp -p " + orig.getPath() + " " + copy.getPath();
		log.put(4, S.e + cmd.getBytes());
		system(cmd.getBytes());
		String cmd2;
		cmd2 = cmd2 + "chown root." + group + " " + copy.getPath();
		system(cmd2.getBytes());
		String cmd3;
		cmd3 = cmd3 + "chmod 4710" + " " + copy.getPath();
		system(cmd3.getBytes());
		File binDir = new File(homeDir, "bin");
		String cmd4;
		cmd4 = cmd4 + "chmod 755" + " " + binDir.getPath();
		system(cmd4.getBytes());

		log.put(2, S.e + "prepared " + command + " = " + copy.getPath());
	}

	private: void preparePath(const char *command, int mode = 0) {
		String str = getConfigValue(command);
		if ((mode & PATH_DUMMY) > 0 && getActiveBoolean("simulation"))
			str = str + "-dummy";
		str = File(str).complete();
		if ((mode & PATH_COPY) > 0) {
			File binary = new File(str);
			String nameOfCopy = getNameOfCopy(binary);
			if (File(nameOfCopy).exists())
				str = nameOfCopy;
		}
		if ((mode & (PATH_COPY | PATH_ROOT)) > 0)
			checkExecAsRoot(str.getBytes());
		set(command, str.getBytes());
		log.put(2, S.e + command + " = " + str);
	}

	private: void readConfigFile();

	public: void reload();

	private: void resetItems() {
		for (int i = 0; i < numOfItems; i++) {
			items[i]->config = items[i]->default0;
			items[i]->active = items[i]->config;
			items[i]->isDefault = true;
			items[i]->lineInConfig = -1;
		}
	}

	private: int search(const char *key) {
		return search(key, 0, numOfItems - 1);
	}

	private: int search(const char *key, int a, int b) {
		if (a > b)
			return -1;

		int m = (a + b) / 2;
		int diff = items[m]->key.compareToIgnoreCase(key);
		if (diff > 0)
			return search(key, a, m - 1);
		else
			if (diff < 0)
				return search(key, m + 1, b);
			else
				return m;
	}

	private: void set(const char *key, boolean value) {
		int index = indexOf(key);
		*(Boolean *)&items[index]->active = new Boolean(value);
		items[index]->isDefault = false;
	}

	private: void set(const char *key, int value) {
		int index = indexOf(key);
		*(Integer *)&items[index]->active = new Integer(value);
		items[index]->isDefault = false;
	}

	private: void set(const char *key, const char *value) {
		int index = indexOf(key);
		*(String *)&items[index]->active = new String(value);
		items[index]->isDefault = false;
	}

	public: void setPermissions();

	private: void testGrowisofs() {
		String advice;
		advice = advice + "Install the dvd+rw-tools 5.2 or above"
			+ " and/or adjust <b>growisofsPath</b> in " + fileName;

		Process growisofs(getActiveValue("growisofsPath"));
		growisofs.addArg("--version");
		growisofs.start();
		const char *lineStr = growisofs.readLine();
		if (lineStr == NULL)
			throw (new Exception(S.e + "Cannot run growisofs ("
				+ getActiveValue("growisofsPath") + ")"))
				->addHelp(S.e + advice);

		int a = 0, b = 0;
		if (sscanf(lineStr, "growisofs %d.%d", &a, &b) < 2)
			throw (new Exception(S.e + "growisofs --version failed"))
				->addHelp(S.e + advice);

		if (a < 5 || b < 2)
			throw (new Exception(S.e + "growisofs " + a + "." + b
				+ " is to old"))
				->addHelp(S.e + advice);
	}

	private: void testMkisofs();
	private: void testMpg123();

	private: void testOgg123() {
		Process ogg123(getActiveValue("ogg123Path"));
		ogg123.addArg("--version");
		ogg123.start();
		const char *lineStr = ogg123.readLine();
		if (lineStr == NULL)
			throw (new Exception(S.e + "Cannot run ogg123 ("
				+ getActiveValue("ogg123Path") + ")"))
				->addHelp(S.e + "Try one of\n<ul>\n"
					"<li>install the vorbis-tools"
						+ " and/or adjust <b>ogg123Path</b> in "
						+ fileName + "</li>\n"
					"<li>set <b>audioCDSupport=off</b> in "
						+ fileName + "</li></ul>");
		do {
			char str1[strlen(lineStr) + 1], str2[strlen(lineStr) + 1];
			if (sscanf(lineStr, "%s %[^:]", str1, str2) == 2)
				ogg123Info = str2;
			lineStr = ogg123.readLine();
		} while (lineStr != NULL);
	}

	private: void testSox() {
		Process sox(getActiveValue("soxPath"));
		sox.addArg("-h");
		sox.start();
		const char *lineStr = sox.readLine();
		if (lineStr == NULL)
			throw (new Exception(S.e + "Cannot run sox ("
				+ getActiveValue("soxPath") + ")"))
				->addHelp(S.e + "Try one of\n<ul>\n"
					"<li>install sox and/or adjust <b>soxPath</b> in "
						+ fileName + "</li>\n"
					"<li>set <b>audioCDSupport=off</b> in "
						+ fileName + "</li></ul>");
		do {
			char str1[strlen(lineStr) + 1], str2[strlen(lineStr) + 1];
			if (sscanf(lineStr, "%[^:]: Version %s", str1, str2) == 2)
				soxInfo = str2;
			lineStr = sox.readLine();
		} while (lineStr != NULL);
	}

	static gid_t toGid(const char *groupName);
	static const char *toGroup(gid_t gid);
	public: String &toHTML();
	private: static const char *toString(boolean b);
	static uid_t toUid(const char *userName);
	static const char *toUser(uid_t uid);
	void write();
	void write(const char *str);
	void write(const char *str1, const char *str2);
	void write(const char *str, int number);
	void writeConfigFile();
	void writeInfoFile();
	void writeWaitForCDMsgFile();
};

extern Config config;

#endif
