/*
 * Copyright 2005-2011, Ingo Weinhold, ingo_weinhold@gmx.de.
 * Distributed under the terms of the MIT License.
 */


#define TRACE() //printf("  %s\n", __func__ );
#define LEAVE() //printf( "%s: leaving\n", __func__ );

#if defined( HoG_GENODE )
/**************/
//static int debug = 1;
#define HoG_GENODE_TRACE(x) //x
#endif


#ifdef BUILDING_FS_SHELL
#	include "compat.h"
#	define B_OK			0
#	define B_FILE_ERROR	EBADF
#else
//#	include <BeOSBuildCompatibility.h>
#endif

#include "fs_descriptors.h"

#include <map>

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <fs_attr.h>

#include <syscalls.h>

#include "fs_impl.h"

using std::map;

static const int kVirtualDescriptorStart = 10000;

typedef map<int, BPrivate::Descriptor*> DescriptorMap;
static DescriptorMap *sDescriptors;

#ifdef HoG_GENODE
#include <base/mutex.h>
static Genode::Mutex sdesc_Lock;
#define MTSAFE_GUARD  Genode::Mutex::Guard lock_guard( sdesc_Lock );
#endif




namespace BPrivate {


static int
dup_maybe_system(int fd)
{
	TRACE();
	
	if (get_descriptor(fd) != NULL)
		return _kern_dup(fd);

	int clonedFD = dup(fd);
	return clonedFD >= 0 ? clonedFD : errno;
}


static status_t
close_maybe_system(int fd)
{
	TRACE();
	
	if (get_descriptor(fd) != NULL)
		return _kern_close(fd);

	return close(fd) == 0 ? B_OK : errno;
}


// #pragma mark - Descriptor


// constructor
Descriptor::~Descriptor()
{
	TRACE();
	
}

#if defined( HoG_GENODE )
Descriptor::Descriptor()
:	fd( -1 )
{
	HoG_GENODE_TRACE( printf("  descriptor base ctor @ %p\n", this) );
}
#endif

// IsSystemFD
bool
Descriptor::IsSystemFD() const
{
	TRACE();
	
	return false;
}

// GetPath
status_t
Descriptor::GetPath(string& path) const
{
	TRACE();
	
	return get_path(fd, NULL, path);
}

// GetNodeRef
status_t
Descriptor::GetNodeRef(NodeRef &ref)
{
	TRACE();
	
//printf(" BASE desc.GetNodeRef\n");
	struct stat st;
	status_t error = GetStat(false, &st);
HoG_GENODE_TRACE( printf(" BASE desc.GetNodeRef called f(?)stat(), which yields (%ld, %ld) ---------> lets call FixUp in this case too ! eh ?\n", st.st_dev, st.st_ino) );
	if (error != B_OK)
		return error;

	ref = NodeRef(st);

	return B_OK;
}


// #pragma mark - FileDescriptor


// constructor
FileDescriptor::FileDescriptor(int fd)
{
	TRACE();
	
	HoG_GENODE_TRACE( printf("   FileDescriptor.ctor fd <%d>\n", fd) );
	
	this->fd = fd;
}

// destructor
FileDescriptor::~FileDescriptor()
{
	Close();
}

// Close
status_t
FileDescriptor::Close()
{
	if (fd >= 0) {
		int oldFD = fd;
		fd = -1;
		if (close(oldFD) < 0)
			return errno;
	}

	return B_OK;
}

// Dup
status_t
FileDescriptor::Dup(Descriptor *&clone)
{
	int dupFD = dup(fd);
	if (dupFD < 0)
		return errno;

	clone = new FileDescriptor(dupFD);
	return B_OK;
}

// GetStat
status_t
FileDescriptor::GetStat(bool traverseLink, struct stat *st)
{
	HoG_GENODE_TRACE( printf("*FileDesc*.GetStat()\n") );
	
	if (fstat(fd, st) < 0)
		return errno;
	return B_OK;
}

// IsSystemFD
bool
FileDescriptor::IsSystemFD() const
{
	return true;
}


// #pragma mark - DirectoryDescriptor


// constructor
DirectoryDescriptor::DirectoryDescriptor(DIR *dir, const NodeRef &ref)
{
	TRACE();
	HoG_GENODE_TRACE( printf("   DirectoryDescriptor NodeR (%ld, %ld) @%p\n", ref.device, ref.node, this) );
	
	this->dir = dir;
	this->ref = ref;
}

// destructor
DirectoryDescriptor::~DirectoryDescriptor()
{
	Close();
}

// Close
status_t
DirectoryDescriptor::Close()
{
	if (dir) {
		DIR *oldDir = dir;
		dir = NULL;
		if (closedir(oldDir) < 0)
			return errno;
	}

	return B_OK;
}

// Dup
status_t
DirectoryDescriptor::Dup(Descriptor *&clone)
{
	string path;
	status_t error = get_path(fd, NULL, path);
	if (error != B_OK)
		return error;

	DIR *dupDir = opendir(path.c_str());
	if (!dupDir)
		return errno;

	clone = new DirectoryDescriptor(dupDir, ref);
	return B_OK;
}

// GetStat
status_t
DirectoryDescriptor::GetStat(bool traverseLink, struct stat *st)
{
	// get a usable path
	string realPath;
	status_t error = get_path(fd, NULL, realPath);
	if (error != B_OK)
		return error;

	// stat
	int result;
///FIXME-2: and what about fstat() above ? and lstat below ?
///FIXME-2: stat() in Dir case !  (in file case and in symlink case we don't care, not affected by the /boot-as-parent-device bug)
	result = stat(realPath.c_str(), st);
HoG_GENODE_TRACE( printf(" DirDescriptor.stat -> (res %d) >>Device: %ld << for path <%s> versus this.ref %ld %ld \n", result, st->st_dev, realPath.c_str(), this->ref.device, this->ref.node) );

	if (result < 0)
		return errno;

	return B_OK;
}

// GetNodeRef
status_t
DirectoryDescriptor::GetNodeRef(NodeRef &ref)
{
	ref = this->ref;

	return B_OK;
}


// #pragma mark - SymlinkDescriptor


// constructor
SymlinkDescriptor::SymlinkDescriptor(const char *path)
{
	TRACE();
	
	this->path = path;
}

// Close
status_t
SymlinkDescriptor::Close()
{
	return B_OK;
}

// Dup
status_t
SymlinkDescriptor::Dup(Descriptor *&clone)
{
	clone = new SymlinkDescriptor(path.c_str());
	return B_OK;
}

// GetStat
status_t
SymlinkDescriptor::GetStat(bool traverseLink, struct stat *st)
{
	// stat
	int result;
	if (traverseLink)
		result = stat(path.c_str(), st);
	else
		result = lstat(path.c_str(), st);
HoG_GENODE_TRACE( printf(" SymLink descriptor .GetNodeRef called ! Should apply FixUp here too\n") );

	if (result < 0)
		return errno;

	return B_OK;
}

// GetPath
status_t
SymlinkDescriptor::GetPath(string& path) const
{
	path = this->path;
	return B_OK;
}


// #pragma mark - AttributeDescriptor


AttributeDescriptor::AttributeDescriptor(int fileFD, const char* attribute,
	uint32 type, int openMode)
	:
	fFileFD(dup_maybe_system(fileFD)),
	fType(type),
	fOpenMode(openMode),
	fData(NULL),
	fDataSize(0)

{
	TRACE();
	
	strlcpy(fAttribute, attribute, sizeof(fAttribute));
}


AttributeDescriptor::~AttributeDescriptor()
{
	Close();
}


status_t
AttributeDescriptor::Init()
{
	if (fFileFD < 0)
		return B_IO_ERROR;

	// stat the attribute
	attr_info info;
	if (fs_stat_attr(fFileFD, fAttribute, &info) < 0) {
		if (errno == B_ENTRY_NOT_FOUND) {
			if ((fOpenMode & O_CREAT) == 0)
				return errno;

			// create the attribute
			if (fs_write_attr(fFileFD, fAttribute, fType, 0, NULL, 0) < 0)
				return errno;
			return B_OK;
		}
		return errno;
	}

	if ((fOpenMode & O_TRUNC) == 0) {
		// truncate the attribute
		if (fs_write_attr(fFileFD, fAttribute, fType, 0, NULL, 0) < 0)
			return errno;
		return B_OK;
	}

	// we have to read in the attribute data
	if (info.size == 0)
		return B_OK;

	fData = (uint8*)malloc(info.size);
	if (fData == NULL)
		return B_NO_MEMORY;

	fDataSize = info.size;

	ssize_t bytesRead = fs_read_attr(fFileFD, fAttribute, fType, 0, fData,
		fDataSize);
	if (bytesRead < 0)
		return errno;
	if ((size_t)bytesRead != fDataSize)
		return B_IO_ERROR;

	return B_OK;
}


status_t
AttributeDescriptor::Write(off_t offset, const void* buffer, size_t bufferSize)
{
	if (offset < 0)
		return B_BAD_VALUE;

	if ((fOpenMode & O_ACCMODE) != O_WRONLY
		&& (fOpenMode & O_ACCMODE) != O_RDWR) {
		return B_NOT_ALLOWED;
	}

	// we may need to resize the buffer
	size_t minSize = (size_t)offset + bufferSize;
	if (minSize > fDataSize) {
		uint8* data = (uint8*)realloc(fData, minSize);
		if (data == NULL)
			return B_NO_MEMORY;

		if ((size_t)offset > fDataSize)
			memset(data + offset, 0, offset - fDataSize);

		fData = data;
		fDataSize = minSize;
	}

	// copy the data and write all of it
	if (bufferSize == 0)
		return B_OK;

	memcpy((uint8*)fData + offset, buffer, bufferSize);

	ssize_t bytesWritten = fs_write_attr(fFileFD, fAttribute, fType, 0,
		fData, fDataSize);
	if (bytesWritten < 0)
		return errno;
	if ((size_t)bytesWritten != fDataSize)
		return B_IO_ERROR;

	return B_OK;
}


status_t
AttributeDescriptor::Close()
{
	if (fFileFD < 0)
		return B_BAD_VALUE;

	close_maybe_system(fFileFD);
	fFileFD = -1;

	free(fData);
	fData = NULL;
	fDataSize = 0;

	return B_OK;
}


status_t
AttributeDescriptor::Dup(Descriptor*& clone)
{
	return B_NOT_SUPPORTED;
}


status_t
AttributeDescriptor::GetStat(bool traverseLink, struct stat* st)
{
	return B_NOT_SUPPORTED;
}


// #pragma mark - AttrDirDescriptor


// constructor
AttrDirDescriptor::AttrDirDescriptor(DIR *dir, const NodeRef &ref)
	: DirectoryDescriptor(dir, ref)
{
	TRACE();
	
}

// destructor
AttrDirDescriptor::~AttrDirDescriptor()
{
	TRACE();
	
	Close();
}

// Close
status_t
AttrDirDescriptor::Close()
{
	TRACE();
	
	if (dir) {
		DIR *oldDir = dir;
		dir = NULL;
		if (fs_close_attr_dir(oldDir) < 0)
			return errno;
	}

	return B_OK;
}

// Dup
status_t
AttrDirDescriptor::Dup(Descriptor *&clone)
{
	TRACE();
	
	// we don't allow dup()int attr dir descriptors
	return B_FILE_ERROR;
}

// GetStat
status_t
AttrDirDescriptor::GetStat(bool traverseLink, struct stat *st)
{
	TRACE();
	
	// we don't allow stat()int attr dir descriptors
	return B_FILE_ERROR;
}

// GetNodeRef
status_t
AttrDirDescriptor::GetNodeRef(NodeRef &ref)
{
	TRACE();
	
	ref = this->ref;

	return B_OK;
}


// get_descriptor
Descriptor *
get_descriptor(int fd)
{
	TRACE();
	
	MTSAFE_GUARD;
	
	if (!sDescriptors)
		return NULL;
	DescriptorMap::iterator it = sDescriptors->find(fd);
	if (it == sDescriptors->end())
		return NULL;
	return it->second;
}

// add_descriptor
int
add_descriptor(Descriptor *descriptor)
{
	TRACE();
	
	MTSAFE_GUARD;
	
	if (!sDescriptors)
		sDescriptors = new DescriptorMap;

	int fd = -1;
	if (FileDescriptor *file = dynamic_cast<FileDescriptor*>(descriptor)) {
		fd = file->fd;
	} else {
		// find a free slot
		for (fd = kVirtualDescriptorStart;
			sDescriptors->find(fd) != sDescriptors->end();
			fd++) {
		}
	}

	(*sDescriptors)[fd] = descriptor;
	descriptor->fd = fd;
HoG_GENODE_TRACE( printf("   ++++ FD descriptors:  mapFD[ %d ] = %p +++++++ \n", fd, descriptor) );

	return fd;
}

// delete_descriptor
status_t
delete_descriptor(int fd)
{
	TRACE();
	
#if defined( HoG_GENODE )
	///ToDo: in HoG we get called before we had a chance to "new" this obj for some reason
	if (!sDescriptors)
		return B_ERROR;
#endif
	
	DescriptorMap::iterator it = sDescriptors->find(fd);
	if (it == sDescriptors->end())
		return B_FILE_ERROR;

	status_t error = it->second->Close();
	delete it->second;
	sDescriptors->erase(it);

	if (sDescriptors->size() == 0) {
		delete sDescriptors;
		sDescriptors = NULL;
	}
	
	LEAVE();
	
	return error;
}


bool
is_unknown_or_system_descriptor(int fd)
{
	TRACE();
	
	Descriptor* descriptor = get_descriptor(fd);
	return descriptor == NULL || descriptor->IsSystemFD();
}


} // namespace BPrivate
