#include "SCK.H"

/* algorithm from hpfs_stop_cycles in linux/fs/hpfs/super.c */
/* set (*c)[1] = 0 at first time */

static int SPAD_STOP_CYCLES(blk_t key, blk_t (*c)[2])
{
	if (__unlikely((*c)[0] == key) && __unlikely((*c)[1] != 0)) return -1;
	(*c)[1]++;
	if (__likely(!(((*c)[1] - 1) & (*c)[1]))) (*c)[0] = key;
	return 0;
}

static void increase_filename(char filename[256])
{
	char *t = strrchr(filename, '~');
	char *tt;
	if (!t || __unlikely(!t[1])) {
		addsuffix:
		filename[253] = 0;
		strcat(filename, "~1");
		return;
	}
	for (tt = t + 1; *tt; tt++) if (__unlikely(*tt < '0') || __unlikely(*tt > '9')) goto addsuffix;
	while (--tt != t) {
		if (__likely(*tt != '9')) {
			(*tt)++;
			return;
		}
		*tt = '0';
	}
	if (__likely(strlen(filename) < 255)) {
		memmove(tt + 2, tt + 1, strlen(tt/* + 1*/)/* + 1*/);
		tt[1] = '1';
		return;
	}
	if (__likely(tt != filename)) {
		tt[-1] = '~';
		tt[0] = '1';
		return;
	}
	return;
}

static void make_fnode_block(struct fnode_block *f, unsigned dirblks)
{
	unsigned i;
	memset(f, 0, dirblks << SSIZE_BITS);
	for (i = 0; i < dirblks; i++, f = (struct fnode_block *)((char *)f + FNODE_BLOCK_SIZE)) {
		struct fnode *fn;
		f->magic = FNODE_BLOCK_MAGIC;
		f->flags = (FNODE_BLOCK_FIRST * !i) | (FNODE_BLOCK_LAST * (i == dirblks - 1)) | FNODE_BLOCK_CHECKSUM_VALID;
		f->txc = 0x80000000U;
		f->cc = 0;
		fn = f->fnodes;
		fn->next = (FNODE_MAX_SIZE & FNODE_NEXT_SIZE) | FNODE_NEXT_FREE;
		fn->cc = 0;
		fn->txc = 0;
		f->checksum ^= CHECKSUM_BASE ^ __byte_sum(f, FNODE_BLOCK_SIZE);
	}
}

static int lookup(blk_t blk, char *filename, struct fnode **result, blk_t *res_page)
{
	int r;
	__u32 hash = name_hash(filename);
	struct fnode_block *f;
	unsigned fp;
	blk_t c[2];
	*result = NULL;
	c[1] = 0;
	again:
	if (__unlikely(SPAD_STOP_CYCLES(blk, &c))) {
		log_printf(0, "CYCLE DETECTED WHEN LOOKING UP AT BLOCK %"blk_format"X", blk);
		return 1;
	}
	f = read_buffer(blk, 1);
	if (__unlikely(!f)) {
		log_printf(0, "ERROR READING FNODE BLOCK %"blk_format"X: %s", blk, buffer_error);
		return 1;
	}
	if (__unlikely(f->magic == DNODE_PAGE_MAGIC)) {
		unsigned idx;
		blk_t subblk;
		struct dnode_page *dn = read_buffer(blk, dnode_page_sectors);
		struct dnode_page_entry *data;
		int o;
		if (__unlikely(!dn)) {
			log_printf(0, "ERROR READING DNODE %"blk_format"X: %s", blk, buffer_error);
			return 1;
		}
		idx = hash & ((1 << dnode_hash_bits) - 1);
		hash >>= dnode_hash_bits;
		if (cc_valid(dn->cc, dn->txc)) data = (struct dnode_page_entry *)((char *)dn + DNODE_ENTRY_OFFSET), o = 0;
		else data = (struct dnode_page_entry *)((char *)dn + DNODE_ENTRY_OFFSET + dnode_data_size), o = 1;
		subblk = __make64(data[idx].b0 | (data[idx].b1 << 16), data[idx].b2);
		if (__likely(subblk != 0)) {
			blk = subblk;
			goto again;
		}
		if (!res_page) return 0;
		if (__unlikely(r = alloc_space(0, "FNODE BLOCK", 1 << super.sectors_per_fnodepage_bits, (1 << super.sectors_per_fnodepage_bits) - 1, &subblk))) return r;
		data[idx].b0 = subblk;
		data[idx].b1 = subblk >> 16;
		data[idx].b2 = subblk >> 31 >> 1;
		dn->checksum[o] = __byte_sum(data, dnode_data_size) ^ CHECKSUM_BASE;
		write_buffer(blk, dnode_page_sectors, dn);
		f = mem_alloc(1 << SSIZE_BITS << super.sectors_per_fnodepage_bits);
		if (__unlikely(!f)) {
			log_printf(1, "CAN'T ALLOC MEMORY FOR FNODE BLOCK");
			return 2;
		}
		make_fnode_block(f, 1 << super.sectors_per_fnodepage_bits);
		write_buffer(subblk, 1 << super.sectors_per_fnodepage_bits, f);
		mem_free(f);
		blk = subblk;
		goto again;
	}
	if (__unlikely(f->magic != FNODE_BLOCK_MAGIC)) {
		log_printf(0, "BAD MAGIC ON FNODE BLOCK %"blk_format"X: %08X", blk, f->magic);
		return 1;
	}
	if (res_page) *res_page = blk, res_page = NULL;
#define fn ((struct fnode *)((char *)f + fp))
	fp = SIZEOF_FNODE_BLOCK;
	do {
		unsigned size = fn->next & FNODE_NEXT_SIZE;
		if (cc_valid(fn->cc, fn->txc) && fn->next & FNODE_NEXT_FREE) {
			fp += size;
			continue;
		}
		if (__unlikely(size <= FNODE_NAME_POS) || __unlikely(fp + size > FNODE_BLOCK_SIZE) || __unlikely(FNODE_NAME_POS + fn->namelen > size) || __unlikely(!fn->namelen)) {
			log_printf(0, "FNODE BLOCK %"blk_format"X: INVALID FNODE SIZE %X AT %X, NAMELEN %X", blk, fn->next, fp, fn->namelen);
			return 1;
		}
		if (__unlikely(fn->namelen == strlen(filename)) && !_memcasecmp(FNODE_NAME(fn), filename, fn->namelen)) {
			*result = fn;
			return 0;
		}
		fp += size;
	} while (fp < FNODE_BLOCK_SIZE);
#undef fn
	if (!(f->flags & FNODE_BLOCK_LAST)) {
		blk++;
		goto again;
	}
	if (cc_valid(f->cc, f->txc)) {
		blk = __make64(f->next0, f->next1);
		goto again;
	}
	return 0;
}

static int add_entry(blk_t blk, struct fnode *new_fn, char *filename, struct fnode_ea *ea, unsigned ea_size)
{
	int r;
	struct fnode_block *f;
	unsigned fp;
	unsigned wanted_size = FNODE_SIZE(strlen(filename), ea_size);
	blk_t subblk;
	blk_t c[2];
	c[1] = 0;
	again:
	if (__unlikely(SPAD_STOP_CYCLES(blk, &c))) {
		log_printf(0, "CYCLE DETECTED WHEN ADDING ENTRY AT BLOCK %"blk_format"X", blk);
		return 1;
	}
	f = read_buffer(blk, 1);
	if (__unlikely(!f)) {
		read_err:
		log_printf(0, "ERROR READING FNODE BLOCK %"blk_format"X: %s", blk, buffer_error);
		log_printf(0, "THE DEVICE IS UNRELIABLE, FNODE HAS BEEN READ BEFORE");
		return 1;
	}
	if (__unlikely(f->magic != FNODE_BLOCK_MAGIC)) {
		log_printf(0, "BAD MAGIC ON FNODE BLOCK %"blk_format"X: %08X", blk, f->magic);
		log_printf(0, "THE DEVICE IS UNRELIABLE, MAGIC HAS BEEN CORRECT BEFORE");
		return 1;
	}
#define update_block							\
do {									\
	f->flags |= FNODE_BLOCK_CHECKSUM_VALID;				\
	f->checksum ^= CHECKSUM_BASE ^ __byte_sum(f, FNODE_BLOCK_SIZE);	\
	write_buffer(blk, 1, f);					\
	if (__unlikely(!(f = read_buffer(blk, 1)))) goto read_err;	\
} while (0)
#define fn ((struct fnode *)((char *)f + fp))
	fp = SIZEOF_FNODE_BLOCK;
	do {
		unsigned size = fn->next & FNODE_NEXT_SIZE;
		if (cc_valid(fn->cc, fn->txc) && fn->next & FNODE_NEXT_FREE) {
			int need_update = 0;
			next_join:
			fp += size;
			if (fp < FNODE_BLOCK_SIZE && cc_valid(fn->cc, fn->txc) && fn->next & FNODE_NEXT_FREE) {
				unsigned newsize = fn->next & FNODE_NEXT_SIZE;
				fp -= size;
				size += newsize;
				fn->next = (fn->next & ~FNODE_NEXT_SIZE) | (size & FNODE_NEXT_SIZE);
				need_update = 1;
				goto next_join;
			}
			if (size >= wanted_size) {
				fp -= size;
				memset(fn, 0, wanted_size);
				memcpy(fn, new_fn, sizeof(struct fnode));
				fn->next = (fn->next & ~(FNODE_NEXT_SIZE | FNODE_NEXT_FREE)) | wanted_size;
				fn->namelen = strlen(filename);
				memcpy(FNODE_NAME(fn), filename, strlen(filename));
				memcpy((char *)fn + FNODE_EA_POS(fn->namelen), ea, ea_size);
				fp += wanted_size;
				if (wanted_size != size) {
					fn->next = (size - wanted_size) | FNODE_NEXT_FREE;
					fn->cc = 0;
					fn->txc = 0;
				}
				need_update = 2;
			}
			if (need_update) update_block;
			if (need_update == 2) return 0;
			continue;
		}
		fp += size;
	} while (fp < FNODE_BLOCK_SIZE);
	if (!(f->flags & FNODE_BLOCK_LAST)) {
		blk++;
		goto again;
	}
	if (cc_valid(f->cc, f->txc)) {
		blk = __make64(f->next0, f->next1);
		goto again;
	}
	if (__unlikely(r = alloc_space(0, "FNODE BLOCK", 1 << super.sectors_per_block_bits, (1 << super.sectors_per_block_bits) - 1, &subblk))) return r;
	f->next0 = subblk;
	f->next1 = subblk >> 31 >> 1;
	f->cc = 0;
	f->txc = 0;
	update_block;
	f = mem_alloc(1 << SSIZE_BITS << super.sectors_per_block_bits);
	if (__unlikely(!f)) {
		log_printf(1, "CAN'T ALLOC MEMORY FOR FNODE BLOCK");
		return 2;
	}
	make_fnode_block(f, 1 << super.sectors_per_block_bits);
	f->prev0 = blk;
	f->prev1 = blk >> 31 >> 1;
	write_buffer(subblk, 1 << super.sectors_per_block_bits, f);
	mem_free(f);
	blk = subblk;
	goto again;
#undef fn
#undef update_block
}

static int do_mkdir(blk_t blk, char *filename, blk_t *result)
{
	int r;
	struct fnode dir_entry;
	struct fnode_block *f;
	blk_t subblk;
	if (__unlikely(r = alloc_space(0, "FNODE BLOCK", 1 << super.sectors_per_fnodepage_bits, (1 << super.sectors_per_fnodepage_bits) - 1, &subblk))) return r;
	*result = subblk;
	f = mem_alloc(1 << SSIZE_BITS << super.sectors_per_fnodepage_bits);
	if (__unlikely(!f)) {
		log_printf(1, "CAN'T ALLOC MEMORY FOR FNODE BLOCK");
		return 2;
	}
	make_fnode_block(f, 1 << super.sectors_per_fnodepage_bits);
	write_buffer(subblk, 1 << super.sectors_per_fnodepage_bits, f);
	mem_free(f);
	memset(&dir_entry, 0, sizeof dir_entry);
	dir_entry.ctime = time(NULL);
	dir_entry.mtime = time(NULL);
	dir_entry.run10 = subblk;
	dir_entry.run11 = subblk >> 31 >> 1;
	dir_entry.flags = FNODE_FLAGS_DIR;
	if (__unlikely(r = add_entry(blk, &dir_entry, filename, NULL, 0))) {
		if (r != 2) {
			int rr;
			if ((rr = free_blocks(subblk, 1 << super.sectors_per_fnodepage_bits))) return rr;
		}
		return r;
	}
	return 0;
}

int add_fnode_to_dir(char *path, char *filename, struct fnode *new_fnode, struct fnode_ea *ea, unsigned ea_size)
{
	int r;
	blk_t blk;
	char dirname_orig[257];
	char dirname[257];
	int chg_name;
	char *p;
	struct fnode *fn;
	blk_t pg;
	blk = root_dir;
	gosub:
	while (*path == '/') path++;
	if (!*path) goto end_of_path;
	strlcpy(dirname, path, 257);
	if ((p = strchr(dirname, '/'))) {
		*p = 0;
		path += p - dirname;
	} else {
		path = "";
	}
	if (__unlikely(strlen(dirname) == 256)) {
		log_printf(0, "TOO LONG DIRECTORY NAME IN PATH");
		return 1;
	}
	strcpy(dirname_orig, dirname);
	chg_name = 0;
	find_dir_again:
	if (__unlikely(r = lookup(blk, dirname, &fn, &pg))) return r;
	if (__unlikely(!fn)) {
		if (__unlikely(r = do_mkdir(pg, dirname, &blk))) return r;
		if (__unlikely(chg_name)) log_printf(0, "DIRECTORY NAME \"%s\" RENAMED TO \"%s\" BECAUSE OF EXISTING FILE WITH THE SAME NAME", dirname_orig, dirname);
		goto gosub;
	} else {
		if (__unlikely(!(fn->flags & FNODE_FLAGS_DIR))) {
			increase_filename(dirname);
			chg_name = 1;
			goto find_dir_again;
		}
		blk = cc_valid(fn->cc, fn->txc) ? __make64(fn->run10, fn->run11) : __make64(fn->run20, fn->run21);
		goto gosub;
	}
	end_of_path:
	if (__unlikely(strlen(filename) >= 256)) {
		log_printf(0, "TOO LONG FILENAME: %u BYTES", (unsigned)strlen(filename));
		return 1;
	}
	strcpy(dirname, filename);
	chg_name = 0;
	find_file_again:
	if (__unlikely(r = lookup(blk, dirname, &fn, &pg))) return r;
	if (__unlikely(fn != NULL)) {
		increase_filename(dirname);
		chg_name = 1;
		goto find_file_again;
	}
	if (__unlikely(r = add_entry(pg, new_fnode, dirname, ea, ea_size))) return r;
	if (__unlikely(chg_name)) log_printf(0, "%s NAME \"%s\" RENAMED TO \"%s\" BECAUSE OF EXISTING DIRECTORY OR FILE WITH THE SAME NAME", new_fnode->flags & FNODE_FLAGS_DIR ? "DIRECTORY" : "FILE", filename, dirname);
	return 0;
}

int create_fixed_fnode(struct fnode *new_fnode, struct fnode_ea *ea, unsigned ea_size, blk_t *result)
{
	int r;
	struct fixed_fnode_block fx;
	if (__unlikely(r = alloc_space(0, "FIXED FNODE BLOCK", 1 << super.sectors_per_block_bits, (1 << super.sectors_per_block_bits) - 1, result))) return r;
	memset(&fx, 0, sizeof fx);
	fx.magic = FIXED_FNODE_BLOCK_MAGIC;
	fx.flags = 0;
	fx.cc = 0;
	fx.txc = 0;
	fx.nlink0 = 1;	/* will be updated later */
	memcpy(fx.fnode0, new_fnode, sizeof(struct fnode));
	((struct fnode *)&fx.fnode0)->next = FNODE_SIZE(0, ea_size);
	((struct fnode *)&fx.fnode0)->namelen = 0;
	memcpy((char *)fx.fnode0 + FNODE_EA_POS(0), ea, ea_size);
	fx.checksum ^= CHECKSUM_BASE ^ __byte_sum(&fx, FIXED_FNODE_BLOCK_SIZE);
	if (__unlikely(!update_fixed_fnode_block(&fx, *result))) {
		if (free_blocks(*result, 1 << super.sectors_per_block_bits) == 2) return 2;
		return 1;
	}
	return 0;
}
