/* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only */
/* Copyright (c) 2023 Brett Sheffield <bacs@librecast.net> */

/* mdex.h  - Librecast multicast indexing API */

#ifndef _MDEX_H
#define _MDEX_H 1

#include <librecast/types.h>
#include <librecast/crypto.h>

#define MDEX_STACK_SZ 4096
#define MDEX_DEFAULT_DEPTH -1 /* default depth for recursive indexing -1 = no limit */

/* these two sets of flags must not conflict as they are sometimes combined */
enum {
	/* flags for mdex entry type (internal use only) */
	MDEX_NONE    = 0x00000000,
	MDEX_PTR     = 0x00000001, /* entry refers to bytes in memory */
	MDEX_FILE    = 0x00000002, /* entry refers to bytes in a file */
	MDEX_DIR     = 0x00000004, /* entry refers to a directory */
	MDEX_LINK    = 0x00000008, /* entry refers to a symlink */

	/* API flags for mdex_add* etc. */
	MDEX_ALIAS   = 0x00010000, /* pointer to another entry */
	MDEX_OTI     = 0x00020000, /* entry will be sent with OTI header */
	MDEX_RAND    = 0x00040000, /* FEC: random ESIs */
	MDEX_RECURSE = 0x00080000, /* Recursively index */
	MDEX_MOUNT   = 0x00100000, /* Do not cross mount points */
	MDEX_SYMLINK = 0x00200000, /* Follow symbolic links when recursively indexing */
};

enum {
	MDEX_DEBUG_FILE = 0x00000001, /* print indexed files to mdex->stream */
};

typedef unsigned char mdex_hash_t[HASHSIZE];

typedef struct mdex_filemeta {
	mdex_hash_t *dir;	   /* pointer to directory data */
	char *name;                /* pointer to NUL-terminated path */
	struct stat *sb;           /* pointer to stat buffer */
	unsigned int ref;          /* reference count */
} mdex_filemeta_t;

struct mdex_mem_s {
	size_t size;
	void * data;
};

struct mdex_file_s {
	size_t size;
	size_t off;
	mdex_filemeta_t file;
};

typedef struct mdex_stack_s {
	size_t ptr;
	size_t len;
	void ** stack;
} mdex_stack_t;

struct mdex_s {
	/* Head of list. Probably also the root of the tree unless we start balancing operations */
	mdex_idx_t *head;
	/* root of tree */
	mdex_idx_t *root;
	/* next available index slot */
	mdex_idx_t *next;
	/* last available index slot */
	mdex_idx_t *last;
	/* head of entry list */
	mdex_entry_t *head_entry;
	/* next available entry slot */
	mdex_entry_t *next_entry;
	/* to track allocations and reusable (deleted) idx + entries */
	mdex_stack_t alloc_stack;
	mdex_stack_t idx_stack;
	mdex_stack_t entry_stack;
	/* debug settings */
	size_t files; /* number of files/directories in index */
	FILE *stream;
	int debug;
	/* root directory of mdex (will be trimmed from file aliases in index) */
	char *basedir;
	size_t basedirlen;
	char *rootdir;
	size_t rootdirlen;
	/* how many entries we have allocated space for */
	size_t entries;
	/* page size in bytes */
	size_t pagesz;
	int maxdepth; /* maximum depth for recursive indexing */
};

/* when HASH_SIZE=32, each struct fits into a 64 byte cache line */
struct mdex_idx_s {
	unsigned char hash[HASHSIZE];
	mdex_idx_t *clown; /* With clowns to the left of me */
	mdex_idx_t *joker; /* And jokers to the right */
	mdex_entry_t *entry; /* Here I am, stuck in the middle with you */
	mdex_idx_t *next;
};

struct mdex_entry_s {
	int type;
	union {
		struct mdex_mem_s   ptr;
		struct mdex_file_s file;
	};
	mdex_idx_t *idx;
	mdex_entry_t *next;
};

mdex_t *mdex_init(size_t entries);
void mdex_free(mdex_t *mdex);
int mdex_add(mdex_t *mdex, void *data, size_t len, q_t *q, int flags);
int mdex_addfile(mdex_t *mdex, const char *path, q_t *q, int flags);
int mdex_get(mdex_t *mdex, unsigned char *hash, size_t hashlen, mdex_entry_t *entry);
int mdex_getalias(mdex_t *mdex, const char *path, mdex_entry_t *entry);
void mdex_aliashash(const char *path, unsigned char *hash, size_t hashlen);
int mdex_put(mdex_t *mdex, unsigned char *hash, size_t hashlen, mdex_entry_t *entry);
int mdex_del(mdex_t *mdex, unsigned char *hash, size_t hashlen);
int mdex_alias(mdex_t *mdex, unsigned char *alias, size_t aliaslen,
		unsigned char *hash, size_t hashlen);

char *mdex_sharepath(mdex_t *mdex, const char *restrict path);

/* create hash of mtree root with key n
 * used to index mtree in several parts, and to distinguish the root node from
 * the first data node when nodes = 1 */
int mdex_tree_hash(unsigned char *hash, size_t hashlen, mtree_t *mtree, size_t n);
void mdex_tree_hash_sb(unsigned char *hash, size_t hashlen, mtree_t *mtree, size_t n, struct stat *sb,
		const char *sharepath);
void mdex_hash_entry(unsigned char *hash, size_t hashlen, const char *path, struct stat *sb,
                mdex_hash_t *dir, int entries);

/* find root hash for dir in mdex */
int mdex_directory_root(mdex_t *mdex, const char *dir, unsigned char *root);

/* calculate root hash for directory */
int mdex_get_directory_root(const char *dir, unsigned char *root);

#endif /* _MDEX_H */
