#ifndef FILE_H
#define FILE_H

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

#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <utime.h>
#include "String.h"
#include "StringBuffer.h"

/**
 * An abstract file class similar to java.io.File.
 *
 * @version 20040317
 * @author Jörg P. M. Haeger
 */
class File {
	String pathName;
	String *nameMem, *parentMem;

public:
	File(const char *pathName): pathName(pathName) {
		init();
	}

	File(String *pathName): pathName(pathName) {
		init();
	}

	File(String &pathName): pathName(pathName) {
		init();
	}

	File(File *file) {
		pathName = file->pathName;
		delete file;
		init();
	}

	File(const char *parent, const char *child) {
		pathName = pathName + parent + '/' + child;
		init();
	}

	File(const char *parent, String &child) {
		pathName = pathName + parent + '/' + child;
		init();
	}

	File(const char *parent, String *child) {
		pathName = pathName + parent + '/' + *child;
		delete child;
		init();
	}

	File(String &parent, const char *child) {
		pathName = parent;
		pathName = pathName + '/' + child;
		init();
	}

	File(File &parent, const char *child) {
		pathName = parent.pathName;
		pathName = pathName + '/' + child;
		init();
	}

	File(File &parent, String &child) {
		pathName = parent.pathName;
		pathName = pathName + '/' + child;
		init();
	}

	File(File *parent, const char *child) {
		if (parent == NULL)
			pathName = child;
		else {
			pathName = parent->pathName;
			pathName = pathName + '/' + child;
//			delete parent;
		}
		init();
	}

	~File() {
		if (nameMem != NULL)
			delete nameMem;
		if (parentMem != NULL)
			delete parentMem;
	}

	File &operator=(File *file) {
		pathName = file->pathName;
		delete file;

		if (nameMem != NULL) {
			delete nameMem;
			nameMem = NULL;
		}
		if (parentMem != NULL) {
			delete parentMem;
			parentMem = NULL;
		}

		return *this;
	}

	int checkOwner(uid_t owner) {
		return checkOwner(pathName.getBytes(), owner, 0);
	}

	void chown(uid_t owner, gid_t group) {
		chown(pathName.getBytes(), owner, group);
	}

	const char *complete() {
		if (pathName.indexOf("/") >= 0)
			return pathName.getBytes();

		const char *path = getenv("PATH");
		if (path == NULL)
			return pathName.getBytes();

		while (1) {
			char dir[strlen(path) + 1 + strlen(pathName.getBytes()) + 1];
			const char *ptr = strchr(path, ':');
			if (ptr == NULL)
				strcpy(dir, path);
			else {
				strncpy(dir, path, ptr - path);
				dir[ptr - path] = 0;
				path = ptr + 1;
			}
			if (dir[0] != 0)
				strcat(dir, "/");
			strcat(dir, pathName.getBytes());
			struct stat statBuf;
			if (stat(dir, &statBuf) == 0) {
				pathName = dir;
				break;
			}
			if (ptr == NULL)
				break;
		}
		return pathName.getBytes();
	}

	int exists() {
		struct stat statBuf;
		return stat(pathName.getBytes(), &statBuf) == 0;
	}

	public: String &getName() {
		if (nameMem == NULL)
			nameMem = pathName.substring(
				pathName.lastIndexOf("/") + 1);
		return *nameMem;
	}

	public: String &getParent() {
		if (parentMem == NULL) {
			int pos = pathName.lastIndexOf("/");
			if (pos <= 0)
				parentMem = new String("/");
			else
				parentMem = pathName.substring(0, pos);
		}
		return *parentMem;
	}

	String &getPath() {
		return pathName;
	}

	off_t getSize() {
		return length();
	}

	int getTreeSizeKB() {
		int filesCounter = 0;
		return getTreeSizeKB(pathName.getBytes(), filesCounter);
	}

	int getTreeSizeKB(int &filesCounter) {
		return getTreeSizeKB(pathName.getBytes(), filesCounter);
	}

	uid_t getUser() {
		struct stat statBuf;
		if (stat(pathName.getBytes(), &statBuf) != 0)
			return 0;
		return statBuf.st_uid;
	}

	private: void init() {
		int pos = pathName.length();
		while (pos > 1 && pathName.charAt(pos - 1) == '/')
			pos--;
		if (pos < pathName.length())
			pathName = pathName.substring(0, pos);

		nameMem = parentMem = NULL;
	}

	public: int isDirectory() const {
		String pathName2 = pathName;
		struct stat statBuf;
		if (stat(pathName2.getBytes(), &statBuf) != 0)
			return 0;
		return S_ISDIR(statBuf.st_mode);
	}

	int isLink() {
		struct stat statBuf;
		if (lstat(pathName.getBytes(), &statBuf) != 0)
			return 0;
		return S_ISLNK(statBuf.st_mode);
	}

	int isReadAndWritable(uid_t owner, gid_t group, int depth = 1024) {
		return isReadAndWritable(pathName.getBytes(), owner, group, depth);
	}

	int lastModified() const {
		String pathName(File::pathName);
		struct stat statBuf;
		if (stat(pathName.getBytes(), &statBuf) == 0)
			return statBuf.st_mtime;
		else
			return 0;
	}

	off_t length() {
		struct stat statBuf;
		if (stat(pathName.getBytes(), &statBuf) != 0)
			return 0;
		return statBuf.st_size;
	}

	int mkdir() {
		return ::mkdir(pathName.getBytes(), S_IRWXU) == 0;
	}

	int mkdirs() {
		StringBuffer str = "";
		for (int i = 0; ; i++) {
			char ch;
			if (i < pathName.length())
				ch = pathName.charAt(i);
			else
			if (i == pathName.length())
				ch = '/';
			else
				break;

			str.append(ch);
			if (ch == '/') {
				File dir = new File(new String(str));
				if (!dir.exists())
					if (!dir.mkdir())
						return 0;
			}
		}
		return 1;
	}

	int mkdirsParent() {
		File parent = new File(getParent());
		return parent.mkdirs();
	}

	void remove() {
		remove(pathName.getBytes());
	}

	public: bool renameTo(File &dest) {
		rename(pathName.getBytes(), dest.getPath().getBytes()) == 0;
	}

	int setLastModified(int time) {
		struct utimbuf buf;
		buf.actime = buf.modtime = time;
		return utime(pathName.getBytes(), &buf) == 0;
	}

private:
	static int checkOwner(const char *pathName, uid_t owner, int level) {
		struct stat statBuf;
		if (level <= 1) {
			// follow links on the first or second level
			if (stat(pathName, &statBuf) != 0)
				return -1;
		}
		else
			if (lstat(pathName, &statBuf) != 0)
				return -1;

		if (statBuf.st_uid != owner)
			return -1;

		if (!S_ISDIR(statBuf.st_mode))
			return 0;

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

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

			File f = new File(pathName, dirEntry->d_name);
			if (checkOwner(f.getPath().getBytes(), owner, level + 1) != 0) {
				result = -1;
				break;
			}
		}

		closedir(dir);
		return result;
	}

	static void chown(const char *pathName, uid_t owner, gid_t group) {
		struct stat statBuf;
		if (stat(pathName, &statBuf) != 0)
			return;

		::chown(pathName, owner, group);

		if (!S_ISDIR(statBuf.st_mode))
			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);
			chown(tmpPathName, owner, group);
		}

		closedir(dir);
	}

	static int getTreeSizeKB(
			const char *pathName, int &filesCounter, int level = 0) {
		if (level >= 10)
			return 0;

		struct stat statBuf;
		if (stat(pathName, &statBuf) != 0)
			return 0;

		if (!S_ISDIR(statBuf.st_mode)) {
			filesCounter++;
			return (statBuf.st_size + 1023) / 1024;
		}

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

		int sum = 0;
		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);
			sum += getTreeSizeKB(tmpPathName, filesCounter, level + 1);
		}

		closedir(dir);
		return sum;
	}

	static int isReadAndWritable(const char *pathName,
		uid_t owner, gid_t group, int depth = 1024) {
		struct stat statBuf;
		if (stat(pathName, &statBuf) != 0)
			return -1;

		if (statBuf.st_uid == owner) {
			if ((statBuf.st_mode & S_IRUSR) == 0
				|| (statBuf.st_mode & S_IWUSR) == 0)
				return -1;
		}
		else
			if (statBuf.st_gid == group) {
				if ((statBuf.st_mode & S_IRGRP) == 0
					|| (statBuf.st_mode & S_IWGRP) == 0)
				return -1;
			}
			else
				if ((statBuf.st_mode & S_IROTH) == 0
					|| (statBuf.st_mode & S_IWOTH) == 0)
				return -1;

		if (!S_ISDIR(statBuf.st_mode) || depth <= 0)
			return 0;

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

		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);
			if (isReadAndWritable(tmpPathName,
					owner, group, depth - 1) != 0)
				return -1;
		}

		closedir(dir);
		return 0;
	}

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

		if (!S_ISDIR(statBuf.st_mode)) {
			unlink(pathName);
			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);
			remove(tmpPathName);
		}

		closedir(dir);
		rmdir(pathName);
	}
};

#endif
