#include "lib/mlrutil.h"
#include "containers/lrec.h"
#include "containers/sllv.h"
#include "containers/hss.h"
#include "containers/mixutil.h"
#include "mapping/mappers.h"
#include "cli/argparse.h"

typedef struct _mapper_cut_state_t {
	slls_t*  pfield_name_list;
	hss_t*   pfield_name_set;
	regex_t* regexes;
	int      nregex;
	int      do_arg_order;
	int      do_complement;
} mapper_cut_state_t;

static sllv_t*   mapper_cut_process_no_regexes(lrec_t* pinrec, context_t* pctx, void* pvstate);
static sllv_t*   mapper_cut_process_with_regexes(lrec_t* pinrec, context_t* pctx, void* pvstate);
static void      mapper_cut_free(void* pvstate);
static mapper_t* mapper_cut_alloc(slls_t* pfield_name_list,
	int do_arg_order, int do_complement, int do_regexes);
static void      mapper_cut_usage(FILE* o, char* argv0, char* verb);
static mapper_t* mapper_cut_parse_cli(int* pargi, int argc, char** argv);

// ----------------------------------------------------------------
mapper_setup_t mapper_cut_setup = {
	.verb = "cut",
	.pusage_func = mapper_cut_usage,
	.pparse_func = mapper_cut_parse_cli,
};

// ----------------------------------------------------------------
static void mapper_cut_usage(FILE* o, char* argv0, char* verb) {
	fprintf(o, "Usage: %s %s [options]\n", argv0, verb);
	fprintf(o, "Passes through input records with specified fields included/excluded.\n");
	fprintf(o, "-f {a,b,c}       Field names to include for cut.\n");
	fprintf(o, "-o               Retain fields in the order specified here in the argument list.\n");
	fprintf(o, "                 Default is to retain them in the order found in the input data.\n");
	fprintf(o, "-x|--complement  Exclude, rather than include, field names specified by -f.\n");
	fprintf(o, "-r               Treat field names as regular expressions. \"ab\", \"a.*b\" will\n");
	fprintf(o, "                 match any field name containing the substring \"ab\" or matching\n");
	fprintf(o, "                 \"a.*b\", respectively; anchors of the form \"^ab$\", \"^a.*b$\" may\n");
	fprintf(o, "                 be used. The -o flag is ignored when -r is present.\n");
	fprintf(o, "Examples:\n");
	fprintf(o, "  %s %s -f hostname,status\n", argv0, verb);
	fprintf(o, "  %s %s -x -f hostname,status\n", argv0, verb);
	fprintf(o, "  %s %s -r -f '^status$,sda[0-9]'\n", argv0, verb);
	fprintf(o, "  %s %s -r -f '^status$,\"sda[0-9]\"'\n", argv0, verb);
	fprintf(o, "  %s %s -r -f '^status$,\"sda[0-9]\"i' (this is case-insensitive)\n", argv0, verb);
}

// ----------------------------------------------------------------
static mapper_t* mapper_cut_parse_cli(int* pargi, int argc, char** argv) {
	slls_t* pfield_name_list  = NULL;
	int     do_arg_order  = FALSE;
	int     do_complement = FALSE;
	int     do_regexes    = FALSE;

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

	ap_state_t* pstate = ap_alloc();
	ap_define_string_list_flag(pstate, "-f", &pfield_name_list);
	ap_define_true_flag(pstate, "-o",           &do_arg_order);
	ap_define_true_flag(pstate, "-x",           &do_complement);
	ap_define_true_flag(pstate, "--complement", &do_complement);
	ap_define_true_flag(pstate, "-r",           &do_regexes);

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

	if (pfield_name_list == NULL) {
		mapper_cut_usage(stderr, argv[0], verb);
		return NULL;
	}

	return mapper_cut_alloc(pfield_name_list, do_arg_order, do_complement, do_regexes);
}

// ----------------------------------------------------------------
static mapper_t* mapper_cut_alloc(slls_t* pfield_name_list,
	int do_arg_order, int do_complement, int do_regexes)
{
	mapper_t* pmapper = mlr_malloc_or_die(sizeof(mapper_t));

	mapper_cut_state_t* pstate = mlr_malloc_or_die(sizeof(mapper_cut_state_t));
	if (!do_regexes) {
		pstate->pfield_name_list   = pfield_name_list;
		slls_reverse(pstate->pfield_name_list);
		pstate->pfield_name_set    = hss_from_slls(pfield_name_list);
		pstate->nregex             = 0;
		pmapper->pprocess_func     = mapper_cut_process_no_regexes;
	} else {
		pstate->pfield_name_list   = NULL;
		pstate->pfield_name_set    = NULL;
		pstate->nregex = pfield_name_list->length;
		pstate->regexes = mlr_malloc_or_die(pstate->nregex * sizeof(regex_t));
		int i = 0;
		for (sllse_t* pe = pfield_name_list->phead; pe != NULL; pe = pe->pnext, i++) {
			// Let them type in a.*b if they want, or "a.*b", or "a.*b"i.
			// Strip off the leading " and trailing " or "i.
			regcomp_or_die_quoted(&pstate->regexes[i], pe->value, REG_NOSUB);
		}
		pmapper->pprocess_func     = mapper_cut_process_with_regexes;
	}
	pstate->do_arg_order   = do_arg_order;
	pstate->do_complement  = do_complement;

	pmapper->pvstate       = (void*)pstate;
	pmapper->pfree_func    = mapper_cut_free;

	return pmapper;
}

static void mapper_cut_free(void* pvstate) {
	mapper_cut_state_t* pstate = (mapper_cut_state_t*)pvstate;
	if (pstate->pfield_name_list != NULL)
		slls_free(pstate->pfield_name_list);
	if (pstate->pfield_name_set != NULL)
		hss_free(pstate->pfield_name_set);
	for (int i = 0; i < pstate->nregex; i++)
		regfree(&pstate->regexes[i]);
}

// ----------------------------------------------------------------
static sllv_t* mapper_cut_process_no_regexes(lrec_t* pinrec, context_t* pctx, void* pvstate) {
	if (pinrec != NULL) {
		mapper_cut_state_t* pstate = (mapper_cut_state_t*)pvstate;
		if (!pstate->do_complement) {
			// Loop over the record and free the fields not in the
			// to-be-retained set, being careful about the fact that we're
			// modifying what we're looping over.
			for (lrece_t* pe = pinrec->phead; pe != NULL; /* next in loop */) {
				if (!hss_has(pstate->pfield_name_set, pe->key)) {
					lrece_t* pf = pe->pnext;
					lrec_remove(pinrec, pe->key);
					pe = pf;
				} else {
					pe = pe->pnext;
				}
			}
			if (pstate->do_arg_order) {
				// OK since the field-name list was reversed at construction time.
				for (sllse_t* pe = pstate->pfield_name_list->phead; pe != NULL; pe = pe->pnext) {
					char* field_name = pe->value;
					lrec_move_to_head(pinrec, field_name);
				}
			}
			return sllv_single(pinrec);
		} else {
			for (sllse_t* pe = pstate->pfield_name_list->phead; pe != NULL; pe = pe->pnext) {
				char* field_name = pe->value;
				lrec_remove(pinrec, field_name);
			}
			return sllv_single(pinrec);
		}
	}
	else {
		return sllv_single(NULL);
	}
}

// ----------------------------------------------------------------
static sllv_t* mapper_cut_process_with_regexes(lrec_t* pinrec, context_t* pctx, void* pvstate) {
	if (pinrec != NULL) {
		mapper_cut_state_t* pstate = (mapper_cut_state_t*)pvstate;
		// Loop over the record and free the fields to be discarded, being
		// careful about the fact that we're modifying what we're looping over.
		for (lrece_t* pe = pinrec->phead; pe != NULL; /* next in loop */) {
			int matches_any = FALSE;
			for (int i = 0; i < pstate->nregex; i++) {
				if (regmatch_or_die(&pstate->regexes[i], pe->key, 0, NULL)) {
					matches_any = TRUE;
					break;
				}
			}
			if (matches_any ^ pstate->do_complement) {
				pe = pe->pnext;
			} else {
				lrece_t* pf = pe->pnext;
				lrec_remove(pinrec, pe->key);
				pe = pf;
			}
		}
		return sllv_single(pinrec);
	}
	else {
		return sllv_single(NULL);
	}
}
