/* SPDX-License-Identifier: copyleft-next-0.3.1 */
/* Copyright 2023 Kim Kuparinen < kimi.h.kuparinen@gmail.com > */

#ifndef SCOPE_H
#define SCOPE_H

#include "ast.h"
#include "debug.h"

enum scope_flags {
	SCOPE_PUBLIC = (1 << 0),
	SCOPE_FILE = (1 << 1),
};

/* basic linked list for now, can probably be optimized into some kind of hash
 * table later */
struct visible {
	struct ast_node *node;
	struct scope *owner;
	struct visible *next;
};

struct scratch {
	struct ast_node *node;
	struct scratch *next;
};

struct actual {
	struct ast_node *node;
	struct actual *next;
};

struct callable {
	struct proc_node *root;
	struct ast_node *id;
	struct callable *next;
};

struct param_node {
	struct ast_node *type;
	struct proc_node *proc;
	struct param_node *next;
};

struct proc_node {
	struct param_node *primitives;

	struct param_node *referential;
	struct param_node *fallback;

	struct ast_node *proc;
};


struct scope {
	struct scope *parent;
	enum scope_flags flags;
	size_t number;

	struct file_ctx fctx;

	/* used by the parent to keep track of all its children */
	struct scope *next;
	struct scope *children;

	/* list of actualized functions, shared between all scopes in the
	 * compilation unit */
	struct actual *actuals;

	/* for temp stuff */
	struct scratch *scratch;

	/* types */
	/* TODO: add actualized types maybe? */
	struct visible *enums;
	struct visible *unions;
	struct visible *structs;
	struct visible *aliases;
	struct visible *builtins;
	struct visible *templates;

	/* basic variables */
	struct visible *vars;
	struct visible *macros;

	/* collect procedures */
	struct visible *procs;

	/* callables */
	struct callable *callable;
};

enum match_flags {
	MATCH_GLOBAL = (1 << 0),
	MATCH_CALL = (1 << 1),
};

struct scope *create_scope();
struct actual *create_actuals();
void destroy_actuals(struct actual *actuals);
void destroy_visible(struct scope *scope, struct visible *visible);
void destroy_scope(struct scope *scope);

struct scope *create_temp_scope(struct scope *parent);

int scope_add_defaults(struct scope *root);
/* not sure if this should be public */
void scope_destroy_defaults(struct scope *root);

int scope_add_scratch(struct scope *scope, struct ast_node *scratch);

void scope_set_flags(struct scope *scope, enum scope_flags flags);
int scope_flags(struct scope *scope, enum scope_flags flags);

void scope_add_scope(struct scope *parent, struct scope *child);

int scope_add_actual(struct scope *scope, struct ast_node *node);
/* sets a node's owner as scope, and as needed propagates references to public
 * things up the file scope chain. */
int scope_add_var(struct scope *scope, struct ast_node *var);
int scope_add_type(struct scope *scope, struct ast_node *type);
int scope_add_proc(struct scope *scope, struct ast_node *proc);
int scope_add_macro(struct scope *scope, struct ast_node *macro);
int scope_add_alias(struct scope *scope, struct ast_node *alias);
int scope_add_template(struct scope *scope, struct ast_node *type_template);

int scope_add_existing_var(struct scope *scope, struct visible *var);
int scope_add_existing_proc(struct scope *scope, struct visible *proc);

/* actuals are global, so technically no need for a file_* */
struct ast_node *scope_find_actual(struct scope *scope, struct ast_node *id);

struct ast_node *scope_find(struct scope *scope, struct ast_node *id);
struct ast_node *scope_find_var(struct scope *scope, struct ast_node *id);
struct ast_node *scope_find_type(struct scope *scope, struct ast_node *id);
struct ast_node *scope_find_proc(struct scope *scope, struct ast_node *id);
struct ast_node *scope_find_macro(struct scope *scope, struct ast_node *id);
struct ast_node *scope_find_alias(struct scope *scope, struct ast_node *id);
struct ast_node *scope_find_template(struct scope *scope, struct ast_node *id);

struct ast_node *file_scope_find(struct scope *scope, struct ast_node *id);
struct ast_node *file_scope_find_var(struct scope *scope, struct ast_node *id);
struct ast_node *file_scope_find_type(struct scope *scope, struct ast_node *id);
struct ast_node *file_scope_find_proc(struct scope *scope, struct ast_node *id);
struct ast_node *file_scope_find_macro(struct scope *scope,
                                       struct ast_node *id);
struct ast_node *file_scope_find_alias(struct scope *scope,
                                       struct ast_node *id);
struct ast_node *file_scope_find_override(struct scope *scope,
                                          struct ast_node *id);
struct ast_node *file_scope_find_template(struct scope *scope,
                                          struct ast_node *id);

struct ast_node *scope_resolve_arr(struct scope *scope, struct ast_node *call);
struct ast_node *scope_resolve_macro(struct scope *scope,
                                     struct ast_node *call);
struct ast_node *scope_resolve_actual(struct scope *scope,
                                      struct ast_node *call);
struct ast_node *scope_resolve_proc(struct scope *scope, struct ast_node *call);
struct ast_node *scope_resolve_call(struct scope *scope, struct ast_node *call);

struct ast_node *scope_resolve_type(struct scope *scope, struct ast_node *type);
struct ast_node *file_scope_resolve_type(struct scope *scope,
                                         struct ast_node *type);

struct ast_node *file_scope_resolve_call(struct scope *scope,
                                         struct ast_node *call);
int implements(enum match_flags flags, struct scope *scope,
               struct ast_node *arg_type,
               struct ast_node *param_type);
int primitive_type(struct ast_node *type);
int fully_qualified(struct ast_node *type);

#endif /* SCOPE_H */
