/*********************************************************************
 * This file is part of the cpplibs suite.
 *
 * Copyright (C) 2001 Topi Mäenpää
 * All rights reserved.
 *
 * This program is free software. You can redistribute and/or modify
 * it under the terms of the free software licence found in the
 * accompanying file "COPYING". The licence terms must always be
 * redistributed with this source file. The above copyright notice
 * must be reproduced in all modified and unmodified copies of this
 * source file.
 *
 * $Revision: 1.5 $
 *********************************************************************/

#include "../String.h"
#include "File.h"
#include <errno.h>
#include <pwd.h>
#include <grp.h>
#include <stdio.h>
#include <unistd.h>
#include <dirent.h>
#include <glob.h>
#include <libgen.h>
#include <fstream>

#ifndef _SC_GETGR_R_SIZE_MAX
#	 define GETGR_R_SIZE 1024
#else
#  define GETGR_R_SIZE sysconf(_SC_GETGR_R_SIZE_MAX)
#endif

#ifndef _SC_GETPW_R_SIZE_MAX
#	 define GETPW_R_SIZE 1024
#else
#  define GETPW_R_SIZE sysconf(_SC_GETGR_R_SIZE_MAX)
#endif

using namespace std;

namespace util { namespace io {

	void File::mkdir(string path, mode_t mode) throw (IOException&)
	{
		if (::mkdir(path.c_str(), mode))
			throw IOException("File::mkdir(string, mode_t): " + getErrorString(errno));
	}

	void File::remove(string path) throw (IOException&)
	{
		if (isDirectory(path))
			{
				if (rmdir(path.c_str()))
					throw IOException("File::remove(string, mode_t): " + getErrorString(errno));
			}
		else if (unlink(path.c_str()))
			throw IOException("File::remove(string, mode_t): " + getErrorString(errno));
	}

	void File::rename(string path, string name) throw (IOException&)
	{
		if (::rename(path.c_str(), name.c_str()))
			throw IOException("File::rename(string, string): " + getErrorString(errno));
	}

	bool File::exists(string path) throw (IOException&)
	{
		struct stat stats;
		if (!stat(path.c_str(),&stats))
			return true;
		else if (errno == ENOTDIR || errno == ENOENT)
			return false;

		throw IOException("File::exists(string): " + getErrorString(errno));
	}
	
	off_t File::getSize(string path) throw (IOException&)
	{
		struct stat stats;
		if (stat(path.c_str(),&stats))
			throw IOException("File::getSize(string): " + getErrorString(errno));

		return stats.st_size;
	}

	nlink_t File::getLinks(string path) throw (IOException&)
	{
		struct stat stats;
		if (stat(path.c_str(),&stats))
			throw IOException("File::getLinks(string): " + getErrorString(errno));

		return stats.st_nlink;
	}

	void File::chmod(string path, mode_t mode) throw (IOException&)
	{
		if (::chmod(path.c_str(),mode))
			throw IOException("File::chmod(string,mode_t): " + getErrorString(errno));
	}

	mode_t File::getMode(string path) throw (IOException&)
	{
		struct stat stats;
		if (stat(path.c_str(),&stats))
			throw IOException("File::getMode(string): " + getErrorString(errno));

		return stats.st_mode;
	}

	time_t File::getAccessTime(string path) throw (IOException&)
	{
		struct stat stats;
		if (stat(path.c_str(),&stats))
			throw IOException("File::getAccessTime(string): " + getErrorString(errno));

		return stats.st_atime;
	}

	time_t File::getChangeTime(string path) throw (IOException&)
	{
		struct stat stats;
		if (stat(path.c_str(),&stats))
			throw IOException("File::getChangeTime(string): " + getErrorString(errno));

		return stats.st_ctime;
	}

	time_t File::getModificationTime(string path) throw (IOException&)
	{
		struct stat stats;
		if (stat(path.c_str(),&stats))
			throw IOException("File::getModificationTime(string): " + getErrorString(errno));

		return stats.st_mtime;
	}

	string File::getOwner(string path) throw (IOException&)
	{
		struct passwd pwd, *ppwd;
		uid_t uid = getOwnerID(path);
		char bfr[GETPW_R_SIZE];
		if (getpwuid_r(uid, &pwd, bfr, GETPW_R_SIZE, &ppwd))
			return String::toString((int)uid);
		
		return ppwd->pw_name;
	} //getpwuid

	string File::getGroup(string path) throw (IOException&)
	{
		struct group grp, *pgrp;
		gid_t gid = getGroupID(path);
		char bfr[GETGR_R_SIZE];
		if (getgrgid_r(gid, &grp, bfr, GETGR_R_SIZE, &pgrp))
			return String::toString((int)gid);

		return pgrp->gr_name;
	}

	uid_t File::getOwnerID(string path) throw (IOException&)
	{
		struct stat stats;
		if (stat(path.c_str(),&stats))
			throw IOException("File::getOwnerID(string): " + getErrorString(errno));

		return stats.st_uid;
	}

	gid_t File::getGroupID(string path) throw (IOException&)
	{
		struct stat stats;
		if (stat(path.c_str(),&stats))
			throw IOException("File::getGroupID(string): " + getErrorString(errno));

		return stats.st_gid;
	}

	string File::baseName(string path)
	{
		char* ptr = strdup(path.c_str());
		string result(basename(ptr));
		free(ptr);
		return result;
	}

	string File::dirName(std::string path)
	{
		char* ptr = strdup(path.c_str());
		string result(dirname(ptr));
		free(ptr);
		return result;
	}

	void File::setWorkDir(string path) throw (IOException&)
	{
		if (chdir(path.c_str()))
			throw IOException("File::setWorkDir(string): " + getErrorString(errno));
	}

	string File::getWorkDir() throw (IOException&)
	{
		int size = 128;
		while (true)
			{
				char* bfr = (char*)malloc(size);
				if (!bfr)
					throw IOException("File::getWorkDir(): Not enough memory.");
				char *ptr = getcwd(bfr,size);
				if (!ptr)
					{
						free(bfr);
						if (errno == ERANGE)
							size <<= 1;
						else
							throw IOException("File::setWorkDir(string): " + getErrorString(errno));
					}
				else
					{
						string result(ptr);
						free(bfr);
						return result;
					}
			}
	}

	List<string> File::listDir(string path, int blockSize) throw (IOException&)
	{
		struct dirent entry, *pentry;
		DIR* dir = opendir(path.c_str());
		if (!dir)
			throw IOException("File::listDir(string, int): " + getErrorString(errno));
		List<string> result(blockSize, blockSize);
		do
			{
				if (readdir_r(dir, &entry, &pentry))
					throw IOException("File::listDir(string, int): " + getErrorString(errno));
				if (pentry)
					result += pentry->d_name;
			} while (pentry);

		return result;
	}

	List<string> File::wildCard(string pattern) throw (IOException&)
	{
		glob_t gl;
		if (glob(pattern.c_str(), 0, NULL, &gl))
			{
				globfree(&gl);
				return List<string>(0);
			}
		List<string> result(gl.gl_pathc);
		char** ptr = gl.gl_pathv;
		for (int i=gl.gl_pathc; i--; ptr++)
			result += *ptr;
		globfree(&gl);
		
		return result;
	}

	Blob<char> File::readBlob(string file) throw (IOException&)
	{
		unsigned int size = (unsigned int)getSize(file);
		SmartPtr<char> bfr((char*)malloc(size));

		if (!bfr.get())
			throw IOException("File::readBlob(string): Cannot allocate memory.");
			
		ifstream in(file.c_str());
		if (!in)
			throw IOException("File::readBlob(string): Cannot open file.");
		in.read(bfr.get(), size);
		if (!in)
			throw IOException("File::readBlob(string): Cannot read data.");
		in.close();

		return Blob<char>(bfr.release(), size);
	}
	
	string File::readContents(string file) throw (IOException&)
	{
		Blob<char> contents = readBlob(file);
		SmartPtr<char> ptr(contents.getData());
		return string(contents.getData(), contents.getLength());
	}

	List<string> File::readLines(string file, int blockSize) throw (IOException&)
	{
		Blob<char> contents = readBlob(file);
		SmartPtr<char> tmp(contents.getData());

		List<string> result(blockSize, blockSize);

		char* data = contents.getData(), *ptr = data;
		int len = contents.getLength(), start = 0;
		for (int i=0; i<len; i++, ptr++)
			{
				if (*ptr == '\n')
					{
						result += string(data+start, i-start);
						start = i+1;
					}
			}
		if (start < len)
			result += string(data+start, len-start);
		return result;
	}
	

	string File::getErrorString(int error)
	{
		switch (error)
			{
			case EOVERFLOW: return "File too large.";
			case EPERM:
			case EACCES: return "Permission denied.";
			case EFAULT: return "Address space not accessible.";
			case EINTR: return "Internal error (illegal arguments?)";
			case ELOOP: return "Too many symbolic links.";
			case ENAMETOOLONG: return "Name too long.";
			case EROFS: return "Read-only file system.";
			case EIO: return "I/O error.";
			case ENOTDIR:
			case ENOENT: return "File not found.";
			case ENOLINK: return "Cannot reach remote machine.";
			case ENOMEM: return "Not enough memory.";
			case EEXIST: return "File already exists.";
			case EXDEV: return "Cross-device move not allowed.";
			case EISDIR: return "Cannot move on an existing directory.";
			default: return "Unidentified error.";
			}
	}
}}


