/*
 * VBMouse - VirtualBox Shared Folders service client functions
 * Copyright (C) 2022 Javier S. Pedro
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

#ifndef VBOXSHFL_H
#define VBOXSHFL_H

#include "vboxhgcm.h"

#define SHFLSTRING_WITH_BUF(varname, bufsize) \
	struct { \
	    SHFLSTRING shflstr; \
	    char buf[bufsize]; \
	} varname = { .shflstr = { .u16Size = bufsize} }

#define SHFLDIRINFO_WITH_NAME_BUF(varname, bufsize) \
	struct { \
	    SHFLDIRINFO dirinfo; \
	    char buf[bufsize]; \
	} varname = { \
	    .dirinfo = { .name = { .u16Size = bufsize} } \
	}

static inline unsigned shflstring_size_with_buf(const SHFLSTRING *str)
{
	return sizeof(SHFLSTRING) + str->u16Size;
}

static inline unsigned shflstring_size_optional_in(const SHFLSTRING *str)
{
	if (str->u16Length > 0) {
		return sizeof(SHFLSTRING) + str->u16Size;
	} else {
		return 0;
	}
}

static inline void shflstring_clear(SHFLSTRING *str)
{
	str->u16Length = 0;
}

static void shflstring_strcpy(SHFLSTRING *str, const char __far *src)
{
	str->u16Length = MIN(_fstrlen(src), str->u16Size - 1);
	_fmemcpy(str->ach, src, str->u16Length);
	str->ach[str->u16Length] = '\0';
}

static void shflstring_strncpy(SHFLSTRING *str, const char __far *src, unsigned len)
{
	str->u16Length = MIN(len, str->u16Size - 1);
	_fmemcpy(str->ach, src, str->u16Length);
	str->ach[str->u16Length] = '\0';
}

static inline void vbox_hgcm_set_parameter_shflroot(VMMDevHGCMCall __far *req, unsigned arg, SHFLROOT root)
{
	vbox_hgcm_set_parameter_uint32(req, arg, root);
}

static inline void vbox_hgcm_set_parameter_shflhandle(VMMDevHGCMCall __far *req, unsigned arg, SHFLHANDLE handle)
{
	vbox_hgcm_set_parameter_uint64(req, arg, handle);
}

static void vbox_hgcm_set_parameter_shflstring(VMMDevHGCMCall __far *req, unsigned arg, const SHFLSTRING *str)
{
	vbox_hgcm_set_parameter_pointer(req, arg, shflstring_size_with_buf(str), str);
}

static vboxerr vbox_shfl_query_mappings(LPVBOXCOMM vb, hgcm_client_id_t client_id, uint32_t flags, unsigned __far *num_maps, SHFLMAPPING __far *maps)
{
	VMMDevHGCMCall __far *req = (void __far *) vb->buf;
	vboxerr err;

	vbox_hgcm_init_call(req, client_id, SHFL_FN_QUERY_MAPPINGS, 3);

	vbox_hgcm_set_parameter_uint32(req, 0, flags);
	vbox_hgcm_set_parameter_uint32(req, 1, *num_maps);
	vbox_hgcm_set_parameter_pointer(req, 2, sizeof(SHFLMAPPING) * *num_maps, maps);

	if ((err = vbox_hgcm_do_call_sync(vb, req)) < 0)
		return err;

	*num_maps = vbox_hgcm_get_parameter_uint32(req, 1);

	return req->header.result;
}

static vboxerr vbox_shfl_query_map_name(LPVBOXCOMM vb, hgcm_client_id_t client_id, SHFLROOT root, SHFLSTRING *name)
{
	VMMDevHGCMCall __far *req = (void __far *) vb->buf;
	vboxerr err;

	vbox_hgcm_init_call(req, client_id, SHFL_FN_QUERY_MAP_NAME, 2);

	vbox_hgcm_set_parameter_shflroot(req, 0, root);
	vbox_hgcm_set_parameter_shflstring(req, 1, name);

	if ((err = vbox_hgcm_do_call_sync(vb, req)) < 0)
		return err;

	return req->header.result;
}

static vboxerr vbox_shfl_map_folder(LPVBOXCOMM vb, hgcm_client_id_t client_id, const SHFLSTRING *name, SHFLROOT *root)
{
	VMMDevHGCMCall __far *req = (void __far *) vb->buf;
	vboxerr err;

	vbox_hgcm_init_call(req, client_id, SHFL_FN_MAP_FOLDER, 4);

	// arg 0 in shflstring "name"
	vbox_hgcm_set_parameter_pointer(req, 0, shflstring_size_with_buf(name), name);

	// arg 1 out uint32 "root"
	vbox_hgcm_set_parameter_uint32(req, 1, 0);

	// arg 2 in uint32 "delimiter"
	vbox_hgcm_set_parameter_uint32(req, 2, '\\');

	// arg 3 in uint32 "caseSensitive"
	vbox_hgcm_set_parameter_uint32(req, 3, 0);

	if ((err = vbox_hgcm_do_call_sync(vb, req)) < 0)
		return err;

	*root = vbox_hgcm_get_parameter_uint32(req, 1);

	return req->header.result;
}

static vboxerr vbox_shfl_unmap_folder(LPVBOXCOMM vb, hgcm_client_id_t client_id, SHFLROOT root)
{
	VMMDevHGCMCall __far *req = (void __far *) vb->buf;
	vboxerr err;

	vbox_hgcm_init_call(req, client_id, SHFL_FN_UNMAP_FOLDER, 1);

	// arg 0 in uint32 "root"
	vbox_hgcm_set_parameter_shflroot(req, 0, root);

	if (err = vbox_hgcm_do_call_sync(vb, req))
		return err;

	return req->header.result;
}

static vboxerr vbox_shfl_open(LPVBOXCOMM vb, hgcm_client_id_t client_id, SHFLROOT root, const SHFLSTRING *name, SHFLCREATEPARMS *parms)
{
	VMMDevHGCMCall __far *req = (void __far *) vb->buf;
	vboxerr err;

	vbox_hgcm_init_call(req, client_id, SHFL_FN_CREATE, 3);

	// arg 0 in uint32 "root"
	vbox_hgcm_set_parameter_shflroot(req, 0, root);

	// arg 1 in shflstring "name"
	vbox_hgcm_set_parameter_shflstring(req, 1, name);

	// arg 2 in shflcreateparms "parms"
	vbox_hgcm_set_parameter_pointer(req, 2, sizeof(SHFLCREATEPARMS), parms);

	if ((err = vbox_hgcm_do_call_sync(vb, req)) < 0)
		return err;

	return req->header.result;
}

static vboxerr vbox_shfl_close(LPVBOXCOMM vb, hgcm_client_id_t client_id, SHFLROOT root, SHFLHANDLE handle)
{
	VMMDevHGCMCall __far *req = (void __far *) vb->buf;
	vboxerr err;

	vbox_hgcm_init_call(req, client_id, SHFL_FN_CLOSE, 2);

	// arg 0 in uint32 "root"
	vbox_hgcm_set_parameter_shflroot(req, 0, root);

	// arg 1 in uint64 "handle"
	vbox_hgcm_set_parameter_shflhandle(req, 1, handle);

	if ((err = vbox_hgcm_do_call_sync(vb, req)) < 0)
		return err;

	return req->header.result;
}

static vboxerr vbox_shfl_read(LPVBOXCOMM vb, hgcm_client_id_t client_id, SHFLROOT root, SHFLHANDLE handle,
                               unsigned long offset, unsigned __far *size, void __far *buffer)
{
	VMMDevHGCMCall __far *req = (void __far *) vb->buf;
	vboxerr err;

	vbox_hgcm_init_call(req, client_id, SHFL_FN_READ, 5);

	// arg 0 in uint32 "root"
	vbox_hgcm_set_parameter_shflroot(req, 0, root);

	// arg 1 in uint64 "handle"
	vbox_hgcm_set_parameter_shflhandle(req, 1, handle);

	// arg 2 in uint64 "offset"
	vbox_hgcm_set_parameter_uint64(req, 2, offset);

	// arg 3 inout uint32 "size"
	vbox_hgcm_set_parameter_uint32(req, 3, *size);

	// arg 4 out void "buffer"
	vbox_hgcm_set_parameter_pointer(req, 4, *size, buffer);

	if ((err = vbox_hgcm_do_call_sync(vb, req)) < 0)
		return err;

	*size = vbox_hgcm_get_parameter_uint32(req, 3);

	return req->header.result;
}

static vboxerr vbox_shfl_write(LPVBOXCOMM vb, hgcm_client_id_t client_id, SHFLROOT root, SHFLHANDLE handle,
                               unsigned long offset, unsigned __far *size, void __far *buffer)
{
	VMMDevHGCMCall __far *req = (void __far *) vb->buf;
	vboxerr err;

	vbox_hgcm_init_call(req, client_id, SHFL_FN_WRITE, 5);

	// arg 0 in uint32 "root"
	vbox_hgcm_set_parameter_shflroot(req, 0, root);

	// arg 1 in uint64 "handle"
	vbox_hgcm_set_parameter_shflhandle(req, 1, handle);

	// arg 2 in uint64 "offset"
	vbox_hgcm_set_parameter_uint64(req, 2, offset);

	// arg 3 inout uint32 "size"
	vbox_hgcm_set_parameter_uint32(req, 3, *size);

	// arg 4 in void "buffer"
	vbox_hgcm_set_parameter_pointer(req, 4, *size, buffer);

	if ((err = vbox_hgcm_do_call_sync(vb, req)) < 0)
		return err;

	*size = vbox_hgcm_get_parameter_uint32(req, 3);

	return req->header.result;
}

static vboxerr vbox_shfl_lock(LPVBOXCOMM vb, hgcm_client_id_t client_id, SHFLROOT root, SHFLHANDLE handle,
                              unsigned long offset, unsigned long length, unsigned flags)
{
	VMMDevHGCMCall __far *req = (void __far *) vb->buf;
	vboxerr err;

	vbox_hgcm_init_call(req, client_id, SHFL_FN_LOCK, 5);

	// arg 0 in uint32 "root"
	vbox_hgcm_set_parameter_shflroot(req, 0, root);

	// arg 1 in uint64 "handle"
	vbox_hgcm_set_parameter_shflhandle(req, 1, handle);

	// arg 2 in uint64 "offset"
	vbox_hgcm_set_parameter_uint64(req, 2, offset);

	// arg 3 in uint64 "length"
	vbox_hgcm_set_parameter_uint64(req, 3, length);

	// arg 4 in uint32 "flags"
	vbox_hgcm_set_parameter_uint32(req, 4, flags);

	if ((err = vbox_hgcm_do_call_sync(vb, req)) < 0)
		return err;

	return req->header.result;
}

static vboxerr vbox_shfl_flush(LPVBOXCOMM vb, hgcm_client_id_t client_id, SHFLROOT root, SHFLHANDLE handle)
{
	VMMDevHGCMCall __far *req = (void __far *) vb->buf;
	vboxerr err;

	vbox_hgcm_init_call(req, client_id, SHFL_FN_FLUSH, 2);

	// arg 0 in uint32 "root"
	vbox_hgcm_set_parameter_shflroot(req, 0, root);

	// arg 1 in uint64 "handle"
	vbox_hgcm_set_parameter_shflhandle(req, 1, handle);

	if ((err = vbox_hgcm_do_call_sync(vb, req)) < 0)
		return err;

	return req->header.result;
}

static vboxerr vbox_shfl_list(LPVBOXCOMM vb, hgcm_client_id_t client_id, SHFLROOT root, SHFLHANDLE handle,
                              unsigned flags, unsigned __far *size, const SHFLSTRING *path, SHFLDIRINFO *dirinfo, unsigned __far *resume, unsigned __far *count)
{
	VMMDevHGCMCall __far *req = (void __far *) vb->buf;
	vboxerr err;

	vbox_hgcm_init_call(req, client_id, SHFL_FN_LIST, 8);

	// arg 0 in uint32 "root"
	vbox_hgcm_set_parameter_shflroot(req, 0, root);

	// arg 1 in uint64 "handle"
	vbox_hgcm_set_parameter_shflhandle(req, 1, handle);

	// arg 2 in uint32 "flags"
	vbox_hgcm_set_parameter_uint32(req, 2, flags);

	// arg 3 inout uint32 "size"
	vbox_hgcm_set_parameter_uint32(req, 3, *size);

	// arg 4 in shflstring "path"
	vbox_hgcm_set_parameter_pointer(req, 4, shflstring_size_optional_in(path), path);

	// arg 5 out void "dirinfo"
	vbox_hgcm_set_parameter_pointer(req, 5, *size, dirinfo);

	// arg 6 inout uint32 "resume_point"
	vbox_hgcm_set_parameter_uint32(req, 6, *resume);

	// arg 7 out uint32 "count"
	vbox_hgcm_set_parameter_uint32(req, 7, 0);

	if ((err = vbox_hgcm_do_call_sync(vb, req)) < 0)
		return err;

	*size = vbox_hgcm_get_parameter_uint32(req, 3);
	*resume = vbox_hgcm_get_parameter_uint32(req, 6);
	*count = vbox_hgcm_get_parameter_uint32(req, 7);

	return req->header.result;
}

static vboxerr vbox_shfl_info(LPVBOXCOMM vb, hgcm_client_id_t client_id, SHFLROOT root, SHFLHANDLE handle,
                               unsigned flags, unsigned __far *size, void __far *buffer)
{
	VMMDevHGCMCall __far *req = (void __far *) vb->buf;
	vboxerr err;

	vbox_hgcm_init_call(req, client_id, SHFL_FN_INFORMATION, 5);

	// arg 0 in uint32 "root"
	vbox_hgcm_set_parameter_shflroot(req, 0, root);

	// arg 1 in uint64 "handle"
	vbox_hgcm_set_parameter_shflhandle(req, 1, handle);

	// arg 2 in uint32 "flags"
	vbox_hgcm_set_parameter_uint32(req, 2, flags);

	// arg 3 inout uint32 "size"
	vbox_hgcm_set_parameter_uint32(req, 3, *size);

	// arg 4 inout void "buffer"
	vbox_hgcm_set_parameter_pointer(req, 4, *size, buffer);

	if ((err = vbox_hgcm_do_call_sync(vb, req)) < 0)
		return err;

	*size = vbox_hgcm_get_parameter_uint32(req, 3);

	return req->header.result;
}

static vboxerr vbox_shfl_remove(LPVBOXCOMM vb, hgcm_client_id_t client_id, SHFLROOT root,
                                const SHFLSTRING *path, unsigned flags)
{
	VMMDevHGCMCall __far *req = (void __far *) vb->buf;
	vboxerr err;

	vbox_hgcm_init_call(req, client_id, SHFL_FN_REMOVE, 3);

	// arg 0 in uint32 "root"
	vbox_hgcm_set_parameter_shflroot(req, 0, root);

	// arg 1 in shflstring "path"
	vbox_hgcm_set_parameter_shflstring(req, 1, path);

	// arg 2 in uint32 "flags"
	vbox_hgcm_set_parameter_uint32(req, 2, flags);

	if ((err = vbox_hgcm_do_call_sync(vb, req)) < 0)
		return err;

	return req->header.result;
}

static vboxerr vbox_shfl_rename(LPVBOXCOMM vb, hgcm_client_id_t client_id, SHFLROOT root,
                                const SHFLSTRING *src, const SHFLSTRING *dst, unsigned flags)
{
	VMMDevHGCMCall __far *req = (void __far *) vb->buf;
	vboxerr err;

	vbox_hgcm_init_call(req, client_id, SHFL_FN_RENAME, 4);

	// arg 0 in uint32 "root"
	vbox_hgcm_set_parameter_shflroot(req, 0, root);

	// arg 1 in shflstring "src"
	vbox_hgcm_set_parameter_shflstring(req, 1, src);

	// arg 2 in shflstring "dst"
	vbox_hgcm_set_parameter_shflstring(req, 2, dst);

	// arg 3 in uint32 "flags"
	vbox_hgcm_set_parameter_uint32(req, 3, flags);

	if ((err = vbox_hgcm_do_call_sync(vb, req)) < 0)
		return err;

	return req->header.result;
}

static vboxerr vbox_shfl_set_utf8(LPVBOXCOMM vb, hgcm_client_id_t client_id)
{
	VMMDevHGCMCall __far *req = (void __far *) vb->buf;
	vboxerr err;

	vbox_hgcm_init_call(req, client_id, SHFL_FN_SET_UTF8, 0);

	if ((err = vbox_hgcm_do_call_sync(vb, req)) < 0)
		return err;

	return req->header.result;
}

static vboxerr vbox_shfl_query_map_info(LPVBOXCOMM vb, hgcm_client_id_t client_id, SHFLROOT root,
                                        SHFLSTRING *name, SHFLSTRING *mountPoint, unsigned *flags, unsigned *version)
{
	VMMDevHGCMCall __far *req = (void __far *) vb->buf;
	vboxerr err;

	vbox_hgcm_init_call(req, client_id, SHFL_FN_QUERY_MAP_INFO, 5);

	// arg 0 in uint32 "root"
	vbox_hgcm_set_parameter_shflroot(req, 0, root);

	// arg 1 inout shflstring "name"
	vbox_hgcm_set_parameter_shflstring(req, 1, name);

	// arg 2 inout shflstring "mountPoint"
	vbox_hgcm_set_parameter_shflstring(req, 2, mountPoint);

	// arg 3 inout uint64 "flags"
	vbox_hgcm_set_parameter_uint64(req, 3, *flags);

	// arg 4 out uint32 "version"
	vbox_hgcm_set_parameter_uint32(req, 4, 0);

	if ((err = vbox_hgcm_do_call_sync(vb, req)) < 0)
		return err;

	*flags = vbox_hgcm_get_parameter_uint64(req, 3);
	*version = vbox_hgcm_get_parameter_uint32(req, 4);

	return req->header.result;
}

static vboxerr vbox_shfl_set_file_size(LPVBOXCOMM vb, hgcm_client_id_t client_id,
                                       SHFLROOT root, SHFLHANDLE handle, unsigned long size)
{
	VMMDevHGCMCall __far *req = (void __far *) vb->buf;
	vboxerr err;

	vbox_hgcm_init_call(req, client_id, SHFL_FN_SET_FILE_SIZE, 3);

	// arg 0 in uint32 "root"
	vbox_hgcm_set_parameter_shflroot(req, 0, root);

	// arg 1 in uint64 "handle"
	vbox_hgcm_set_parameter_shflhandle(req, 1, handle);

	// arg 2 in uint64 "new_size"
	vbox_hgcm_set_parameter_uint64(req, 2, size);

	if ((err = vbox_hgcm_do_call_sync(vb, req)) < 0)
		return err;

	return req->header.result;
}

#endif // VBOXSHFL_H