
#ifndef FILE_H
#define FILE_H

#include <dirent.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include "Support.h"

class File
{
	char *pathName;

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

	File(const char *parent, const char *child)
	{
		pathName = new char[
			strlen(parent) + 1 + strlen(child) + 1];
		sprintf(pathName, "%s/%s", parent, child);
	}

	File(const File &parent, const char *child)
	{
		pathName = new char[
			strlen(parent.getName()) + 1 + strlen(child) + 1];
		sprintf(pathName, "%s/%s", parent.getName(), child);
	}

	File(const File *parent, const char *child)
	{
		if (parent == NULL)
		{
			pathName = strDup(child);
			return;
		}

		pathName = new char[
			strlen(parent->getName()) + 1 + strlen(child) + 1];
		sprintf(pathName, "%s/%s", parent->getName(), child);
	}

	~File()
	{
		delete pathName;
	}

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

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

	const char *complete()
	{
		if (strchr(pathName, '/') != NULL)
			return pathName;

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

		while (1)
		{
			char dir[strlen(path) + 1 + strlen(pathName) + 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);
			struct stat statBuf;
			if (stat(dir, &statBuf) == 0)
			{
				delete pathName;
				pathName = strDup(dir);
				break;
			}
			if (ptr == NULL)
				break;
		}
		return pathName;
	}

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

	const char *getName() const
	{
		return pathName;
	}

	int getSize() const
	{
		struct stat statBuf;
		if (stat(pathName, &statBuf) != 0)
			return 0;
		return statBuf.st_size;
	}

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

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

	int isReadAndWritable(uid_t owner, gid_t group)
	{
		return isReadAndWritable(pathName, owner, group);
	}

	void mkdirs() const
	{
//		char str[strlen(pathName) + 2];
//		sprintf(str, "%s/", pathName);
//		makeDirs(str);
		makeDirs(pathName);
	}

	void remove()
	{
		remove(pathName);
	}

private:
	int checkOwner(const char *pathName, uid_t owner)
	{
		struct stat statBuf;
		if (stat(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;

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

		closedir(dir);
		return 0;
	}

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

	int isReadAndWritable(const char *pathName,
		uid_t owner, gid_t group)
	{
		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))
			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) != 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
