#ifndef AD3_TERNARYTRIE
#define AD3_TERNARYTRIE

#define ALPHABET_SIZE 256
#define DELIMITER '\0'
#define MAX(x, y) (((x) > (y)) ? (x) : (y))

// Should not be higher than 254 or stuff will break
#define TRIE_MAX_SKIP_SIZE 8

/**
 * The implementation of a Ternary Trie.
 *
 * Each node should be represented by a binary tree in order to reduce the
 * memory usage.
 */

#include <stdbool.h>
#include <stddef.h>
#include <string.h>

const static char charset[] =
    "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
const static size_t charset_len = sizeof(charset) - 1;

// Length of randomly generated keys
#define RANDOM_KEY_LENGTH_SHORT 4
#define RANDOM_KEY_LENGTH_LONG 16

/**
 * Type definition for the struct representing the current Trie.
 *
 * You can (and should) redefine this in your c-file with the concrete fields.
 */
typedef struct ttrie Trie;

typedef enum entry_type { Redirect, Paste, Unknown } EntryType;

typedef struct entry {
  EntryType type;
  char *string;
} Entry;

typedef enum trie_exit_code {
  Ok = 0,
  NotFound,
  AlreadyPresent,
  FileError
} TrieExitCode;

Entry *entry_new(EntryType type, const char *string);

/**
 * Allocate & initialize a new trie, and populate it with the data from the
 * given data file.
 *
 * @return 0 if everything was successful, non-zero otherwise
 */
TrieExitCode trie_init(Trie **trie_ptr, const char *file_path);

/**
 * De-allocate a trie by freeing the memory occupied by this trie.
 *
 * @param trie which should be freed
 */
void trie_free(Trie *trie);

/**
 * Search for an entry in the trie.
 *
 * @param trie
 * @param entry_ptr pointer to Entry will be stored here, if found
 * @param key key representing the entry
 * @return 0 if the search was successful, 1 if not found
 */
TrieExitCode trie_search(Trie *trie, Entry **entry_ptr, const char *key);

/**
 * Add a string to this trie.
 *
 * @param trie
 * @param key key to represent entry with
 * @param entry entry to add
 * @return 0 if added, 1 if already in trie, something else if other errors
 */
TrieExitCode trie_add(Trie *trie, const char *key, Entry *entry);

/**
 * Add an entry by generating a random string as the key.
 *
 * @param trie
 * @param entry entry to add
 * @param secure whether to generate a longer, more secure random key
 * @return pointer to the generated key. This pointer is safe to use after
 * unlocking the trie, and should be freed manually.
 */
TrieExitCode trie_add_random(Trie *trie, char **key_ptr, Entry *entry,
                             bool secure);

/**
 * Remove an entry from this trie given its key.
 *
 * @param trie
 * @param key key representing entry
 * @return true if the entry was present and has been removed, false if it was
 * not present
 */
bool trie_remove(Trie *trie, const char *key);

/**
 * Returns the number of entries in this trie.
 *
 * @param trie
 * @return the number of entries in this trie
 */
size_t trie_size(Trie *trie);

/*
 * Acquire a read lock on the trie.
 *
 * @return 0 if successful, non-zero otherwise (return value of
 * pthread_rwlock_rdlock)
 */
int trie_rlock(Trie *trie);

/*
 * Acquire a write lock on the trie.
 *
 * @return 0 if successful, non-zero otherwise (return value of
 * pthread_rwlock_wrlock)
 */
int trie_wlock(Trie *trie);

/*
 * Release the lock on a trie after having acquired it beforehand.
 *
 * @return 0 if successful, non-zero otherwise (return value of
 * pthread_rwlock_unlock)
 */
int trie_unlock(Trie *trie);

#endif // AD3_TERNARYTRIE