/*
 * This file is part of libFirm.
 * Copyright (C) 2012 University of Karlsruhe.
 */

/**
 * @file
 * @brief       Backend module interface.
 * @author      Matthias Braun
 * @date        29.09.2005
 */
#include "bemodule.h"

#include "isas.h"
#include "util.h"
#include "xmalloc.h"
#include <stdbool.h>
#include <stdlib.h>

/**
 * A module list entry.
 */
struct be_module_list_entry_t {
	const char *name;                    /**< The name of the entry. */
	void *data;                          /**< Some data associated with this entry. */
	struct be_module_list_entry_t *next; /**< Points to the next entry. */
};

void be_init_2addr(void);
void be_init_arch(void);
void be_init_blocksched(void);
void be_init_chordal(void);
void be_init_chordal_common(void);
void be_init_chordal_main(void);
void be_init_copyheur4(void);
void be_init_copyilp(void);
void be_init_copyilp2(void);
void be_init_copynone(void);
void be_init_copyopt(void);
void be_init_daemelspill(void);
void be_init_dwarf(void);
void be_init_listsched(void);
void be_init_live(void);
void be_init_loopana(void);
void be_init_pbqp(void);
void be_init_pbqp_coloring(void);
void be_init_peephole(void);
void be_init_pref_alloc(void);
void be_init_ra(void);
void be_init_sched(void);
void be_init_sched_normal(void);
void be_init_sched_rand(void);
void be_init_sched_trivial(void);
void be_init_spill(void);
void be_init_spillbelady(void);
void be_init_spilloptions(void);
void be_init_spillslots(void);
void be_init_ssaconstr(void);
void be_init_state(void);

void be_quit_pbqp(void);

/**
 * Driver for module initialization.
 * Call your module initialization function here.
 */
void be_init_modules(void)
{
	static bool run_once = false;
	if (run_once)
		return;
	run_once = true;

	be_init_2addr();
	be_init_arch();
	be_init_blocksched();
	be_init_chordal_common();
	be_init_copyopt();
	be_init_dwarf();
	be_init_live();
	be_init_loopana();
	be_init_peephole();
	be_init_ra();
	be_init_sched();
	be_init_spill();
	be_init_spilloptions();
	be_init_spillslots();
	be_init_ssaconstr();
	be_init_state();

	/* in the following groups the first one is the default */
	be_init_arch_ia32();
	be_init_arch_arm();
	be_init_arch_mips();
	be_init_arch_sparc();
	be_init_arch_amd64();
	be_init_arch_riscv32();
	be_init_arch_TEMPLATE();

	be_init_listsched();
	be_init_sched_normal();
	be_init_sched_rand();
	be_init_sched_trivial();

	be_init_chordal_main();
	be_init_pref_alloc();

	be_init_chordal();
	be_init_pbqp_coloring();

	be_init_spillbelady();
	be_init_daemelspill();

	be_init_copyheur4();
	be_init_copyilp2();
	be_init_copynone();
	be_init_copyilp();

#ifdef FIRM_GRGEN_BE
	be_init_pbqp();
#endif
}

void be_quit_modules(void)
{
#ifdef FIRM_GRGEN_BE
	be_quit_pbqp();
#endif
}

//---------------------------------------------------------------------------

typedef struct module_opt_data_t {
	void **var;
	be_module_list_entry_t * const *list_head;
} module_opt_data_t;

/**
 * Searches in list for module option. If found, set option to given value and
 * return true.
 * Beware: return value of 0 means error.
 */
static bool set_opt_module(void *const data, size_t const length, char const *const opt)
{
	(void)length;

	const module_opt_data_t *moddata = (module_opt_data_t*)data;
	bool                     res     = false;
	for (const be_module_list_entry_t *module = *(moddata->list_head);
	     module != NULL; module = module->next) {
		if (streq(module->name, opt)) {
			*(moddata->var) = module->data;
			res = true;
			break;
		}
	}

	return res;
}

/**
 * Dump the names of all registered module options.
 */
static int dump_opt_module(char *buf, size_t buflen, void *data)
{
	const module_opt_data_t *moddata = (module_opt_data_t*)data;
	for (const be_module_list_entry_t *module = *(moddata->list_head);
	     module != NULL; module = module->next) {
		if (module->data == *(moddata->var)) {
			snprintf(buf, buflen, "%s", module->name);
			return strlen(buf);
		}
	}

	snprintf(buf, buflen, "none");
	return strlen(buf);
}

/**
 * Dump the values of all register module options.
 */
static int dump_opt_module_vals(char *buf, size_t buflen, void *data)
{
	const module_opt_data_t *moddata = (module_opt_data_t*)data;
	char                    *p       = buf;
	for (const be_module_list_entry_t *module = *(moddata->list_head);
	     module != NULL; module = module->next) {
		size_t name_len = strlen(module->name);

		if (module != *(moddata->list_head)) {
			p       = strncat(p, ", ", buflen - 1);
			buflen -= 2;
		}

		p = strncat(p, module->name, buflen - 1);
		if (name_len >= buflen)
			break;

		buflen -= name_len;
	}

	return strlen(buf);
}

/**
 * Add a new module to list.
 */
void be_add_module_to_list(be_module_list_entry_t **list_head, const char *name,
                           void *module)
{
	be_module_list_entry_t *entry = XMALLOC(be_module_list_entry_t);
	entry->name = name;
	entry->data = module;
	entry->next = *list_head;
	*list_head  = entry;
}

/**
 * Add an option for a module.
 */
void be_add_module_list_opt(lc_opt_entry_t *grp, const char *name,
                            const char *description,
                            be_module_list_entry_t * const * list_head,
                            void **var)
{
	module_opt_data_t *moddata = XMALLOC(module_opt_data_t);
	moddata->var       = var;
	moddata->list_head = list_head;

	lc_opt_add_opt(grp, name, description, lc_opt_type_enum,
	               moddata, sizeof(moddata[0]), set_opt_module,
	               dump_opt_module, dump_opt_module_vals);
}
