#include "fuseapp.h"
#include <errno.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <typeinfo>

static FuseAppBase * fuseApp;

FuseAppBase::FuseAppBase()
{
}
FuseAppBase::~FuseAppBase()
{
}
void * FuseAppBase::init(fuse_conn_info*)
{
	return NULL;
}
int FuseAppBase::opt_parse(void*, const char *, int, fuse_args*)
{
	return 1;
}
int FuseAppBase::access(const char *, int)
{
	return -ENOSYS;
}
int FuseAppBase::chmod(const char *, mode_t)
{
	return -ENOSYS;
}
int FuseAppBase::chown(const char *, uid_t, gid_t)
{
	return -ENOSYS;
}
int FuseAppBase::create(const char *, mode_t, struct fuse_file_info *)
{
	return -ENOSYS;
}
int FuseAppBase::fgetattr(const char *, struct stat *, struct fuse_file_info *)
{
	return -ENOSYS;
}
int FuseAppBase::flush(const char *, struct fuse_file_info *)
{
	return -ENOSYS;
}
int FuseAppBase::fsync(const char *, int, struct fuse_file_info *)
{
	return -ENOSYS;
}
int FuseAppBase::fsyncdir(const char *, int, struct fuse_file_info *)
{
	return -ENOSYS;
}
int FuseAppBase::ftruncate(const char *, off_t, struct fuse_file_info *)
{
	return -ENOSYS;
}
int FuseAppBase::getattr(const char *, struct stat *)
{
	return -ENOSYS;
}
int FuseAppBase::getxattr(const char *, const char *, char *, size_t)
{
	return -ENOSYS;
}
int FuseAppBase::link(const char *, const char *)
{
	return -ENOSYS;
}
int FuseAppBase::listxattr(const char *, char *, size_t)
{
	return -ENOSYS;
}
int FuseAppBase::mkdir(const char *, mode_t)
{
	return -ENOSYS;
}
int FuseAppBase::mknod(const char *, mode_t, dev_t)
{
	return -ENOSYS;
}
int FuseAppBase::open(const char *, struct fuse_file_info *)
{
	return -ENOSYS;
}
int FuseAppBase::opendir(const char *, struct fuse_file_info *)
{
	return -ENOSYS;
}
int FuseAppBase::read(const char *, char *, size_t, off_t, struct fuse_file_info *)
{
	return -ENOSYS;
}
int FuseAppBase::readdir(const char *, void *, fuse_fill_dir_t, off_t, struct fuse_file_info *)
{
	return -ENOSYS;
}
int FuseAppBase::readlink(const char *, char *, size_t)
{
	return -ENOSYS;
}
int FuseAppBase::release(const char *, struct fuse_file_info *)
{
	return -ENOSYS;
}
int FuseAppBase::releasedir(const char *, struct fuse_file_info *)
{
	return -ENOSYS;
}
int FuseAppBase::removexattr(const char *, const char *)
{
	return -ENOSYS;
}
int FuseAppBase::rename(const char *, const char *)
{
	return -ENOSYS;
}
int FuseAppBase::rmdir(const char *)
{
	return -ENOSYS;
}
int FuseAppBase::setxattr(const char *, const char *, const char *, size_t, int)
{
	return -ENOSYS;
}
int FuseAppBase::statfs(const char *, struct statvfs *)
{
	return -ENOSYS;
}
int FuseAppBase::symlink(const char *, const char *)
{
	return -ENOSYS;
}
int FuseAppBase::truncate(const char *, off_t)
{
	return -ENOSYS;
}
int FuseAppBase::unlink(const char *)
{
	return -ENOSYS;
}
int FuseAppBase::write(const char *, const char *, size_t, off_t, struct fuse_file_info *)
{
	return -ENOSYS;
}
int FuseAppBase::lock(const char *, struct fuse_file_info *, int, struct flock *)
{
	return -ENOSYS;
}
int FuseAppBase::utimens(const char *, const struct timespec[2])
{
	return -ENOSYS;
}
int FuseAppBase::bmap(const char *, size_t, uint64_t *)
{
	return -ENOSYS;
}
int FuseAppBase::onError(const std::exception & e) throw()
{
	fprintf(stderr, "Unknown exception calling (what: %s)\n", e.what());
	return -ENOSYS;
}

template <typename... Args>
class fuseCall {
	public:
		template <int (FuseAppBase::*f)(Args...)>
			static int helper(Args ... a)
			{
				try {
					return (fuseApp->*f)(a...);
				}
				catch (const std::exception & ex) {
					if (int rtn = fuseApp->onError(ex)) {
						return rtn;
					}
					return helper<f>(a...);
				}
				catch (...) {
					fprintf(stderr, "Unknown exception calling %s\n", typeid(f).name());
					return -ENOSYS;
				}
			}
};
static void * fuseInit (struct fuse_conn_info *conn)
{
	return fuseApp->init(conn);
}
static void fuseDestroy(void *)
{
	delete fuseApp;
}

int
FuseAppBase::run(int & argc, char** & argv, FuseAppBase * fa)
{
	struct fuse_opt fuse_opts[] = {
		{ NULL, 0, 0 }
	};
	fuseApp = fa;
	struct fuse_operations operations = {
		fuseCall<const char *, struct stat *>::helper<&FuseAppBase::getattr>,
		fuseCall<const char *, char *, size_t>::helper<&FuseAppBase::readlink>,
		NULL, // getdir deprecated
		fuseCall<const char *, mode_t, dev_t>::helper<&FuseAppBase::mknod>,
		fuseCall<const char *, mode_t>::helper<&FuseAppBase::mkdir>,
		fuseCall<const char *>::helper<&FuseAppBase::unlink>,
		fuseCall<const char *>::helper<&FuseAppBase::rmdir>,
		fuseCall<const char *, const char *>::helper<&FuseAppBase::symlink>,
		fuseCall<const char *, const char *>::helper<&FuseAppBase::rename>,
		fuseCall<const char *, const char *>::helper<&FuseAppBase::link>,
		fuseCall<const char *, mode_t>::helper<&FuseAppBase::chmod>,
		fuseCall<const char *, uid_t, gid_t>::helper<&FuseAppBase::chown>,
		fuseCall<const char *, off_t>::helper<&FuseAppBase::truncate>,
		NULL, // utime deprecated
		fuseCall<const char *, struct fuse_file_info *>::helper<&FuseAppBase::open>,
		fuseCall<const char *, char *, size_t, off_t, struct fuse_file_info *>::helper<&FuseAppBase::read>,
		fuseCall<const char *, const char *, size_t, off_t, struct fuse_file_info *>::helper<&FuseAppBase::write>,
		fuseCall<const char *, struct statvfs *>::helper<&FuseAppBase::statfs>,
		fuseCall<const char *, struct fuse_file_info *>::helper<&FuseAppBase::flush>,
		fuseCall<const char *, struct fuse_file_info *>::helper<&FuseAppBase::release>,
		fuseCall<const char *, int, struct fuse_file_info *>::helper<&FuseAppBase::fsync>,
		fuseCall<const char *, const char *, const char *, size_t, int>::helper<&FuseAppBase::setxattr>,
		fuseCall<const char *, const char *, char *, size_t>::helper<&FuseAppBase::getxattr>,
		fuseCall<const char *, char *, size_t>::helper<&FuseAppBase::listxattr>,
		fuseCall<const char *, const char *>::helper<&FuseAppBase::removexattr>,
		fuseCall<const char *, struct fuse_file_info *>::helper<&FuseAppBase::opendir>,
		fuseCall<const char *, void *, fuse_fill_dir_t, off_t, struct fuse_file_info *>::helper<&FuseAppBase::readdir>,
		fuseCall<const char *, struct fuse_file_info *>::helper<&FuseAppBase::releasedir>,
		fuseCall<const char *, int, struct fuse_file_info *>::helper<&FuseAppBase::fsyncdir>,
		fuseInit,
		fuseDestroy,
		fuseCall<const char *, int>::helper<&FuseAppBase::access>,
		fuseCall<const char *, mode_t, struct fuse_file_info *>::helper<&FuseAppBase::create>,
		fuseCall<const char *, off_t, struct fuse_file_info *>::helper<&FuseAppBase::ftruncate>,
		fuseCall<const char *, struct stat *, struct fuse_file_info *>::helper<&FuseAppBase::fgetattr>
#if (FUSE_MINOR_VERSION >= 6)
			,
		fuseCall<const char *, struct fuse_file_info *, int, struct flock *>::helper<&FuseAppBase::lock>,
		fuseCall<const char *, const struct timespec [2]>::helper<&FuseAppBase::utimens>,
		fuseCall<const char *, size_t, uint64_t *>::helper<&FuseAppBase::bmap>
#if (FUSE_MINOR_VERSION >= 8)
			,
		0, // flag_nullpath_ok
#if (FUSE_MINOR_VERSION >= 9)
		0, // flag_nopath
		0, // flag_utime_omit_ok
#endif
		0, // flag_reserved
		NULL, // ioctl
		NULL // poll
#if (FUSE_MINOR_VERSION >= 9)
			,
		NULL, // writebuf
		NULL, // readbuf
		NULL, // flock
		NULL // fallocate
#endif
#endif
#endif
	};
	struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
	if (fuse_opt_parse(&args, fuseApp, fuse_opts,
			fuseCall<void *, const char *, int, struct fuse_args *>::helper<&FuseAppBase::opt_parse>) == -1) {
		exit(1);
	}
	return fuse_main(args.argc, args.argv, &operations, fa);
}