#include "cli/argparse.h"
#include "cli/mlrcli.h"
#include "mapping/mappers.h"
#include "lib/mlr_globals.h"
#include "lib/mlrutil.h"
#include "lib/mlrregex.h"
#include "containers/sllv.h"

typedef struct _mapper_grep_state_t {
	ap_state_t* pargp;
	int exclude;
	regex_t regex;
	cli_writer_opts_t* pwriter_opts;
} mapper_grep_state_t;

static void      mapper_grep_usage(FILE* o, char* argv0, char* verb);
static mapper_t* mapper_grep_parse_cli(int* pargi, int argc, char** argv,
	cli_reader_opts_t* _, cli_writer_opts_t* __);
static mapper_t* mapper_grep_alloc(ap_state_t* pargp, char* regex_string, int exclude, int ignore_case,
	cli_writer_opts_t* pwriter_opts);
static void      mapper_grep_free(mapper_t* pmapper, context_t* _);
static sllv_t*   mapper_grep_process(lrec_t* pinrec, context_t* pctx, void* pvstate);

// ----------------------------------------------------------------
mapper_setup_t mapper_grep_setup = {
	.verb = "grep",
	.pusage_func = mapper_grep_usage,
	.pparse_func = mapper_grep_parse_cli,
	.ignores_input = FALSE,
};

// ----------------------------------------------------------------
static mapper_t* mapper_grep_parse_cli(int* pargi, int argc, char** argv,
	cli_reader_opts_t* _, cli_writer_opts_t* pwriter_opts)
{
	char* regex_string = NULL;
	int   exclude = FALSE;
	int   ignore_case = FALSE;

	if ((argc - *pargi) < 1) {
		mapper_grep_usage(stderr, argv[0], argv[*pargi]);
		return NULL;
	}

	char* verb = argv[(*pargi)++];

	ap_state_t* pstate = ap_alloc();
	ap_define_true_flag(pstate, "-v", &exclude);
	ap_define_true_flag(pstate, "-i", &ignore_case);

	if (!ap_parse(pstate, verb, pargi, argc, argv)) {
		mapper_grep_usage(stderr, argv[0], verb);
		return NULL;
	}

	if ((argc - *pargi) < 1) {
		mapper_grep_usage(stderr, argv[0], verb);
		return NULL;
	}

	regex_string = argv[(*pargi)++];

	mapper_t* pmapper = mapper_grep_alloc(pstate, regex_string, exclude, ignore_case, pwriter_opts);
	return pmapper;
}
static void mapper_grep_usage(FILE* o, char* argv0, char* verb) {
	fprintf(o, "Usage: %s %s [options] {regular expression}\n", argv0, verb);
	fprintf(o, "Passes through records which match {regex}.\n");
	fprintf(o, "Options:\n");
	fprintf(o, "-i    Use case-insensitive search.\n");
	fprintf(o, "-v    Invert: pass through records which do not match the regex.\n");
	fprintf(o, "Note that \"%s filter\" is more powerful, but requires you to know field names.\n", argv0);
	fprintf(o, "By contrast, \"%s %s\" allows you to regex-match the entire record. It does\n", argv0, verb);
	fprintf(o, "this by formatting each record in memory as DKVP, using command-line-specified\n");
	fprintf(o, "ORS/OFS/OPS, and matching the resulting line against the regex specified\n");
	fprintf(o, "here. In particular, the regex is not applied to the input stream: if you\n");
	fprintf(o, "have CSV with header line \"x,y,z\" and data line \"1,2,3\" then the regex will\n");
	fprintf(o, "be matched, not against either of these lines, but against the DKVP line\n");
	fprintf(o, "\"x=1,y=2,z=3\".  Furthermore, not all the options to system grep are supported,\n");
	fprintf(o, "and this command is intended to be merely a keystroke-saver. To get all the\n");
	fprintf(o, "features of system grep, you can do\n");
	fprintf(o, "  \"%s --odkvp ... | grep ... | %s --idkvp ...\"\n", argv0, argv0);
}

// ----------------------------------------------------------------
static mapper_t* mapper_grep_alloc(ap_state_t* pargp, char* regex_string, int exclude, int ignore_case,
	cli_writer_opts_t* pwriter_opts)
{
	mapper_t* pmapper = mlr_malloc_or_die(sizeof(mapper_t));

	mapper_grep_state_t* pstate = mlr_malloc_or_die(sizeof(mapper_grep_state_t));
	pstate->pargp = pargp;
	int cflags = REG_NOSUB;
	if (ignore_case)
		cflags |= REG_ICASE;
	regcomp_or_die_quoted(&pstate->regex, regex_string, cflags);
	pstate->exclude = exclude;
	pstate->pwriter_opts = pwriter_opts;

	pmapper->pvstate       = pstate;
	pmapper->pprocess_func = mapper_grep_process;
	pmapper->pfree_func    = mapper_grep_free;
	return pmapper;
}
static void mapper_grep_free(mapper_t* pmapper, context_t* _) {
	mapper_grep_state_t* pstate = pmapper->pvstate;
	regfree(&pstate->regex);
	ap_free(pstate->pargp);
	free(pstate);
	free(pmapper);
}

// ----------------------------------------------------------------
static sllv_t* mapper_grep_process(lrec_t* pinrec, context_t* pctx, void* pvstate) {
	if (pinrec == NULL) // end of input stream
		return sllv_single(NULL);

	mapper_grep_state_t* pstate = (mapper_grep_state_t*)pvstate;

	char* line = lrec_sprint(pinrec,
		pstate->pwriter_opts->ors,
		pstate->pwriter_opts->ofs,
		pstate->pwriter_opts->ops);

	int matches = regmatch_or_die(&pstate->regex, line, 0, NULL);
	sllv_t* poutrecs = NULL;
	if (matches ^ pstate->exclude) {
		poutrecs = sllv_single(pinrec);
	} else {
		lrec_free(pinrec);
	}
	free(line);
	return poutrecs;
}
