/*
 * Copyright (c) 2020, Alexey Melnikov
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * * Redistributions of source code must retain the above copyright notice, this
 *   list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include <stdbool.h>
#include <fcntl.h>
#include <sys/statvfs.h>
#include <time.h>

#include <map>
#include <string>
#include <vector>

#include "../../hardware.h"
#include "../../user_io.h"
#include "../../file_io.h"
#include "../../cfg.h"
#include "../../shmem.h"

#define SHMEM_ADDR      0x300CE000
#define SHMEM_SIZE      0x2000
static uint8_t *shmem = 0;

#define REQUEST_FLG     0
#define REQUEST_BUFFER  4
#define HDRLEN          8
#define DATA_BUFFER     (REQUEST_BUFFER+66)

//#define DEBUG

#ifdef DEBUG
	#define dbg_print printf
	#define dbg_hexdump hexdump
#else
	#define dbg_print(x,...) void()
	#define dbg_hexdump(x,...) void()
#endif

enum AL_SUBFUNCTIONS {
	AL_RMDIR      = 0x01,
	AL_MKDIR      = 0x03,
	AL_CHDIR      = 0x05,
	AL_CLOSE      = 0x06,
	AL_READ       = 0x08,
	AL_WRITE      = 0x09,
	AL_LOCK       = 0x0A,
	AL_DISKSPACE  = 0x0C,
	AL_SETATTR    = 0x0E,
	AL_GETATTR    = 0x0F,
	AL_RENAME     = 0x11,
	AL_DELETE     = 0x13,
	AL_OPEN       = 0x16,
	AL_CREATE     = 0x17,
	AL_FINDFIRST  = 0x1B,
	AL_FINDNEXT   = 0x1C,
	AL_SKFMEND    = 0x21,
	AL_QUALIFY    = 0x23,
	AL_SPOPEN     = 0x2E,
	AL_UNKNOWN    = 0xFF
};

#define FAT_RO   1
#define FAT_HID  2
#define FAT_SYS  4
#define FAT_VOL  8
#define FAT_DIR 16
#define FAT_ARC 32
#define FAT_DEV 64

static char basepath[1024] = {};
static int baselen = 0;

struct dir_item_t
{
	dirent64 de;
	stat64 st;
};

struct lock
{
	uint16_t token;
	std::vector<dir_item_t> dir_items;
};

static std::map<short, lock> locks;
static short next_key = 0;

static short get_key()
{
	short key;

	do
	{
		next_key++;
		if (!next_key) next_key++;
		key = next_key;

	} while (locks.find(key) != locks.end());

	return key;
}

static short get_lock(const uint16_t token)
{
	for (const auto &pair : locks)
	{
		if (pair.second.token == token)
		{
			dbg_print("! token %u has lock: %d\n", token, pair.first);
			return pair.first;
		}
	}
	return 0;
}

static short add_lock(const uint16_t token)
{
	short key = get_lock(token);
	if (key)
	{
		locks[key].dir_items.clear();
	}
	else
	{
		key = get_key();
		locks[key] = { token, {} };
		dbg_print("+ add lock: %d, %u\n", key, token);
	}
	return key;
}

static std::map<short, fileTYPE> open_file_handles;
static short next_fp = 1;

static short get_fp()
{
	short fp;

	do
	{
		next_fp++;
		if (!next_fp) next_fp++;
		fp = next_fp;

	} while (open_file_handles.find(fp) != open_file_handles.end());

	return fp;
}

static char* find_path(const char *name)
{
	dbg_print("find_path(%s)\n", name);

	static char str[1024] = {};
	const char* p = strchr(name, ':');
	if (p)
	{
		name = p + 1;
	}

	strcpy(str, basepath);
	if (strlen(name))
	{
		strcat(str, "/");
		strcat(str, name);
	}

	char *bsl;
	while ((bsl = strchr(str, '\\'))) *bsl = '/';

	dbg_print("Requested path: %s\n", str);

	if (strncmp(basepath, str, baselen))
	{
		dbg_print("Not belonging to shared folder\n");
		str[0] = 0;
	}
	else if (str[baselen] && str[baselen] != '/')
	{
		dbg_print("No / after root\n");
		str[0] = 0;
	}
	else if (str[baselen])
	{
		char *cur = str + baselen;
		while (*cur)
		{
			cur++;
			char *next = strchr(cur, '/');
			if (!next) next = cur + strlen(cur);
			int len = next - cur;

			if (!len && !*next) break;

			if (!len || !strncmp(cur, ".", len))
			{
				strcpy(cur, next + 1);
				cur--;
				continue;
			}

			if (!strncmp(cur, "..", len))
			{
				cur -= 2;
				while (*cur != '/') cur--;

				if (cur < str + baselen)
				{
					printf("Going above root\n");
					str[0] = 0;
					break;
				}

				// collapse the component
				strcpy(cur, next);
				continue;
			}

			cur = next;
		}
	}

	// remove trailing /
	int len = strlen(str);
	if (len && str[len - 1] == '/') str[len - 1] = 0;

	dbg_print("Converted path: %s\n", str);

	if (str[0])
	{
		char *p = strrchr(str, '/');
		if (!p) str[0] = 0;
		else
		{
			*p = 0;
			if (!PathIsDir(str, 0)) str[0] = 0;
			else *p = '/';
		}
	}

	dbg_print("returned path: %s\n", str);
	return str;
}

static void __attribute__((noinline)) memcpyb(void *dst, const void *src, int len)
{
	char *d = (char*)dst;
	char *s = (char*)src;
	while (len--) *d++ = *s++;
}


// | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | Directory Attribute Flags
//   |   |   |   |   |   |   |   \-- 1 = read only
//   |   |   |   |   |   |   \--- 1 = hidden
//   |   |   |   |   |   \---- 1 = system
//   |   |   |   |   \----- 1 = volume label(exclusive)
//   |   |   |   \------ 1 = subdirectory
//   |   |   \------- 1 = archive
//   \---+-------unused

static uint32_t get_attr(char *path, uint16_t *time, uint16_t *date, uint32_t *size)
{
	if (time) *time = 0;
	if (date) *date = 0;
	if (size) *size = 0;

	stat64 *st = getPathStat(path);
	if (!st) return 0;

	tm *t = localtime(&st->st_mtime);
	if (time) *time = (t->tm_sec / 2) | (t->tm_min << 5) | (t->tm_hour << 11);
	if (date) *date = t->tm_mday | ((t->tm_mon + 1) << 5) | ((t->tm_year - 80) << 9);
	if (size) *size = st->st_size;
	return st->st_mode;
}

static void name83(const char *src, char *dst)
{
	int namelen = 0;
	int extlen = 0;

	const char *p = strrchr(src, '/');
	if (p) src = p + 1;

	if (!strcmp(src, ".") || !strcmp(src, ".."))
	{
		namelen = strlen(src);
	}
	else
	{
		p = strrchr(src, '.');
		if (!p) namelen = strlen(src);
		else
		{
			namelen = p - src;
			extlen = strlen(src) - namelen - 1;
		}
	}


	char ext[4] = { ' ', ' ', ' ', 0 };
	if (p) memcpy(ext, p + 1, extlen);
	for (int i = 0; i < namelen; i++) dst[i] = toupper(src[i]);
	while (namelen < 8) dst[namelen++] = ' ';
	for (int i = 0; i < 3; i++) dst[8 + i] = toupper(ext[i]);
}

static int cmp_name(const char *name, const char *flt)
{
	int namelen = 0;
	int extlen = 0;

	const char *ext = strrchr(name, '.');
	if (!ext)
	{
		namelen = strlen(name);
		ext = name + namelen;
	}
	else
	{
		namelen = ext - name;
		ext++;
		extlen = strlen(ext);
	}

	if (namelen > 8 || extlen > 3) return 0;

	char testname[16];
	char fltname[16];
	name83(name, testname);
	name83(flt, fltname);

	testname[11] = 0;
	fltname[11] = 0;

	char *cmpname = fltname;
	char *cmpend = fltname + 8;
	char *cur = testname;

	while (cmpname < cmpend)
	{
		if (*cmpname == '?')
		{
			cmpname++;
			cur++;
		}
		else if (*cmpname == '*')
		{
			break;
		}
		else if (*cmpname++ != *cur++)
		{
			return 0;
		}
	}

	cmpname = fltname + 8;
	cmpend = fltname + 11;
	cur = testname + 8;

	while (cmpname < cmpend)
	{
		if (*cmpname == '?')
		{
			cmpname++;
			cur++;
		}
		else if (*cmpname == '*')
		{
			break;
		}
		else if (*cmpname++ != *cur++)
		{
			return 0;
		}
	}

	return 1;
}

static int process_request(void *reqres_buffer)
{
	static char str[1024];
	int len = *(unsigned short*)reqres_buffer;
	char func = ((char*)reqres_buffer)[4];

	dbg_hexdump(reqres_buffer, 8, 0);
	if(func != AL_WRITE) dbg_hexdump((char*)reqres_buffer+8, len, 8);

	short res = -1;
	short reslen = 0;

	unsigned short idx = 0;
	short key = 0;

	if (!baselen)
	{
		if (strlen(cfg.shared_folder))
		{
			if (cfg.shared_folder[0] == '/') strcpy(basepath, cfg.shared_folder);
			else
			{
				strcpy(basepath, HomeDir());
				strcat(basepath, "/");
				strcat(basepath, cfg.shared_folder);
			}
		}
		else
		{
			strcpy(basepath, HomeDir());
			strcat(basepath, "/shared");
		}

		baselen = strlen(basepath);
		if (baselen && basepath[baselen - 1] == '/')
		{
			basepath[baselen - 1] = 0;
			baselen--;
		}

		if (baselen) FileCreatePath(basepath);
	}

	char *buf = ((char*)reqres_buffer) + 8;
	buf[len] = 0;

	switch (func)
	{
	case AL_RMDIR:
	{
		dbg_print("> AL_RMDIR\n");

		char *path = find_path(buf);
		if (!*path)
		{
			res = 3;
			break;
		}

		if(!DirDelete(path))
		{
			dbg_print("Cannot delete dir %s\n", path);
			res = 29;
			break;
		}

		res = 0;
	}
	break;

	case AL_MKDIR:
	{
		dbg_print("> AL_MKDIR\n");

		char *path = find_path(buf);
		if (!*path)
		{
			res = 3;
			break;
		}

		if (!FileCreatePath(path))
		{
			res = 29;
			break;
		}

		res = 0;
	}
	break;

	case AL_CHDIR:
	{
		dbg_print("> AL_CHDIR\n");

		char *path = find_path(buf);
		if (!*path || !PathIsDir(path))
		{
			res = 3;
			break;
		}

		res = 0;
	}
	break;

	case AL_OPEN:
	{
		dbg_print("> AL_OPEN\n");

		uint16_t attr = *(uint16_t *)buf;
		char *path = find_path(buf + 6);
		if (!*path)
		{
			res = 3;
			break;
		}

		if (!FileExists(path, 0))
		{
			res = 2;
			break;
		}

		int mode = attr & 3;

		short key = get_fp();
		open_file_handles[key] = {};

		if (!FileOpenEx(&open_file_handles[key], path, mode, 0, 0))
		{
			open_file_handles.erase(key);
			res = 5;
			break;
		}

		dbg_print("opened handle: %d\n", key);

		*buf++ = 0;
		name83(path, buf);
		buf += 11;
		get_attr(path, (uint16_t*)buf, (uint16_t*)(buf + 2), (uint32_t*)(buf + 4));
		buf += 8;
		*buf++ = key;
		*buf++ = key >> 8;
		*buf++ = 0;
		*buf++ = 0;
		*buf++ = mode;

		/*
	   Bit 0-2 access mode
		   000=read
		   001=write
		   010=read/write
	   4-6 sharing mode
		   000=compatibility
		   001=deny read/write
		   010=deny write
		   011=deny read
		   100=deny none
		13 critical error handling
		   0=execute INT 24
		   1=return error code
		14 buffering
		   0=buffer writes
		   1=don't buffer writes
		15 1=FCB SFT
		*/
		reslen = 25;
		res = 0;
	}
	break;

	case AL_CREATE:
	{
		dbg_print("> AL_CREATE\n");

		char *path = find_path(buf + 6);
		if (!*path)
		{
			res = 3;
			break;
		}

		int mode = O_RDWR | O_CREAT | O_TRUNC;

		short key = get_fp();
		open_file_handles[key] = {};

		if (!FileOpenEx(&open_file_handles[key], path, mode, 0, 0))
		{
			open_file_handles.erase(key);
			res = 5;
			break;
		}

		dbg_print("opened handle: %d\n", key);

		*buf++ = 0;
		name83(path, buf);
		buf += 11;
		get_attr(path, (uint16_t*)buf, (uint16_t*)(buf + 2), (uint32_t*)(buf + 4));
		buf += 8;
		*buf++ = key;
		*buf++ = key >> 8;
		*buf++ = 0;
		*buf++ = 0;
		*buf++ = 2;

		reslen = 25;
		res = 0;
	}
	break;

	case AL_SPOPEN:
	{
		dbg_print("> AL_SPOPEN\n");

		/* actioncode contains instructions about how to behave...
		 *   high nibble = action if file does NOT exist:
		 *     0000 fail
		 *     0001 create
		 *   low nibble = action if file DOES exist
		 *     0000 fail
		 *     0001 open
		 *     0010 truncate/open */
		uint16_t attr = *(uint16_t *)buf;
		uint16_t actioncode = *(uint16_t *)(buf + 2);
		uint16_t openmode = *(uint16_t *)(buf + 4);

		char *path = find_path(buf + 6);
		if (!*path)
		{
			res = 3;
			break;
		}

		int mode = openmode & 0x3;
		uint16_t spopres = 0;

		if (FileExists(path, 0))
		{
			if ((actioncode & 0xF) == 1)
			{
				spopres = 1;
			}
			else if ((actioncode & 0xF) == 2)
			{
				mode = O_RDWR | O_TRUNC;
				spopres = 3;
			}
			else
			{
				res = 5;
				break;
			}
		}
		else
		{
			// some copiers create an empty file with hidden attribute and then fail if found non-hidden file
			// so prohibit to create a hidden file.
			if ((actioncode & 0xF0) == 0x10 && !(attr & FAT_HID))
			{
				mode = O_RDWR | O_CREAT;
				spopres = 2;
			}
			else
			{
				res = 2;
				break;
			}
		}

		key = get_fp();
		open_file_handles[key] = {};

		if (!FileOpenEx(&open_file_handles[key], path, mode, 0, 0))
		{
			open_file_handles.erase(key);
			res = 5;
			break;
		}

		dbg_print("opened handle: %d\n", key);

		*buf++ = 0;
		name83(path, buf);
		buf += 11;
		get_attr(path, (uint16_t*)buf, (uint16_t*)(buf + 2), (uint32_t*)(buf + 4)); // 12 14 16
		buf += 8;
		*buf++ = key;
		*buf++ = key >> 8;
		*buf++ = spopres;
		*buf++ = spopres >> 8;
		*buf++ = openmode & 0x7f;

		reslen = 25;
		res = 0;
	}
	break;

	case AL_CLOSE:
	{
		dbg_print("> AL_CLOSE\n");

		key = *(short *)buf;
		if (open_file_handles.find(key) != open_file_handles.end())
		{
			FileClose(&open_file_handles[key]);
			open_file_handles.erase(key);

			dbg_print("closed handle: %d\n", key);
		}

		reslen = 0;
		res = 0;
	}
	break;

	case AL_READ:
	{
		dbg_print("> AL_READ\n");

		key = buf[4] | (buf[5] << 8);
		if (open_file_handles.find(key) == open_file_handles.end())
		{
			res = 5;
			break;
		}

		uint32_t off;
		memcpyb(&off, buf, 4);
		uint16_t sz = buf[6] | (buf[7] << 8);
		dbg_print("  read %d bytes at %d\n", sz, off);

		FileSeek(&open_file_handles[key], off, SEEK_SET);

		int read = FileReadAdv(&open_file_handles[key], buf, sz, -1);
		if (read < 0)
		{
			res = 5;
			break;
		}

		dbg_print("  was read %d\n", read);

		reslen = read;
		res = 0;
	}
	break;

	case AL_WRITE:
	{
		dbg_print("> AL_WRITE\n");

		key = buf[4] | (buf[5] << 8);
		if (open_file_handles.find(key) == open_file_handles.end())
		{
			res = 5;
			break;
		}

		uint32_t off;
		memcpyb(&off, buf, 4);
		uint16_t sz = buf[6] | (buf[7] << 8);
		dbg_print("  write %d bytes at %d\n", sz, off);

		FileSeek(&open_file_handles[key], off, SEEK_SET);

		int written = 0;
		if (sz)
		{
			written = FileWriteAdv(&open_file_handles[key], buf + 8, sz);
			if (!written)
			{
				res = 5;
				break;
			}
		}

		dbg_print("  written %d\n", written);

		*buf++ = written;
		*buf++ = written >> 8;

		reslen = 2;
		res = 0;
	}
	break;

	case AL_LOCK:
	{
		dbg_print("> AL_LOCK\n");

		reslen = 0;
		res = 0;
	}
	break;

	case AL_DISKSPACE:
	{
		dbg_print("> AL_DISKSPACE\n");
		uint32_t total = 10;
		uint32_t avail = 1;

		// Set the sector size to the maximum size supported by FAT16:
		//   1024 MB - 2047 MB: 64 Sectors/Cluster, 32K Cluster Size
		//
		// This allows for MS-DOS version that only support up to 64
		// Sectors/Cluster to display a non-zero value for disk space remaning.
		uint32_t spc = 64;	// Sectors/Cluster (normally based on partition size)
		uint32_t bps = 512;	// Bytes/Sector (this value is not adjusted)

		struct statvfs st;
		if (!statvfs(getFullPath(basepath), &st))
		{
			// Calculate the size available and remaining as reported from Linux
			uint64_t sz = st.f_bsize * st.f_blocks;
			uint64_t av = st.f_bsize * st.f_bavail;
			// Convert those values into cluster sizes.
			total = sz / (bps * spc);
			avail = av / (bps * spc);
		}

		// Limit the total and available sizes to the maximum supported size
		// based on the cluster size. (In our case 2GB at 32KB clusters)
		if (total > UINT16_MAX) total = UINT16_MAX;
		if (avail > UINT16_MAX) avail = UINT16_MAX;

		*buf++ = total;
		*buf++ = total >> 8;
		*buf++ = bps;
		*buf++ = bps >> 8;
		*buf++ = avail;
		*buf++ = avail >> 8;

		reslen = 6;
		res = spc;
	}
	break;

	case AL_SETATTR:
	{
		dbg_print("> AL_SETATTR\n");
		reslen = 0;
		res = 0;
	}
	break;

	case AL_GETATTR:
	{
		dbg_print("> AL_GETATTR\n");

		char *path = find_path(buf);
		if (!*path)
		{
			res = 2;
			break;
		}

		uint16_t time, date;
		uint32_t sz;
		uint32_t mode = get_attr(path, &time, &date, &sz);

		if (!mode)
		{
			res = 2;
			break;
		}

		*buf++ = time;
		*buf++ = time >> 8;
		*buf++ = date;
		*buf++ = date >> 8;
		*buf++ = sz;
		*buf++ = sz >> 8;
		*buf++ = sz >> 16;
		*buf++ = sz >> 24;
		*buf++ = (mode & S_IFDIR) ? FAT_DIR : 0;
		*buf++ = 0;

		res = 0;
		reslen = 10;
	}
	break;

	case AL_RENAME:
	{
		dbg_print("> AL_RENAME\n");

		int srclen = (*buf++) & 0xFF;
		char *path = find_path(buf + srclen);
		if (!*path)
		{
			res = 3;
			break;
		}
		strcpy(str, getFullPath(path));

		buf[srclen] = 0;
		path = find_path(buf);
		if (!*path)
		{
			res = 2;
			break;
		}

		if (rename(getFullPath(path), str))
		{
			res = 5;
			break;
		}

		res = 0;
	}
	break;

	case AL_DELETE:
	{
		dbg_print("> AL_DELETE\n");

		char *path = find_path(buf);
		if (!*path)
		{
			res = 3;
			break;
		}

		if(strchr(path, '?') || strchr(path, '*'))
		{
			res = 2;
			break;
		}

		if (!FileDelete(path))
		{
			res = 2;
			break;
		}

		res = 0;
	}
	break;

	case AL_FINDFIRST:
	{
		dbg_print("> AL_FINDFIRST\n");

		const uint16_t token = ((uint16_t *)buf)[0];

		char attr = buf[2];

		char *path = find_path(buf+3);
		if (!*path)
		{
			res = 0x12;
			break;
		}

		char *flt = strrchr(path, '/');
		if (!*flt)
		{
			res = 0x12;
			break;
		}

		*flt++ = 0;
		key = add_lock(token);

		const char* full_path = getFullPath(path);
		DIR *d = opendir(full_path);
		if (!d)
		{
			locks.erase(key);
			printf("Couldn't open dir: %s\n", full_path);
			res = 0x12;
			break;
		}

		if (attr == 8)
		{
			struct dirent64 de = {};
			strcpy(de.d_name, "MiSTer");
			locks[key].dir_items.push_back({ de, {} });

			*buf++ = 8;
			memcpyb(buf, "MiSTer     ", 11);

			buf += 11;
			*buf++ = 0; *buf++ = 0; // time;
			*buf++ = 0; *buf++ = 0; // date;
			*buf++ = 0; *buf++ = 0; *buf++ = 0; *buf++ = 0; // size;
			*buf++ = key;
			*buf++ = key >> 8;
			*buf++ = 0; *buf++ = 0;

			res = 0;
			reslen = 24;
			break;
		}
		else
		{
			struct dirent64 de, *de2;
			while ((de2 = readdir64(d)))
			{
				if (de2->d_type == DT_REG || (attr & FAT_DIR))
				{
					memcpy(&de, de2, sizeof(dirent64));
					sprintf(str, "%s/%s", path, de.d_name);
					stat64 *st = getPathStat(str);

					if (st && cmp_name(de.d_name, flt))
					{
						name83(de2->d_name, de.d_name);
						de.d_name[11] = 0;
						locks[key].dir_items.push_back({ de, *st });
					}
				}
			}
			closedir(d);
		}
	}
	// fall through

	case AL_FINDNEXT:
	{
		if (func == AL_FINDNEXT)
		{
			dbg_print("> AL_FINDNEXT\n");

			key = *(short *)buf;
			idx = *(short *)(buf+2);
			idx++;

			if (locks.find(key) == locks.end())
			{
				dbg_print("Key %d not found\n", key);
				res = 0x12;
				break;
			}
		}

		if (idx >= locks[key].dir_items.size())
		{
			if (locks.find(key) != locks.end()) locks.erase(key);

			dbg_print("No more items\n");
			res = 0x12;
			break;
		}

		*buf++ = (locks[key].dir_items[idx].de.d_type == DT_DIR) ? FAT_DIR : 0;
		memcpyb(buf, locks[key].dir_items[idx].de.d_name, 11);
		buf += 11;

		tm *t = localtime(&locks[key].dir_items[idx].st.st_mtime);
		uint16_t time = (t->tm_sec / 2) | (t->tm_min << 5) | (t->tm_hour << 11);
		uint16_t date = t->tm_mday | ((t->tm_mon + 1) << 5) | ((t->tm_year - 80) << 9);

		*buf++ = time;
		*buf++ = time >> 8;
		*buf++ = date;
		*buf++ = date >> 8;

		memcpyb(buf, &locks[key].dir_items[idx].st.st_size, 4);
		buf += 4;
		*buf++ = key;
		*buf++ = key >> 8;
		*buf++ = idx;
		*buf++ = idx >> 8;

		res = 0;
		reslen = 24;
	}
	break;

	case AL_SKFMEND:
	{
		dbg_print("> AL_SKFMEND\n");

		key = buf[4] | (buf[5] << 8);
		if (open_file_handles.find(key) == open_file_handles.end())
		{
			res = 2;
			break;
		}

		int32_t off;
		memcpyb(&off, buf, 4);

		int32_t sz = open_file_handles[key].size;
		off += sz;
		if (off < 0) off = 0;

		memcpyb(buf, &off, 4);

		res = 0;
		reslen = 4;
	}
	break;

	case AL_QUALIFY:
	{
		dbg_print("> AL_QUALIFY\n");

		res = 0;
		reslen = 128;
	}
	break;

	}

	((short *)reqres_buffer)[0] = reslen;
	((short *)reqres_buffer)[2] = res;
	dbg_print("result %d, %d:\n", reslen, res);
	dbg_hexdump(reqres_buffer, 8, 0);
	if (reslen > 0 && func != AL_READ)
	{
		dbg_hexdump((char*)reqres_buffer + 8, reslen, 8);
	}
	return reslen;
}

void x86_share_poll()
{
	if (!shmem)
	{
		shmem = (uint8_t *)shmem_map(SHMEM_ADDR, SHMEM_SIZE);
		if (!shmem) shmem = (uint8_t *)-1;
	}
	else if (shmem != (uint8_t *)-1)
	{
		static uint32_t old_req_id = 0;
		uint32_t req_id = *(uint32_t*)(shmem + REQUEST_FLG);

		if ((uint16_t)old_req_id != (uint16_t)req_id)
		{
			dbg_print("\nnew req: %08X\n", req_id);
			old_req_id = req_id;

			if (((req_id >> 16) & 0xFFFF) == 0xA55A && ((req_id + 77) & 0xFF) == ((req_id >> 8) & 0xFF))
			{
				process_request(shmem + REQUEST_BUFFER);
				*(uint16_t*)(shmem + REQUEST_FLG + 2) = (uint16_t)req_id;
			}
		}
	}
}

void x86_share_reset()
{
	open_file_handles.clear();
	locks.clear();
	next_fp = 1;
	next_key = 1;
}
