/*
 * Copyright 2024 Vsevolod Stakhov
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef SRC_LIBUTIL_MULTIPATTERN_H_
#define SRC_LIBUTIL_MULTIPATTERN_H_

#include "config.h"

/**
 * @file multipattern.h
 *
 * This file defines structure that acts like a transparent bridge between
 * hyperscan and ac-trie
 */

#ifdef __cplusplus
extern "C" {
#endif

struct ev_loop;

/**
 * Maximum reentrancy depth of rspamd_multipattern_lookup() on a single
 * multipattern. Each multipattern keeps a small stack of hyperscan scratch
 * contexts of this depth, so a lookup may be re-entered (from within a match
 * callback) up to this many levels deep before the scratch pool is exhausted.
 * Callers that recurse into the same multipattern from their callback (e.g. URL
 * query unwrapping, which re-scans an embedded URL while the enclosing scan is
 * still on the stack) MUST keep their total nesting at or below this bound.
 */
#define RSPAMD_MULTIPATTERN_MAX_REENTRANCY 10

enum rspamd_multipattern_flags {
	RSPAMD_MULTIPATTERN_DEFAULT = 0,
	RSPAMD_MULTIPATTERN_ICASE = (1 << 0),
	RSPAMD_MULTIPATTERN_UTF8 = (1 << 1),
	RSPAMD_MULTIPATTERN_TLD = (1 << 2),
	/* Not supported by acism */
	RSPAMD_MULTIPATTERN_GLOB = (1 << 3),
	RSPAMD_MULTIPATTERN_RE = (1 << 4),
	RSPAMD_MULTIPATTERN_DOTALL = (1 << 5),
	RSPAMD_MULTIPATTERN_SINGLEMATCH = (1 << 6),
	RSPAMD_MULTIPATTERN_NO_START = (1 << 7),
	/*
	 * Explicitly request start-of-match offsets for every occurrence. This is
	 * the default behaviour today (hyperscan is compiled with
	 * HS_FLAG_SOM_LEFTMOST and the ACISM/regex fallbacks derive the start), so
	 * the flag mostly documents intent and makes the request explicit and
	 * future-proof. When set it also wins over RSPAMD_MULTIPATTERN_NO_START and
	 * RSPAMD_MULTIPATTERN_SINGLEMATCH (which would otherwise drop SOM).
	 */
	RSPAMD_MULTIPATTERN_SOM = (1 << 8),
};

/**
 * Multipattern state machine for async compilation
 */
enum rspamd_multipattern_state {
	RSPAMD_MP_STATE_INIT = 0,  /* Patterns added, ACISM ready */
	RSPAMD_MP_STATE_COMPILING, /* HS compile in progress */
	RSPAMD_MP_STATE_COMPILED,  /* HS ready */
	RSPAMD_MP_STATE_FALLBACK   /* HS failed, ACISM permanent */
};

/**
 * Multipattern compilation mode - controls how the multipattern is compiled
 * This is a property of the multipattern as a whole, not per-pattern.
 */
enum rspamd_multipattern_mode {
	RSPAMD_MP_MODE_SYNC = 0, /* Sync compile immediately (original behavior) */
	RSPAMD_MP_MODE_FALLBACK, /* ACISM fallback + async HS compile (for TLD trie) */
	RSPAMD_MP_MODE_LAZY,     /* Async HS compile, no fallback (generic multipatterns) */
};

struct rspamd_multipattern;
struct rspamd_cryptobox_library_ctx;
struct ev_loop;

/**
 * Called on pattern match.
 *
 * Offset convention: both offsets are byte offsets into @text and are 0-based.
 * @match_start is inclusive (the first matched byte) and @match_pos is
 * exclusive (one past the last matched byte), so the matched span is
 * text[match_start .. match_pos) and its length is match_pos - match_start.
 * Note that numerically @match_pos equals the 1-based position of the last
 * matched byte, which is what historical callers treated as "the end position".
 *
 * @param mp multipattern structure
 * @param strnum number of pattern matched (0-based pattern id)
 * @param match_start start byte offset of the match (0-based, inclusive). Only
 *        meaningful when start-of-match reporting is active (the default, or
 *        when RSPAMD_MULTIPATTERN_SOM is set); 0 otherwise.
 * @param match_pos end byte offset of the match (0-based, exclusive)
 * @param text input text
 * @param len length of input text
 * @param context userdata
 * @return if 0 then search for another pattern, otherwise return this value to caller
 */
typedef int (*rspamd_multipattern_cb_t)(struct rspamd_multipattern *mp,
										unsigned int strnum,
										int match_start,
										int match_pos,
										const char *text,
										gsize len,
										void *context);

/**
 * Async compilation callback
 * @param mp multipattern structure
 * @param success TRUE if compilation succeeded
 * @param err error if compilation failed (NULL on success)
 * @param ud userdata
 */
typedef void (*rspamd_multipattern_compile_cb_t)(struct rspamd_multipattern *mp,
												 gboolean success,
												 GError *err,
												 void *ud);

/**
 * Init multipart library and set the appropriate cache dir
 * @param cache_dir
 */
void rspamd_multipattern_library_init(const char *cache_dir);

/**
 * Creates empty multipattern structure
 * @param flags
 * @return
 */
struct rspamd_multipattern *rspamd_multipattern_create(
	enum rspamd_multipattern_flags flags);

/**
 * Creates multipattern with preallocated number of patterns to speed up loading
 * @param flags
 * @param reserved
 * @return
 */
struct rspamd_multipattern *rspamd_multipattern_create_sized(unsigned int reserved,
															 enum rspamd_multipattern_flags flags);

/**
 * Creates new multipattern structure
 * @param patterns vector of null terminated strings
 * @param npatterns number of patterns
 * @param flags flags applied to all patterns
 * @return new multipattern structure
 */
struct rspamd_multipattern *rspamd_multipattern_create_full(
	const char **patterns,
	unsigned int npatterns,
	enum rspamd_multipattern_flags flags);

/**
 * Set compilation mode for multipattern (must be called before compile)
 * @param mp
 * @param mode compilation mode (SYNC, FALLBACK, or LAZY)
 */
void rspamd_multipattern_set_mode(struct rspamd_multipattern *mp,
								  enum rspamd_multipattern_mode mode);

/**
 * Adds new pattern to match engine from zero-terminated string
 * @param mp
 * @param pattern
 */
void rspamd_multipattern_add_pattern(struct rspamd_multipattern *mp,
									 const char *pattern, int flags);

/**
 * Adds new pattern from arbitrary string
 * @param mp
 * @param pattern
 * @param patlen
 * @param flags
 */
void rspamd_multipattern_add_pattern_len(struct rspamd_multipattern *mp,
										 const char *pattern, gsize patlen, int flags);


#define RSPAMD_MULTIPATTERN_COMPILE_NO_FS (0x1u << 0u)
/**
 * Compiles multipattern structure
 * @param mp
 * @return
 */
gboolean rspamd_multipattern_compile(struct rspamd_multipattern *mp,
									 int flags,
									 GError **err);

/**
 * Lookups for patterns in a text using the specified callback function
 * @param mp
 * @param in
 * @param len
 * @param cb if callback returns non-zero, then search is terminated and that value is returned
 * @param ud callback data
 * @return
 */
int rspamd_multipattern_lookup(struct rspamd_multipattern *mp,
							   const char *in, gsize len, rspamd_multipattern_cb_t cb,
							   gpointer ud, unsigned int *pnfound);

/**
 * Get pattern string from multipattern identified by index
 * @param mp
 * @param index
 * @return
 */
const char *rspamd_multipattern_get_pattern(struct rspamd_multipattern *mp,
											unsigned int index);

/**
 * Returns number of patterns in a multipattern matcher
 * @param mp
 * @return
 */
unsigned int rspamd_multipattern_get_npatterns(struct rspamd_multipattern *mp);

/**
 * Destroys multipattern structure
 * @param mp
 */
void rspamd_multipattern_destroy(struct rspamd_multipattern *mp);

/**
 * Returns TRUE if hyperscan is supported
 * @return
 */
gboolean rspamd_multipattern_has_hyperscan(void);

/**
 * Get current state of multipattern compilation
 * @param mp
 * @return state enum value
 */
enum rspamd_multipattern_state rspamd_multipattern_get_state(
	struct rspamd_multipattern *mp);

/**
 * Check if hyperscan database is ready for use
 * @param mp
 * @return TRUE if hyperscan is compiled and ready
 */
gboolean rspamd_multipattern_is_hs_ready(struct rspamd_multipattern *mp);

/**
 * Start async hyperscan compilation. ACISM fallback must already be built
 * (via rspamd_multipattern_compile with appropriate flags).
 * @param mp multipattern (must be in INIT or COMPILING state)
 * @param flags compilation flags
 * @param event_loop event loop for async operations
 * @param cb callback when compilation completes
 * @param ud userdata for callback
 */
void rspamd_multipattern_compile_async(struct rspamd_multipattern *mp,
									   int flags,
									   struct ev_loop *event_loop,
									   rspamd_multipattern_compile_cb_t cb,
									   void *ud);

/**
 * Get pattern hash for cache key generation
 * @param mp
 * @param hash_out output buffer (must be rspamd_cryptobox_HASHBYTES)
 */
void rspamd_multipattern_get_hash(struct rspamd_multipattern *mp,
								  unsigned char *hash_out);

/**
 * Hot-swap hyperscan database after async compilation completes.
 * Called internally or from IPC handler when hs_helper sends compiled DB.
 * @param mp multipattern structure
 * @param hs_db new hyperscan database (takes ownership)
 * @return TRUE if swap succeeded
 */
gboolean rspamd_multipattern_set_hs_db(struct rspamd_multipattern *mp,
									   void *hs_db);

/**
 * Pending compilation entry for deferred HS compilation
 */
struct rspamd_multipattern_pending {
	struct rspamd_multipattern *mp;
	char *name;             /* Identifier for logging/IPC */
	unsigned char hash[64]; /* Cache key hash (rspamd_cryptobox_HASHBYTES) */
};

/**
 * Add multipattern to pending compilation queue.
 * Called during pre-fork initialization when hs_helper is not yet available.
 * @param mp multipattern in COMPILING state
 * @param name identifier for this multipattern (e.g., "tld")
 */
void rspamd_multipattern_add_pending(struct rspamd_multipattern *mp,
									 const char *name);

/**
 * Get list of pending multipattern compilations.
 * Returns array of rspamd_multipattern_pending, caller must free array (not contents).
 * @param count output: number of pending entries
 * @return array of pending entries or NULL if none
 */
struct rspamd_multipattern_pending *rspamd_multipattern_get_pending(
	unsigned int *count);

/**
 * Clear pending queue after hs_helper has processed it.
 */
void rspamd_multipattern_clear_pending(void);

/**
 * Find a pending multipattern by name.
 * @param name identifier
 * @return multipattern or NULL if not found
 */
struct rspamd_multipattern *rspamd_multipattern_find_pending(const char *name);

/**
 * Compile hyperscan database and save to cache file.
 * This is called by hs_helper for async compilation.
 * @param mp multipattern in COMPILING state
 * @param cache_dir directory to save cache file
 * @param err error output
 * @return TRUE on success
 */
gboolean rspamd_multipattern_compile_hs_to_cache(struct rspamd_multipattern *mp,
												 const char *cache_dir,
												 GError **err);

typedef void (*rspamd_multipattern_hs_cache_cb_t)(struct rspamd_multipattern *mp,
												  gboolean success,
												  GError *err,
												  void *ud);

/**
 * Compile multipattern HS database and store it in the configured HS cache backend.
 * If Lua backend is enabled, store is done asynchronously and callback is invoked on completion.
 * For file backend, compilation+store is synchronous and callback is invoked immediately.
 */
void rspamd_multipattern_compile_hs_to_cache_async(struct rspamd_multipattern *mp,
												   const char *cache_dir,
												   struct ev_loop *event_loop,
												   rspamd_multipattern_hs_cache_cb_t cb,
												   void *ud);

/**
 * Load hyperscan database from cache file.
 * This is called by workers when they receive notification that
 * hs_helper has compiled a multipattern database.
 * @param mp multipattern in COMPILING state
 * @param cache_dir directory containing cache files
 * @return TRUE if loaded successfully
 */
gboolean rspamd_multipattern_load_from_cache(struct rspamd_multipattern *mp,
											 const char *cache_dir);

/**
 * Asynchronously load hyperscan database for a multipattern from the configured
 * HS cache backend (Lua backend if present, otherwise filesystem).
 *
 * The callback is invoked when hot-swap has been attempted.
 */
void rspamd_multipattern_load_from_cache_async(struct rspamd_multipattern *mp,
											   const char *cache_dir,
											   struct ev_loop *event_loop,
											   void (*cb)(gboolean success, void *ud),
											   void *ud);

#ifdef __cplusplus
}
#endif

#endif /* SRC_LIBUTIL_MULTIPATTERN_H_ */
