#include "asn1fix_internal.h"
#include "asn1fix.h"

/* Print everything to stderr */
static void _default_error_logger(int _severity, const char *fmt, ...);

/*
 * Internal check functions.
 */
static int asn1f_fix_module__phase_1(arg_t *arg);
static int asn1f_fix_module__phase_2(arg_t *arg);
static int asn1f_fix_simple(arg_t *arg);	/* For INTEGER/ENUMERATED */
static int asn1f_fix_constructed(arg_t *arg);	/* For SEQUENCE/SET/CHOICE */
static int asn1f_resolve_constraints(arg_t *arg); /* For subtype constraints */
static int asn1f_check_constraints(arg_t *arg);	/* For subtype constraints */
static int asn1f_check_duplicate(arg_t *arg);
static int asn1f_apply_unique_index(arg_t *arg);
static int phase_1_1(arg_t *arg, int prm2);

arg_t a1f_replace_me_with_proper_interface_arg;

/*
 * Scan every module defined here in search for inconsistences.
 */
int
asn1f_process(asn1p_t *asn, enum asn1f_flags flags,
		error_logger_f error_logger) {
	arg_t arg;
	int fatals = 0;
	int warnings = 0;
	int ret;

	/*
	 * Check validity of arguments.
	 */
	if(asn == NULL) {
		errno = EINVAL;
		return -1;
	}

	/*
	 * If errors handler is not specified, default to internal one.
	 */
	if(error_logger == 0) {
		error_logger = _default_error_logger;
	}

	memset(&arg, 0, sizeof(arg));
	arg.asn = asn;
	arg.eh = error_logger;

	if(flags & A1F_DEBUG) {
		arg.debug = arg.eh;
		arg.debug(-1, "Called %s() with flags %d", __func__, flags);
		flags &= ~A1F_DEBUG;
	}

	/* Allow SIZE() constraint for INTEGER and other types */
	if(flags & A1F_EXTENDED_SizeConstraint) {
		arg.flags |= A1F_EXTENDED_SizeConstraint;
		flags &= ~A1F_EXTENDED_SizeConstraint;
		if(arg.debug) {
			arg.debug(-1,
				"Extended SizeConstraint support enabled");
		}
	}

	a1f_replace_me_with_proper_interface_arg = arg;

	/*
	 * Check that we haven't missed an unknown flag.
	 */
	if(flags) {
		errno = EINVAL;
		return -1;
	}

	/*
	 * Process each module in the list.
	 * PHASE I.
	 */
	TQ_FOR(arg.mod, &(asn->modules), mod_next) {
		ret = asn1f_fix_module__phase_1(&arg);
		/*
		 * These lines are used for illustration purposes.
		 * RET2RVAL() is used everywhere else.
		 */
		if(ret == -1) fatals++;
		if(ret == 1) warnings++;
	}
	/* PHASE II. */
	TQ_FOR(arg.mod, &(asn->modules), mod_next) {
		ret = asn1f_fix_module__phase_2(&arg);
		if(ret == -1) fatals++;
		if(ret == 1) warnings++;
	}

	memset(&a1f_replace_me_with_proper_interface_arg, 0, sizeof(arg_t));

	/*
	 * Compute a return value.
	 */
	return fatals?-1:warnings?1:0;
}

/*
 * Check the internals of a single module.
 */
static int
asn1f_fix_module__phase_1(arg_t *arg) {
	asn1p_expr_t *expr;
	int rvalue = 0;
	int ret;
	asn1p_module_t *omod;

	/*
	 * Check that we don't have a similarly named module.
	 */
	TQ_FOR(omod, &arg->asn->modules, mod_next) {
		int sameNames;
		if(omod == arg->mod) break;
		sameNames = strcmp(omod->ModuleName, arg->mod->ModuleName)?0:1;
		if(omod->module_oid && arg->mod->module_oid) {
			/* Compare only the OID. */
			if(asn1p_oid_compare(omod->module_oid,
					arg->mod->module_oid) == 0) {
				FATAL("ASN.1 module %s in %s "
					"has the same OBJECT IDENTIFIER"
					" as module %s",
					omod->ModuleName,
					omod->source_file_name,
					arg->mod->ModuleName
				);
				RET2RVAL(-1, rvalue);
			} else if(sameNames) {
				WARNING("ASN.1 module %s is defined more than once, with different OIDs", omod->ModuleName);
				RET2RVAL(1, rvalue);
			}
		} else if(sameNames) {
			FATAL("ASN.1 module %s is defined more than once",
				omod->ModuleName);
			RET2RVAL(-1, rvalue);
		}
	}

	switch((arg->mod->module_flags & MSF_MASK_TAGS)) {
	case MSF_NOFLAGS:
	case MSF_EXPLICIT_TAGS:
	case MSF_IMPLICIT_TAGS:
	case MSF_AUTOMATIC_TAGS:
		break;
	default:
		FATAL("Module %s defined with ambiguous global tagging mode",
			arg->mod->ModuleName);
		RET2RVAL(-1, rvalue);
	}

	switch((arg->mod->module_flags & MSF_MASK_INSTRUCTIONS)) {
	case MSF_NOFLAGS:
		/*
		 * arg->mod->module_flags |= MSF_TAG_INSTRUCTIONS;
		 */
		break;
	case MSF_unk_INSTRUCTIONS:
		WARNING("Module %s defined with unrecognized "
			"encoding reference", arg->mod->ModuleName);
		RET2RVAL(1, rvalue);
		/* Fall through */
	case MSF_TAG_INSTRUCTIONS:
	case MSF_XER_INSTRUCTIONS:
		break;
	default:
		FATAL("Module %s defined with ambiguous encoding reference",
			arg->mod->ModuleName);
		RET2RVAL(-1, rvalue);
	}

	/*
	 * Do various non-recursive transformations.
	 */
	TQ_FOR(expr, &(arg->mod->members), next) {
		arg->expr = expr;
		ret = phase_1_1(arg, 0);
		RET2RVAL(ret, rvalue);
		/*
		 * Make sure everybody's behaving well.
		 */
		assert(arg->expr == expr);
	}
	TQ_FOR(expr, &(arg->mod->members), next) {
		arg->expr = expr;
		ret = phase_1_1(arg, 1);
		RET2RVAL(ret, rvalue);
		assert(arg->expr == expr);
	}



	/*
	 * 5. Automatic tagging
	 */
	TQ_FOR(expr, &(arg->mod->members), next) {

		arg->expr = expr;

		ret = asn1f_recurse_expr(arg, asn1f_fix_constr_autotag);
		RET2RVAL(ret, rvalue);

		assert(arg->expr == expr);
	}

	/*
	 * 8. fix BIT STRING
	 * 9. fix spaces in cstrings
	 */
	TQ_FOR(expr, &(arg->mod->members), next) {
		arg->expr = expr;

		ret = asn1f_recurse_expr(arg, asn1f_fix_bit_string);
		RET2RVAL(ret, rvalue);

		ret = asn1f_recurse_expr(arg, asn1f_fix_cstring);
		RET2RVAL(ret, rvalue);

		assert(arg->expr == expr);
	}

	/*
	 * ... Check for tags distinctness.
	 */
	TQ_FOR(expr, &(arg->mod->members), next) {
		arg->expr = expr;

		ret = asn1f_recurse_expr(arg, asn1f_check_constr_tags_distinct);
		RET2RVAL(ret, rvalue);

		assert(arg->expr == expr);
	}

	return rvalue;
}

static int
asn1f_fix_module__phase_2(arg_t *arg) {
	asn1p_expr_t *expr;
	int rvalue = 0;
	int ret;

	TQ_FOR(expr, &(arg->mod->members), next) {
		arg->expr = expr;

		/*
		 * Dereference DEFAULT values.
		 */
		ret = asn1f_recurse_expr(arg, asn1f_fix_dereference_defaults);
		RET2RVAL(ret, rvalue);

		/*
		 * Check semantic validity of constraints.
		 */
		ret = asn1f_recurse_expr(arg, asn1f_check_constraints);
		RET2RVAL(ret, rvalue);

		/*
		 * Uniquely tag each inner type.
		 */
		asn1f_apply_unique_index(0);
		ret = asn1f_recurse_expr(arg, asn1f_apply_unique_index);
		RET2RVAL(ret, rvalue);

		assert(arg->expr == expr);
	}

	return rvalue;
}

static int
phase_1_1(arg_t *arg, int prm2) {
	asn1p_expr_t *expr = arg->expr;
	int rvalue = 0;
	int ret;

	if(expr->lhs_params && expr->spec_index == -1) {
		int i;
		if(!prm2)
			/* Do not process the parameterized type just yet */
			return 0;
		for(i = 0; i < expr->specializations.pspecs_count; i++) {
			arg->expr = expr->specializations.pspec[i].my_clone;
			ret = phase_1_1(arg, 0);
			RET2RVAL(ret, rvalue);
		}
		arg->expr = expr;	/* revert */
		return rvalue;
	} else if(prm2) {
		return 0;	/* Already done! */
	}

	/* Check whether this type is a duplicate */
	if(!expr->lhs_params) {
		ret = asn1f_check_duplicate(arg);
		RET2RVAL(ret, rvalue);
	}

	DEBUG("=== Now processing \"%s\" (%d/0x%x) at line %d ===",
		expr->Identifier, expr->meta_type, expr->expr_type,
		expr->_lineno);
	assert(expr->meta_type != AMT_INVALID);

	/*
	 * 2.1 Pre-process simple types (ENUMERATED, INTEGER, etc).
	 */
	ret = asn1f_recurse_expr(arg, asn1f_fix_simple);
	RET2RVAL(ret, rvalue);

	/*
	 * 2.5.4
	 */
	ret = asn1f_recurse_expr(arg, asn1f_fix_dereference_types);
	RET2RVAL(ret, rvalue);

	/*
	 * Fix tagging of top-level types.
	 */
	ret = asn1f_fix_constr_tag(arg, 1);
	RET2RVAL(ret, rvalue);

	/*
	 * 2.[234] Process SEQUENCE/SET/CHOICE types.
	 */
	ret = asn1f_recurse_expr(arg, asn1f_fix_constructed);
	RET2RVAL(ret, rvalue);

	/*
	 * 2.5.5
	 */
	ret = asn1f_recurse_expr(arg, asn1f_fix_dereference_values);
	RET2RVAL(ret, rvalue);

	/*
	 * Parse class objects and fill up the object class with data.
	 */
	ret = asn1f_parse_class_object(arg);
	RET2RVAL(ret, rvalue);

	/*
	 * Resolve references in constraints.
	 */
	ret = asn1f_recurse_expr(arg, asn1f_resolve_constraints);
	RET2RVAL(ret, rvalue);

	/*
	 * 6. INTEGER value processed at 2.5.4.
	 */

	return rvalue;
}

static int
asn1f_fix_simple(arg_t *arg) {
	int rvalue = 0;
	int ret;

	ret = asn1f_fix_enum(arg);
	RET2RVAL(ret, rvalue);

	ret = asn1f_fix_integer(arg);
	RET2RVAL(ret, rvalue);

	return rvalue;
}

static int
asn1f_fix_constructed(arg_t *arg) {
	int rvalue = 0;
	int ret;

	switch(arg->expr->expr_type) {
	case ASN_CONSTR_SEQUENCE:
	case ASN_CONSTR_SET:
	case ASN_CONSTR_CHOICE:
		break;
	default:
		return 0;
	}

	/* Check identifier distinctness */
	ret = asn1f_check_unique_expr(arg);
	RET2RVAL(ret, rvalue);

	/* Fix extensibility */
	ret = asn1f_fix_constr_ext(arg);
	RET2RVAL(ret, rvalue);

	/* Fix tagging */
	ret = asn1f_fix_constr_tag(arg, 0);
	RET2RVAL(ret, rvalue);

	/* Import COMPONENTS OF stuff */
	ret = asn1f_pull_components_of(arg);
	RET2RVAL(ret, rvalue);

	return rvalue;
}

static int
asn1f_resolve_constraints(arg_t *arg) {
	asn1p_expr_t *top_parent;
	asn1p_expr_type_e etype;
	int rvalue = 0;
	int ret;

	top_parent = asn1f_find_terminal_type(arg, arg->expr);
	if(top_parent)
		etype = top_parent->expr_type;
	else	etype = A1TC_INVALID;

	DEBUG("(%s)", arg->expr->Identifier);

	ret = asn1constraint_resolve(arg, arg->expr->constraints, etype, 0);
	RET2RVAL(ret, rvalue);

	return rvalue;
}

static int
asn1f_check_constraints(arg_t *arg) {
	static enum asn1p_constraint_type_e test_types[] = {
		ACT_EL_RANGE, ACT_CT_SIZE, ACT_CT_FROM };
	asn1p_expr_t *top_parent;
	asn1cnst_range_t *range;
	asn1p_expr_type_e etype;
	unsigned int i;
	int rvalue = 0;
	int ret;

	DEBUG("(%s{%d/%d})",
		arg->expr->Identifier,
		arg->expr->meta_type, arg->expr->expr_type);

	top_parent = asn1f_find_terminal_type(arg, arg->expr);
	if(!top_parent)
		return 0;
	etype = top_parent->expr_type;

	ret = asn1constraint_pullup(arg);
	RET2RVAL(ret, rvalue);

	for(i = 0; i < sizeof(test_types)/sizeof(test_types[0]); i++) {
		range = asn1constraint_compute_PER_range(
				etype,
				arg->expr->combined_constraints,
				test_types[i], 0, 0,
				CPR_noflags /* ignore -fbless-SIZE */);
		if(!range && errno == EPERM) {
			FATAL("This error happened for \"%s\" (meta %d) "
				"at line %d",
				arg->expr->Identifier,
				arg->expr->meta_type,
				arg->expr->_lineno);
			return -1;
		}
		asn1constraint_range_free(range);
	}

	return rvalue;
}

static int
asn1f_check_duplicate(arg_t *arg) {
	arg_t tmparg = *arg;
	int rvalue = 0;

	/*
	 * This is a linear scan in search of a similar type.
	 * The linear scan is just fine for the task, no need to over-optimize.
	 */
	TQ_FOR(tmparg.mod, &arg->asn->modules, mod_next) {
		int critical = 1;	/* FATAL */

		if((arg->mod->_tags & MT_STANDARD_MODULE)
		!= (tmparg.mod->_tags & MT_STANDARD_MODULE)) {
			/* Ignore clashes with standard module */
			critical = 0;	/* WARNING */
		}

		TQ_FOR(tmparg.expr, &(tmparg.mod->members), next) {
			int diff_files;	/* different files */

			assert(tmparg.expr->Identifier);
			assert(arg->expr->Identifier);

			if(arg->expr->spec_index != -1)
				continue;

			if(tmparg.expr == arg->expr) break;

			if(strcmp(tmparg.expr->Identifier,
				  arg->expr->Identifier))
				continue;

			diff_files = strcmp(arg->mod->source_file_name,
					tmparg.mod->source_file_name) ? 1 : 0;

			LOG(critical,
			"ASN.1 expression \"%s\" at line %d of module %s\n"
			"clashes with expression \"%s\" at line %d of module %s"
			"%s%s%s.\n"
			"Rename or remove either instance "
				"to resolve the conflict",
				arg->expr->Identifier,
				arg->expr->_lineno,
				arg->mod->ModuleName,
				tmparg.expr->Identifier,
				tmparg.expr->_lineno,
				tmparg.mod->ModuleName,
				diff_files ? " (" : "",
				diff_files ? tmparg.mod->source_file_name : "",
				diff_files ? ")" : "");
			if(critical)
				return -1;
			RET2RVAL(1, rvalue);
		}
		if(tmparg.mod == arg->mod) break;
	}

	return rvalue;
}

static int
asn1f_apply_unique_index(arg_t *arg) {
	static int unique_index;
	if(!arg) { unique_index = 0; return 0; }

	arg->expr->_type_unique_index = ++unique_index;

	return 0;
}

/*
 * Print everything to stderr
 */
static void
_default_error_logger(int _severity, const char *fmt, ...) {
	va_list ap;
	char *pfx = "";

	switch(_severity) {
	case -1: pfx = "DEBUG: "; break;
	case 0: pfx = "WARNING: "; break;
	case 1: pfx = "FATAL: "; break;
	}
	
	fprintf(stderr, "%s", pfx);
	va_start(ap, fmt);
	vfprintf(stderr, fmt, ap);
	va_end(ap);
	fprintf(stderr, "\n");
}
