#pragma once

// Copyright (c) 2020 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by a LGPLv3.0
// a copy of which can be found in the LICENSE file.


#include "compiler_internal.h"

#define INT5_MAX         15
#define INT12_MAX        2047
#define INT20_MAX        524287
#define INT5_MIN         -16
#define INT12_MIN        -2048
#define INT20_MIN        (-INT20_MAX-1)
#define UINT5_MAX         31
#define UINT12_MAX        4095
#define UINT20_MAX        1048575U

#define SEMA_WARN(_node, level_, ...) (sema_warn_at(context, (_node)->loc, compiler.build.warnings. level_  , __VA_ARGS__))
#define SEMA_ERROR(_node, ...) sema_error_at(context, (_node)->loc, __VA_ARGS__)
#define RETURN_SEMA_ERROR(_node, ...) do { sema_error_at(context, (_node)->loc, __VA_ARGS__); return false; } while (0)
#define RETURN_VAL_SEMA_ERROR(val__, _node, ...) do { sema_error_at(context, (_node)->loc, __VA_ARGS__); return (val__); } while (0)
#define RETURN_NULL_SEMA_ERROR(_node, ...) do { sema_error_at(context, (_node)->loc, __VA_ARGS__); return NULL; } while (0)
#define RETURN_SEMA_ERROR_AT(span__, ...) do { sema_error_at(context, span__, __VA_ARGS__); return false; } while (0)
#define SCOPE_OUTER_START(span__) do { DynamicScope stored_scope = context->active_scope; context_change_scope_with_flags(context, SCOPE_NONE, span__);
#define SCOPE_OUTER_END ASSERT(context->active_scope.defer_last == context->active_scope.defer_start); context->active_scope = stored_scope; } while(0)
#define SCOPE_START(span__) SCOPE_START_WITH_FLAGS(SCOPE_NONE, span__)
#define SCOPE_START_WITH_FLAGS(flags, span__) do { DynamicScope old_scope = context->active_scope; context_change_scope_with_flags(context, flags, span__);
#define SCOPE_START_WITH_LABEL(label, span__) do { DynamicScope old_scope = context->active_scope; context_change_scope_for_label(context, label, span__);
#define SCOPE_END ASSERT(context->active_scope.defer_last == context->active_scope.defer_start); context->active_scope = old_scope; } while(0)
#define SCOPE_POP_ERROR() ((bool)(context->active_scope = old_scope, false))
#define SCOPE_ERROR_END_OUTER() do { context->active_scope = stored_scope; } while(0)
#define PUSH_Y(ast, X) JumpTarget _old_##X = context->X##_jump; context->X##_jump = (JumpTarget) { ast, context->active_scope.defer_last }
#define POP_Y(X) context->X##_jump = _old_##X;
#define PUSH_CONTINUE(ast) PUSH_Y(ast, continue)
#define POP_CONTINUE() POP_Y(continue)
#define PUSH_BREAK(ast) PUSH_Y(ast, break)
#define POP_BREAK() POP_Y(break)
#define PUSH_NEXT(ast, sast) PUSH_Y(ast, next); Ast *_old_next_switch = context->next_switch; context->next_switch = sast
#define POP_NEXT() POP_Y(next); context->next_switch = _old_next_switch
#define PUSH_BREAKCONT(ast) PUSH_CONTINUE(ast); PUSH_BREAK(ast)
#define POP_BREAKCONT() POP_CONTINUE(); POP_BREAK()
#define CHECK_ON_DEFINED(ref__) do { if (!ref__) break; *ref__ = true; return false; } while(0)
#define SET_JUMP_END(context__, node__) do { (context__)->active_scope.end_jump = (EndJump) { true, (node__)->loc }; } while(0)

Decl **global_context_acquire_locals_list(void);
void generic_context_release_locals_list(Decl **);
const char *context_filename(SemaContext *context);

AstId context_get_defers(SemaContext *context, AstId defer_bottom, bool is_success);
void context_pop_defers(SemaContext *context, AstId *next);
void context_pop_defers_and_replace_ast(SemaContext *context, Ast *ast);
void context_change_scope_for_label(SemaContext *context, DeclId label_id, SourceLocId loc);
void context_change_scope_with_flags(SemaContext *context, ScopeFlags flags, SourceLocId loc);
SemaContext *context_transform_for_eval(SemaContext *context, SemaContext *temp_context, CompilationUnit *eval_unit);

TokenType sema_splitpathref(const char *string, ArraySize len, Path **path_ref, const char **ident_ref);
void sema_print_inline(SemaContext *context, SourceLocId original);
void sema_error_at(SemaContext *context, SourceLocId loc, const char *message, ...);
bool sema_warn_at(SemaContext *context, SourceLocId loc, WarningLevel level, const char *message, ...);

void sema_context_init(SemaContext *context, CompilationUnit *unit);
void sema_context_destroy(SemaContext *context);
unsigned sema_context_push_ct_stack(SemaContext *context);
void sema_context_pop_ct_stack(SemaContext *context, unsigned old_state);

bool sema_analyse_function_body(SemaContext *context, Decl *func);
bool sema_analyse_contracts(SemaContext *context, Decl *contracts, Expr **requires, Expr **ensures, AstId **asserts, SourceLocId call_loc, bool *has_ensures);
void sema_append_contract_asserts(AstId assert_first, Ast* compound_stmt);

Decl *sema_create_runner_main(SemaContext *context, Decl *decl);

void sema_analyse_pass_module_hierarchy(Module *module);
void sema_analysis_pass_process_imports(Module *module);
void sema_analysis_pass_register_global_declarations(Module *module);
void sema_analysis_pass_process_includes(Module *module);
void sema_analysis_pass_register_conditional_units(Module *module);
void sema_analysis_pass_register_conditional_declarations(Module *module);
void sema_analysis_pass_process_methods(Module *module, bool process_generic);
void sema_analysis_pass_decls(Module *module);
void sema_analysis_pass_ct_assert(Module *module);
void sema_analysis_pass_ct_echo(Module *module);
void sema_analysis_pass_functions(Module *module);
void sema_analysis_pass_interface_and_weak_sym(Module *module);

void sema_analysis_pass_lambda(Module *module);
void sema_analyze_stage(Module *module, AnalysisStage stage);
void sema_trace_liveness(void);

Expr *sema_expr_resolve_access_child(SemaContext *context, Expr *child, bool *missing);

bool sema_analyse_expr_lvalue(SemaContext *context, Expr *expr, bool *failed_ref);

bool sema_analyse_expr(SemaContext *context, Expr *expr);
Expr *expr_access_inline_member(Expr *parent, Decl *parent_decl);
void expr_set_to_ref(Expr *expr);
bool sema_analyse_ct_expr(SemaContext *context, Expr *expr);
Decl *sema_find_typed_operator(SemaContext *context, OperatorOverload operator_overload, SourceLocId loc, Expr *lhs, Expr *rhs, bool *reverse);
OverloadMatch sema_find_typed_operator_type(SemaContext *context, OperatorOverload operator_overload, OverloadType overload_type, Type *lhs_type, Type *rhs_type, Expr *rhs, Decl **candidate_ref, OverloadMatch last_match, Decl **ambiguous_ref);
BoolErr sema_type_has_equality_overload(SemaContext *context, Type *type);
BoolErr sema_type_can_check_equality_with_overload(SemaContext *context, Type *type);
Decl *sema_find_untyped_operator(Type *type, OperatorOverload operator_overload, Decl *skipped);
bool sema_insert_method_call(SemaContext *context, Expr *method_call, Decl *method_decl, Expr *parent, Expr **arguments, bool reverse_overload);
bool sema_expr_analyse_builtin_call(SemaContext *context, Expr *expr);
void sema_add_methods_to_decl_stack(SemaContext *context, Decl *decl);

bool sema_expr_analyse_macro_call(SemaContext *context, Expr *call_expr, Expr *struct_var, Decl *decl, bool call_var_optional, bool *no_match_ref);
Expr *sema_expr_analyse_ct_arg_index(SemaContext *context, Expr *index_expr, unsigned *index_ref);

Expr *sema_ct_eval_expr(SemaContext *context, CtEvalKind eval_kind, Expr *inner, bool report_missing);
Expr *sema_resolve_string_ident(SemaContext *context, Expr *inner, bool report_missing);
bool sema_analyse_asm(SemaContext *context, AsmInlineBlock *block, Ast *asm_stmt);
bool sema_expr_analyse_sprintf(SemaContext *context, Expr *expr, Expr *format_string, Expr **args, unsigned num_args);
bool sema_check_swizzle_string(SemaContext *context, Expr *expr, const char *kw, unsigned len, unsigned vec_len, bool *is_overlapping_ref, int *index_ref);
bool sema_kw_is_swizzle(const char *kw, unsigned len);
bool sema_bit_assignment_check(SemaContext *context, Expr *right, Decl *member, bool *failed_ref);
CondResult sema_check_comp_time_bool(SemaContext *context, Expr *expr);

bool sema_expr_check_assign(SemaContext *context, Expr *expr, bool *failed_ref);
bool sema_analyse_function_signature(SemaContext *context, Decl *func_decl, TypeInfo *parent, CallABI abi, Signature *signature);
Expr *sema_create_struct_from_expressions(Decl *struct_decl, SourceLocId loc, Expr **exprs);
ConstInitializer *sema_merge_bitstruct_const_initializers(ConstInitializer *lhs, ConstInitializer *rhs, BinaryOp op);
void sema_invert_bitstruct_const_initializer(ConstInitializer *initializer);
ArrayIndex sema_len_from_const(Expr *expr);
void cast_promote_vararg(Expr *arg);
Type *cast_numeric_arithmetic_promotion(Type *type);
void cast_to_int_to_max_bit_size(Expr *lhs, Expr *rhs, Type *left_type, Type *right_type);
bool sema_decl_if_cond(SemaContext *context, Decl *decl);
Decl *sema_generate_parameterized_identifier(SemaContext *context, Decl *generic, Decl *alias, Expr **params,
                                             Decl **param_decls, const char *suffix, const char *csuffix, SourceLocId invocation_loc, SourceLocId loc);
Decl *sema_analyse_parameterized_identifier(SemaContext *context, Path *decl_path, const char *name, SourceLocId loc,
                                            Expr **params, SourceLocId invocation_loc);
bool sema_parameterized_type_is_found(SemaContext *context, Path *decl_path, const char *name, SourceLocId loc);
Type *sema_resolve_type_get_func(Signature *signature, CallABI abi);
INLINE bool sema_set_alignment(SemaContext *context, Type *type, AlignSize *result, bool is_alloca);
INLINE bool sema_set_alloca_alignment(SemaContext *context, Type *type, AlignSize *result);
INLINE bool sema_display_deprecated_warning_on_use(SemaContext *context, Decl *decl, SourceLocId loc);
bool sema_expr_analyse_ct_concat(SemaContext *context, Expr *concat_expr, Expr *left, Expr *right, bool *failed_ref);
bool sema_analyse_attributes(SemaContext *context, Decl *decl, Attr **attrs, AttributeDomain domain, bool *erase_decl);

void unit_register_optional_global_decl(CompilationUnit *unit, Decl *decl);
bool analyse_func_body(SemaContext *context, Decl *decl);
bool sema_check_interfaces(SemaContext *context, Decl *decl);
bool sema_analyse_optional_returns(SemaContext *context, Decl *contract);

INLINE bool sema_analyse_stmt_chain(SemaContext *context, Ast *statement)
{
	if (!ast_ok(statement)) return false;
	AstId current = astid(statement);
	Ast *ast = NULL;
	bool all_ok = true;
	while (current)
	{
		ast = ast_next(&current);
		if (!sema_analyse_statement(context, ast))
		{
			ast_poison(ast);
			all_ok = false;
		}
	}
	return all_ok;
}

INLINE bool sema_analyse_func_macro(SemaContext *context, Decl *decl, AttributeDomain domain, bool *erase_decl)
{
	assert((domain & CALLABLE_TYPE) == domain);
	if (!sema_analyse_attributes(context, decl, decl->attributes, domain,
								 erase_decl)) return decl_poison(decl);
	if (decl_is_deprecated(decl)) context->call_env.ignore_deprecation = true;
	return true;
}

INLINE bool sema_check_left_right_const(SemaContext *context, Expr *left, Expr *right)
{
	if (!sema_cast_const(left)) RETURN_SEMA_ERROR(left, "Expected this to evaluate to a constant value.");
	if (!sema_cast_const(right)) RETURN_SEMA_ERROR(right, "Expected this to evaluate to a constant value.");
	return true;
}

INLINE bool sema_set_alignment(SemaContext *context, Type *type, AlignSize *result, bool is_alloca)
{
	type = type->canonical;
	if (type_is_func_ptr(type))
	{
		*result = type_abi_alignment(type_voidptr);
		return true;
	}
	if (!sema_resolve_type_decl(context, type)) return false;
	*result = is_alloca ? type_alloca_alignment(type) : type_abi_alignment(type);
	return true;
}

INLINE bool sema_set_alloca_alignment(SemaContext *context, Type *type, AlignSize *result)
{
	if (!sema_resolve_type_decl(context, type)) return false;
	*result = type_alloca_alignment(type);
	return true;
}

INLINE Attr* attr_find_kind(Attr **attrs, AttributeType attr_type)
{
	FOREACH(Attr *, attr, attrs)
	{
		if (attr->attr_kind == attr_type) return attr;
	}
	return NULL;
}

INLINE bool sema_display_deprecated_warning_on_use(SemaContext *context, Decl *decl, SourceLocId loc)
{
	ASSERT(decl->resolve_status == RESOLVE_DONE);
	if (!decl_is_deprecated(decl)) return true;
	if (context->call_env.ignore_deprecation) return true;
	const char *msg = decl->attrs_resolved->deprecated;

	// Prevent multiple reports
	decl->attrs_resolved->deprecated = NULL;

	switch (compiler.build.warnings.deprecation)
	{
		case WARNING_NOT_SET:
			UNREACHABLE;
		case WARNING_SILENT:
			return true;
		case WARNING_WARN:
			if (msg[0])
			{
				sema_warning_at(loc, "'%s' is deprecated: %s.", decl->name, msg);
				return true;
			}
			sema_warning_at(loc, "'%s' is deprecated.", decl->name);
			return true;
		case WARNING_ERROR:
			if (msg[0])
			{
				print_error_at(loc, "'%s' is deprecated: %s.", decl->name, msg);
				return false;
			}
			print_error_at(loc, "'%s' is deprecated.", decl->name);
			return false;
	}
	UNREACHABLE
}

static inline IndexDiff range_const_len(Range *range)
{
	switch (range->range_type)
	{
		case RANGE_SINGLE_ELEMENT:
			UNREACHABLE;
		case RANGE_CONST_LEN:
			return range->const_end;
		case RANGE_CONST_END:
			return -1;
		case RANGE_CONST_RANGE:
			return range->len_index;
		case RANGE_DYNAMIC:
			break;
	}
	Expr *start = exprptr(range->start);
	Expr *end = exprptrzero(range->end);
	if (!end || !expr_is_const_int(end)) return -1;
	if (!int_fits(end->const_expr.ixx, TYPE_I32)) return -1;
	IndexDiff end_val = (IndexDiff)int_to_i64(end->const_expr.ixx);
	if (range->is_len) return end_val;
	if (!expr_is_const_int(start)) return -1;
	if (!int_fits(start->const_expr.ixx, TYPE_I32)) return -1;
	IndexDiff start_val = (IndexDiff)int_to_i64(start->const_expr.ixx);
	if (range->start_from_end && range->end_from_end) return start_val - end_val + 1;
	if (range->start_from_end != range->end_from_end) return -1;
	return end_val - start_val + 1;
}

static inline StorageType sema_resolve_storage_type(SemaContext *context, Type *type)
{
	if (!type) return STORAGE_NORMAL;
	bool is_distinct = false;
	RETRY:
	if (type == type_wildcard_optional) return STORAGE_WILDCARD;
	switch (type->type_kind)
	{
		case TYPE_VOID:
			return is_distinct ? STORAGE_UNKNOWN : STORAGE_VOID;
		case TYPE_WILDCARD:
			return STORAGE_WILDCARD;
		case TYPE_MEMBER:
		case TYPE_UNTYPED_LIST:
		case TYPE_TYPEINFO:
		case TYPE_FUNC_RAW:
			return STORAGE_COMPILE_TIME;
		case TYPE_OPTIONAL:
			type = type->optional;
			goto RETRY;
		case TYPE_ALIAS:
			if (!sema_analyse_decl(context, type->decl)) return false;
			type = type->canonical;
			goto RETRY;
		case TYPE_TYPEDEF:
			is_distinct = true;
			if (!sema_analyse_decl(context, type->decl)) return false;
			type = type->decl->distinct->type;
			goto RETRY;
		case TYPE_FLEXIBLE_ARRAY:
			return STORAGE_UNKNOWN;
		default:
			return STORAGE_NORMAL;
	}
}

static inline TypeProperty type_property_by_name(const char *name)
{
	for (unsigned i = 0; i < NUMBER_OF_TYPE_PROPERTIES; i++)
	{
		if (type_property_list[i] == name) return (TypeProperty)i;
	}
	return TYPE_PROPERTY_NONE;
}

