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

#ifndef SERVER_H
#define SERVER_H

#include <time.h>
#include "CDstate.h"
#include "Options.h"
#include "Session.h"
#include "Socket.h"
#include "Thread.h"
#include "Writer.h"

/*
 * Implementation of a server process.
 *
 * @version 20040710
 * @author Jörg P. M. Haeger
 */
class Server {
	public: static const char
		errorInCommand[], hint[], hintPercentDone[], info[], unknown[];

	private: static pthread_mutex_t activateConfigMutex;
	private: static bool activateConfigPending;
	private: static int activeServersNum;
	protected: static String features, license, serialNo, validity;
	protected: static int maxServers, maxUsers;
	static pthread_mutex_t licenseMutex;
	static int nextServerNo;
	static class WriterQueues *writerQueue;
	static Writer *writers;

	const Thread *mainThreadID;
	int serverNo, readerNo;
	time_t connectionT0, sessionT0, sessionT1;
	Socket &socket;
	class InputStream &inStream;
	char errorStr[1024], lineStr[1024];
	int logFilesNum;

	// states
	unsigned int bytesReceived;
	int openCommands, isOffline, lineStrPending;
	String
		clientIP;
	const char
		*clientSystemName, *clientSystemVersion,
		*clientUserID, *clientUserName,
		*userID, *userPassword;
	bool ejectBeforeVerify;
	int copiesReady, copiesToDo;

	// modes
	int
		modeShowCdrecord, modeShowMkisofs;

	struct Options options;
	class Session *session;
	const char *tmpSessionID;
	FILE *project;

	int quit;

public:
	Server(Socket &socket);
	virtual ~Server();
	static void activateConfig();
	static int getActiveServersNum() { return activeServersNum; }
	const char *getClientIP() { return clientIP.getBytes(); }
	const char *getClientUserName() { return clientUserName; }
	int getNo() { return serverNo; }
	static int getWriterFifoPercent(int writerNo)
		{ return writers[writerNo].fifoPercent; }
	static Server *getWriterOwner(int writerNo)
		{ return writers[writerNo].owner; }
	static int getWriterPercent(int writerNo)
		{ return writers[writerNo].percent; }
	static int getWriterReservedUntil(int writerNo)
		{ return writers[writerNo].reservedUntil; }
	static enum writerStates getWriterStatus(int writerNo)
		{ return writers[writerNo].state; }
	static int hextoint(const char *str, int length);
	static void initStatic();
	void run();
	static void terminate();
	int waitForService(class Queue *queue, int toRank);
	int waitForService(class Queue *queue, int toRank, int ID);

private:
	void addArgs(Process &p, const char *args);
	protected: virtual String *addKey(String &preparerID) {
		return new String(preparerID);
	}
	void blank(char *cmdStr);
	void burnCopy();
	void burnSession();
	void burnSession2(int threadID);
	void burnSession3();
	void burnSession4();

	protected: struct CDstate &CDState() {
		return writer().CDState;
	}

	protected: virtual void checkDVDSize() {}
	void checkFileName(const char *fileName);
	void checkLoggedIn();
	protected: virtual void checkMediumSupport(const char *medium) {
		throw new Exception(S.e
			+ "This version of CDWserver has no " + medium + " support!");
	}
	void checkOpenSession();
	static String *dateToShortString();
	void eject() { try { eject2(); } catch (Exception *e) { delete e; } }
	private: void eject2();
	protected: void ejectReader();
	void errorToClient(const char *format, ...);
	void errorToClient(String &str);
	void executeCmd();
	bool filterCancel(bool cancel);

	public: static String *getFeatures() {
		pthread_mutex_lock(&licenseMutex);
		String *str = new String(features);
		pthread_mutex_unlock(&licenseMutex);
		return str;
	}

	public: static String *getLicense() {
		pthread_mutex_lock(&licenseMutex);
		String *str = new String(license);
		pthread_mutex_unlock(&licenseMutex);
		return str;
	}

	public: static int getMaxServers() { return maxServers; }
	public: static int getMaxUsers() { return maxUsers; }

	public: static String *getSerialNo() {
		pthread_mutex_lock(&licenseMutex);
		String *str = new String(serialNo);
		pthread_mutex_unlock(&licenseMutex);
		return str;
	}

	public: static String *getValidity() {
		pthread_mutex_lock(&licenseMutex);
		String *str = new String(validity);
		pthread_mutex_unlock(&licenseMutex);
		return str;
	}

	protected: String *getNewLogFileName();

	protected: bool hasWriter() {
		try {
			writerNo();
			return true;
		}
		catch (Exception *e) {
			delete e;
			return false;
		}
	}

	protected: bool isCommandCancelled();

	protected: bool isMainThread() {
		return Thread::currentThread() == mainThreadID;
	}

	protected: virtual bool isMediumOK();
	void load();

	void logConnection() {
		static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
		pthread_mutex_lock(&mutex);
		try {
			logConnection2();
		}
		catch (Exception *e) {
			pthread_mutex_unlock(&mutex);
			throw e;
		}
		pthread_mutex_unlock(&mutex);
	}

	void logConnection2();

	void logSession(const char *typeStr, const char *result) {
		String result2 = result;
		logSession(typeStr, result2);
	}

	void logSession(const char *typeStr, String &result) {
		static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
		pthread_mutex_lock(&mutex);
		try {
			logSession2(typeStr, result);
		}
		catch (Exception *e) {
			pthread_mutex_unlock(&mutex);
			throw e;
		}
		pthread_mutex_unlock(&mutex);
	}

	void logSession2(const char *typeStr, String &result);
	void nextCmd();

	int noDisc(int trayOpenIfNoDisc = 0) {
		try {
			return noDisc2(trayOpenIfNoDisc);
		}
		catch (Exception *e) {
			delete e;
		}
		return 1;
	}

	private: int noDisc2(int trayOpenIfNoDisc);

	protected: virtual void putFileContinue(
			InputStream &inStream, int length) {
		session->putFileContinue(inStream, length);
		successToClient("file continued");
	}

	protected: virtual void putFileEnd(int mode, int time) {
		session->putFileEnd(mode, time);
	}

	protected: virtual void putFileStart(
			const char *arg1, const char *arg2) {
		session->putFileStart(arg1);
	}

	void readATIP();
	void readMultiSessionInfo();
	protected: virtual void readTOC();
	char *removeEscChars(char *str);
	void reserveDevice(int ID);
	void resetImageFile();
	static void resetStatic();
	void run_cdrdaoCopy();
	void run_cdrecord();
	void run_cdrecord2();
	protected: virtual void run_cdrecordBlank(const char *modeStr);
	void run_cdrecordCopy();
	protected: virtual void run_growisofs() { }
	void run_mkisofs();
	void run_readcd();
	void saveLineStr();
	protected: virtual void showFeatures();
	void showWaitForDiscMsg();
	static const char *spaceToEsc(const char *str);
	protected: void start();
	void start_mkisofs(Process &p);
	protected: virtual void statusToClient() { }
	void successToClient(const char *format, ...);
	static String *timeToDigits(time_t t = 0);
	public: static String *timeToShortString();
	public: static String *timeToString(time_t t = 0);
	protected: void toClient(int code, String &text, int logLevel = 10);
	void toClient(const char *type, const char *format, ...);
	void toClient(const char *type, String &str);
	void verify();
	int waitForDisc();
	void writeImageToRawDevice();

	int writerNo() {
		const Thread *thread = Thread::currentThread();
		for (int i = 0; i < maxNumOfWriters; i++)
			if (writers[i].thread == thread)
				return i;
		throw new Exception(S.e + "Internal error: Has no writer");
	}

	Writer &writer() {
		return writers[writerNo()];
	}
};

#endif
