// *naka* - find a file within another file
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>

#include <ints.h>
#include <macros.h>
#include <version.h>

#include <project.h>

#include <tests.h>

#include <map.h>
#include <comp.h>

void prog_info(FILE* out)
{
	fprintf(out, PROG_NAME " v%s - " PROG_DESCRIPTION
#ifdef DEBUG
	" (debug build)"
#endif
		"\n written by %s with <3 (compiled at %lu UTC (unix ts))\n  license %s.\n", 
			v_ctoss(v_rawtoc(PROG_VERSION)),
			PROG_AUTHOUR,
			PROG_COMPILED_TIMESTAMP,
			PROG_LICENSE);
}

void usage(FILE* out, int argc, char** argv)
{
	IGNORE(argc);

	prog_info(out);
	fprintf(out, "\nUsage: %s <needle> <haystack(s)...>\n", argv[0] ?: PROG_NAME);
	fprintf(out, "\nUsage: %s --help\n", argv[0] ?: PROG_NAME);
}

// err: 0 for normal exit and print to stdout.
// err: nonzero for abnormal exit and print to stderr.
noreturn void usage_then_exit(int err, int argc, char** argv)
{
	usage( err ? stderr : stdout, argc, argv);
	exit(err);
}

static int map_haystacks(const char* const * h, map_t maps[pOUT])
{
	const char* path;
	usize d =0;
	while( (path = *h++) )
	{
		map_t *pOUT c = maps++;
		INFO("Mapping haystack file `%s' (to map addr %p)", path, c);
		debug_assert(c);
		if(!map_handle_err(map_file(path, false, 0, 0, c))) {
			//Handle unmapping previous haystacks before return
			// Prep reset maps to before `c` for unwind
			maps-=1;
			ERROR("Failed to map file `%s', rolling back %lu maps. (erp: %p, prev: %p)", path, d, c, maps);
			while( d --> 0 )
				if(!map_handle_err(map_free(* (--maps)))) WARN("Failed to free map at %p (in %lu unwind)", maps, d);
			return 0;
		}
		d+=1;
	}
	return 1;
}

int main(int argc, char** argv)
{
	TRACE("main start with %d (pn: %s, a1: %s)", argc, argv[0], argv[1] ?: "<null>");

	if(!argv[1]) 
inv_args:
		usage_then_exit(PROG_RET_ARGS, argc, argv);
	else if(strcmp(argv[1], "--help")==0)
		usage_then_exit(0, argc, argv);
	else if(!argv[2])
		goto inv_args;

	map_t needle;
	INFO("Mapping needle file `%s'", argv[1]);
	if(!map_handle_err(map_file(argv[1], false, 0, 0, &needle))) return PROG_RET_MAP_NEEDLE_FAILED;
	if(!map_handle_err(map_preload(&needle, false))) WARN("Failed to advise kernel about memory access: needle");

	// Remove exe name from argv: now is (needle, haystacks...)
	argv+=1;
	// Setup haystack maps
	usize hsn = AS(argc-2, usize);
	map_t haystacks[hsn];
	TRACE("Attempting to map %lu haystacks", hsn);
	if(!map_haystacks((const char**)(argv+1), haystacks)) return PROG_RET_MAP_HAYSTACK_FAILED;

#ifdef _FEATURE_PARALLEL // Multi-threaded	
	//TODO: Setup thread-pool.
	//TODO: Dispatch haystack maps to threadpool.
	//TODO: Use either `cmp_find()` or `cmp_find_many()` to find the `needle` (mapped above) within those haystacks.
	//TODO: Within the threadpool: output information regarding each match/nonmatch.
	//TODO: Join the threadpool and consolidate results.
	//TODO: Iterate through the haystack numbers match results, return the first non-match haystack number through `PROG_RET_MATCH_HAYSTACK_N_FAILED(n)` (haystack numbers are always nonzero). If all were matched, return 0
#else // Single-threaded
	INFO("Computing result for %lu haystacks...", hsn);
	comp_multires_t* full_result = comp_match_all(&needle, hsn, haystacks, false);
	// Number of results actually computed by `comp_match_all()` may differ from `hsn` if `abort_on_fail` is true (currently its hardcoded to false but that may change.)
	usize cres = full_result->num_results;
	INFO("Computed result: Suc. Matched %lu / %lu hs. Full match: %s (first failure: %d)", cres, hsn, full_result->all_matched ? "yes" : "no", full_result->idx_first_failure);
	free(full_result);
#endif

	for(int i=0;i<(int)hsn;i++) {
		INFO("Unmapping haystack haystack file `%s' (#%d) (map addr %p)", argv[i+1], i+1, haystacks+i);
		if(!map_handle_err(map_free(haystacks[i]))) return PROG_RET_UNMAP_HAYSTACK_N_FAILED(i+1);
	}
	INFO("Unmapping needle file `%s'", *argv);
	if(!map_handle_err(map_free(needle))) return PROG_RET_UNMAP_NEEDLE_FAILED;
	TRACE("main end");
	return 0;
}

