#ifndef FUSEAPP_H
#define FUSEAPP_H

#define FUSE_USE_VERSION 26
#include <fuse.h>
#include <typeinfo>
#include <exception>
#include <stdio.h>
#include <errno.h>

class FuseAppBase {
	public:
		FuseAppBase();
		virtual ~FuseAppBase() = 0;
		virtual void * init (struct fuse_conn_info * info);
		virtual int opt_parse(void *, const char * arg, int key, struct fuse_args *);
		virtual int access(const char *, int);
		virtual int chmod(const char *, mode_t);
		virtual int chown(const char *, uid_t, gid_t);
		virtual int create(const char *, mode_t, struct fuse_file_info *);
		virtual int fgetattr(const char *, struct stat *, struct fuse_file_info *);
		virtual int flush(const char *, struct fuse_file_info *);
		virtual int fsync(const char *, int, struct fuse_file_info *);
		virtual int fsyncdir(const char *, int, struct fuse_file_info *);
		virtual int ftruncate(const char *, off_t, struct fuse_file_info *);
		virtual int getattr(const char *, struct stat *);
		virtual int getxattr(const char *, const char *, char *, size_t);
		virtual int link(const char *, const char *);
		virtual int listxattr(const char *, char *, size_t);
		virtual int mkdir(const char *, mode_t);
		virtual int mknod(const char *, mode_t, dev_t);
		virtual int open(const char *, struct fuse_file_info *);
		virtual int opendir(const char *, struct fuse_file_info *);
		virtual int read(const char *, char *, size_t, off_t, struct fuse_file_info *);
		virtual int readdir(const char *, void *, fuse_fill_dir_t, off_t, struct fuse_file_info *);
		virtual int readlink(const char *, char *, size_t);
		virtual int release(const char *, struct fuse_file_info *);
		virtual int releasedir(const char *, struct fuse_file_info *);
		virtual int removexattr(const char *, const char *);
		virtual int rename(const char *, const char *);
		virtual int rmdir(const char *);
		virtual int setxattr(const char *, const char *, const char *, size_t, int);
		virtual int statfs(const char *, struct statvfs *);
		virtual int symlink(const char *, const char *);
		virtual int truncate(const char *, off_t);
		virtual int unlink(const char *);
		virtual int write(const char *, const char *, size_t, off_t, struct fuse_file_info *);
		virtual int lock(const char *, struct fuse_file_info *, int cmd, struct flock *);
		virtual int utimens(const char *, const struct timespec tv[2]);
		virtual int bmap(const char *, size_t blocksize, uint64_t *idx);
		virtual int ioctl(const char *, int cmd, void *arg, struct fuse_file_info *, unsigned int flags, void * data);
		virtual int poll(const char *, struct fuse_file_info *, struct fuse_pollhandle *, unsigned *);
		virtual int write_buf(const char *, struct fuse_bufvec *buf, off_t off, struct fuse_file_info *);
		virtual int read_buf(const char *, struct fuse_bufvec **bufp, size_t size, off_t off, struct fuse_file_info *);
		virtual int flock(const char *, struct fuse_file_info *, int op);
		virtual int fallocate(const char *, int, off_t, off_t, struct fuse_file_info *);
		virtual int onError(const std::exception & err) throw();

		virtual int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc) = 0;
		virtual int main(int &, char **, const struct fuse_operations *) = 0;

#define GetHelper(func) getHelper<&FuseAppBase::func>(typeid(&FuseAppBase::func) != typeid(&FuseApp::func))
		template <typename FuseApp>
		static int run(int & argc, char** & argv, FuseApp * fa)
		{
			auto args = fa->runint(argc, argv);
			struct fuse_operations operations = {
				fuseCall<const char *, struct stat *>::GetHelper(getattr),
				fuseCall<const char *, char *, size_t>::GetHelper(readlink),
				NULL, // getdir deprecated
				fuseCall<const char *, mode_t, dev_t>::GetHelper(mknod),
				fuseCall<const char *, mode_t>::GetHelper(mkdir),
				fuseCall<const char *>::GetHelper(unlink),
				fuseCall<const char *>::GetHelper(rmdir),
				fuseCall<const char *, const char *>::GetHelper(symlink),
				fuseCall<const char *, const char *>::GetHelper(rename),
				fuseCall<const char *, const char *>::GetHelper(link),
				fuseCall<const char *, mode_t>::GetHelper(chmod),
				fuseCall<const char *, uid_t, gid_t>::GetHelper(chown),
				fuseCall<const char *, off_t>::GetHelper(truncate),
				NULL, // utime deprecated
				fuseCall<const char *, struct fuse_file_info *>::GetHelper(open),
				fuseCall<const char *, char *, size_t, off_t, struct fuse_file_info *>::GetHelper(read),
				fuseCall<const char *, const char *, size_t, off_t, struct fuse_file_info *>::GetHelper(write),
				fuseCall<const char *, struct statvfs *>::GetHelper(statfs),
				fuseCall<const char *, struct fuse_file_info *>::GetHelper(flush),
				fuseCall<const char *, struct fuse_file_info *>::GetHelper(release),
				fuseCall<const char *, int, struct fuse_file_info *>::GetHelper(fsync),
				fuseCall<const char *, const char *, const char *, size_t, int>::GetHelper(setxattr),
				fuseCall<const char *, const char *, char *, size_t>::GetHelper(getxattr),
				fuseCall<const char *, char *, size_t>::GetHelper(listxattr),
				fuseCall<const char *, const char *>::GetHelper(removexattr),
				fuseCall<const char *, struct fuse_file_info *>::GetHelper(opendir),
				fuseCall<const char *, void *, fuse_fill_dir_t, off_t, struct fuse_file_info *>::GetHelper(readdir),
				fuseCall<const char *, struct fuse_file_info *>::GetHelper(releasedir),
				fuseCall<const char *, int, struct fuse_file_info *>::GetHelper(fsyncdir),
				fuseInit,
				fuseDestroy,
				fuseCall<const char *, int>::GetHelper(access),
				fuseCall<const char *, mode_t, struct fuse_file_info *>::GetHelper(create),
				fuseCall<const char *, off_t, struct fuse_file_info *>::GetHelper(ftruncate),
				fuseCall<const char *, struct stat *, struct fuse_file_info *>::GetHelper(fgetattr),
#if (FUSE_MINOR_VERSION >= 6)
				fuseCall<const char *, struct fuse_file_info *, int, struct flock *>::GetHelper(lock),
				fuseCall<const char *, const struct timespec [2]>::GetHelper(utimens),
				fuseCall<const char *, size_t, uint64_t *>::GetHelper(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
				fuseCall<const char *, int, void *, struct fuse_file_info *, unsigned int, void *>::GetHelper(ioctl),
				fuseCall<const char *, struct fuse_file_info *, struct fuse_pollhandle *, unsigned *>::GetHelper(poll),
#if (FUSE_MINOR_VERSION >= 9)
				fuseCall<const char *, struct fuse_bufvec *, off_t, struct fuse_file_info *>::GetHelper(write_buf),
				fuseCall<const char *, struct fuse_bufvec **, size_t, off_t, struct fuse_file_info *>::GetHelper(read_buf),
				fuseCall<const char *, struct fuse_file_info *, int>::GetHelper(flock),
				fuseCall<const char *, int, off_t, off_t, struct fuse_file_info *>::GetHelper(fallocate),
#endif
#endif
#endif
			};
			return fa->main(args.argc, args.argv, &operations);
		}
		struct fuse_args runint(int &, char ** &);

	private:
		static void * fuseInit(struct fuse_conn_info *conn);
		static void fuseDestroy(void *);

		template <typename... Args>
		class fuseCall {
			public:
				typedef int (*WrapperFunc)(Args...);
				template <int (FuseAppBase::*b)(Args...)>
				static WrapperFunc getHelper(bool implemented)
				{
					auto func = &helper<b>;
					return implemented ? func : NULL;
				}
				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 FuseAppBase * fuseApp;
};

#endif