// Copyright (C) 2020-2023 Free Software Foundation, Inc.

// This file is part of GCC.

// GCC is free software; you can redistribute it and/or modify it under
// the terms of the GNU General Public License as published by the Free
// Software Foundation; either version 3, or (at your option) any later
// version.

// GCC is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
// for more details.

// You should have received a copy of the GNU General Public License
// along with GCC; see the file COPYING3.  If not see
// <http://www.gnu.org/licenses/>.

#ifndef RUST_COMPILE_CONTEXT
#define RUST_COMPILE_CONTEXT

#include "rust-system.h"
#include "rust-hir-map.h"
#include "rust-name-resolver.h"
#include "rust-hir-type-check.h"
#include "rust-backend.h"
#include "rust-hir-full.h"
#include "rust-mangle.h"
#include "rust-tree.h"

namespace Rust {
namespace Compile {

struct fncontext
{
  tree fndecl;
  ::Bvariable *ret_addr;
};

class Context
{
public:
  Context (::Backend *backend);

  void setup_builtins ();

  bool lookup_compiled_types (tree t, tree *type)
  {
    hashval_t h = type_hasher (t);
    auto it = compiled_type_map.find (h);
    if (it == compiled_type_map.end ())
      return false;

    *type = it->second;
    return true;
  }

  tree insert_compiled_type (tree type)
  {
    hashval_t h = type_hasher (type);
    auto it = compiled_type_map.find (h);
    if (it != compiled_type_map.end ())
      return it->second;

    compiled_type_map.insert ({h, type});
    push_type (type);
    return type;
  }

  tree insert_main_variant (tree type)
  {
    hashval_t h = type_hasher (type);
    auto it = main_variants.find (h);
    if (it != main_variants.end ())
      return it->second;

    main_variants.insert ({h, type});
    return type;
  }

  ::Backend *get_backend () { return backend; }
  Resolver::Resolver *get_resolver () { return resolver; }
  Resolver::TypeCheckContext *get_tyctx () { return tyctx; }
  Analysis::Mappings *get_mappings () { return mappings; }

  void push_block (tree scope)
  {
    scope_stack.push_back (scope);
    statements.push_back ({});
  }

  tree pop_block ()
  {
    auto block = scope_stack.back ();
    scope_stack.pop_back ();

    auto stmts = statements.back ();
    statements.pop_back ();

    backend->block_add_statements (block, stmts);

    return block;
  }

  tree peek_enclosing_scope ()
  {
    if (scope_stack.size () == 0)
      return nullptr;

    return scope_stack.back ();
  }

  void add_statement_to_enclosing_scope (tree stmt)
  {
    statements.at (statements.size () - 2).push_back (stmt);
  }

  void add_statement (tree stmt) { statements.back ().push_back (stmt); }

  void insert_var_decl (HirId id, ::Bvariable *decl)
  {
    compiled_var_decls[id] = decl;
  }

  bool lookup_var_decl (HirId id, ::Bvariable **decl)
  {
    auto it = compiled_var_decls.find (id);
    if (it == compiled_var_decls.end ())
      return false;

    *decl = it->second;
    return true;
  }

  void insert_function_decl (const TyTy::FnType *ref, tree fn)
  {
    auto id = ref->get_ty_ref ();
    auto dId = ref->get_id ();

    rust_assert (compiled_fn_map.find (id) == compiled_fn_map.end ());
    compiled_fn_map[id] = fn;

    auto it = mono_fns.find (dId);
    if (it == mono_fns.end ())
      mono_fns[dId] = {};

    mono_fns[dId].push_back ({ref, fn});
  }

  void insert_closure_decl (const TyTy::ClosureType *ref, tree fn)
  {
    auto dId = ref->get_def_id ();
    auto it = mono_closure_fns.find (dId);
    if (it == mono_closure_fns.end ())
      mono_closure_fns[dId] = {};

    mono_closure_fns[dId].push_back ({ref, fn});
  }

  tree lookup_closure_decl (const TyTy::ClosureType *ref)
  {
    auto dId = ref->get_def_id ();
    auto it = mono_closure_fns.find (dId);
    if (it == mono_closure_fns.end ())
      return error_mark_node;

    for (auto &i : it->second)
      {
	const TyTy::ClosureType *t = i.first;
	tree fn = i.second;

	if (ref->is_equal (*t))
	  return fn;
      }

    return error_mark_node;
  }

  bool lookup_function_decl (HirId id, tree *fn, DefId dId = UNKNOWN_DEFID,
			     const TyTy::BaseType *ref = nullptr,
			     const std::string &asm_name = std::string ())
  {
    // for for any monomorphized fns
    if (ref != nullptr)
      {
	rust_assert (dId != UNKNOWN_DEFID);

	auto it = mono_fns.find (dId);
	if (it == mono_fns.end ())
	  return false;

	for (auto &e : mono_fns[dId])
	  {
	    const TyTy::BaseType *r = e.first;
	    tree f = e.second;

	    if (ref->is_equal (*r))
	      {
		*fn = f;
		return true;
	      }

	    if (DECL_ASSEMBLER_NAME_SET_P (f) && !asm_name.empty ())
	      {
		tree raw = DECL_ASSEMBLER_NAME_RAW (f);
		const char *rptr = IDENTIFIER_POINTER (raw);

		bool lengths_match_p
		  = IDENTIFIER_LENGTH (raw) == asm_name.size ();
		if (lengths_match_p
		    && strncmp (rptr, asm_name.c_str (),
				IDENTIFIER_LENGTH (raw))
			 == 0)
		  {
		    *fn = f;
		    return true;
		  }
	      }
	  }
	return false;
      }

    auto it = compiled_fn_map.find (id);
    if (it == compiled_fn_map.end ())
      return false;

    *fn = it->second;
    return true;
  }

  void insert_const_decl (HirId id, tree expr) { compiled_consts[id] = expr; }

  bool lookup_const_decl (HirId id, tree *expr)
  {
    auto it = compiled_consts.find (id);
    if (it == compiled_consts.end ())
      return false;

    *expr = it->second;
    return true;
  }

  void insert_label_decl (HirId id, tree label) { compiled_labels[id] = label; }

  bool lookup_label_decl (HirId id, tree *label)
  {
    auto it = compiled_labels.find (id);
    if (it == compiled_labels.end ())
      return false;

    *label = it->second;
    return true;
  }

  void insert_pattern_binding (HirId id, tree binding)
  {
    implicit_pattern_bindings[id] = binding;
  }

  bool lookup_pattern_binding (HirId id, tree *binding)
  {
    auto it = implicit_pattern_bindings.find (id);
    if (it == implicit_pattern_bindings.end ())
      return false;

    *binding = it->second;
    return true;
  }

  void push_fn (tree fn, ::Bvariable *ret_addr)
  {
    fn_stack.push_back (fncontext{fn, ret_addr});
  }
  void pop_fn () { fn_stack.pop_back (); }

  bool in_fn () { return fn_stack.size () != 0; }

  // Note: it is undefined behavior to call peek_fn () if fn_stack is empty.
  fncontext peek_fn ()
  {
    rust_assert (!fn_stack.empty ());
    return fn_stack.back ();
  }

  void push_type (tree t) { type_decls.push_back (t); }
  void push_var (::Bvariable *v) { var_decls.push_back (v); }
  void push_const (tree c) { const_decls.push_back (c); }
  void push_function (tree f) { func_decls.push_back (f); }

  void write_to_backend ()
  {
    backend->write_global_definitions (type_decls, const_decls, func_decls,
				       var_decls);
  }

  bool function_completed (tree fn)
  {
    for (auto it = func_decls.begin (); it != func_decls.end (); it++)
      {
	tree i = (*it);
	if (i == fn)
	  {
	    return true;
	  }
      }
    return false;
  }

  void push_loop_context (Bvariable *var) { loop_value_stack.push_back (var); }

  Bvariable *peek_loop_context () { return loop_value_stack.back (); }

  Bvariable *pop_loop_context ()
  {
    auto back = loop_value_stack.back ();
    loop_value_stack.pop_back ();
    return back;
  }

  void push_loop_begin_label (tree label)
  {
    loop_begin_labels.push_back (label);
  }

  tree peek_loop_begin_label () { return loop_begin_labels.back (); }

  tree pop_loop_begin_label ()
  {
    tree pop = loop_begin_labels.back ();
    loop_begin_labels.pop_back ();
    return pop;
  }

  void push_const_context (void) { const_context++; }
  void pop_const_context (void)
  {
    if (const_context > 0)
      const_context--;
  }
  bool const_context_p (void) { return (const_context > 0); }

  std::string mangle_item (const TyTy::BaseType *ty,
			   const Resolver::CanonicalPath &path) const
  {
    return mangler.mangle_item (ty, path);
  }

  void push_closure_context (HirId id);
  void pop_closure_context ();
  void insert_closure_binding (HirId id, tree expr);
  bool lookup_closure_binding (HirId id, tree *expr);

  std::vector<tree> &get_type_decls () { return type_decls; }
  std::vector<::Bvariable *> &get_var_decls () { return var_decls; }
  std::vector<tree> &get_const_decls () { return const_decls; }
  std::vector<tree> &get_func_decls () { return func_decls; }

  static hashval_t type_hasher (tree type);

private:
  ::Backend *backend;
  Resolver::Resolver *resolver;
  Resolver::TypeCheckContext *tyctx;
  Analysis::Mappings *mappings;
  Mangler mangler;

  // state
  std::vector<fncontext> fn_stack;
  std::map<HirId, ::Bvariable *> compiled_var_decls;
  std::map<hashval_t, tree> compiled_type_map;
  std::map<HirId, tree> compiled_fn_map;
  std::map<HirId, tree> compiled_consts;
  std::map<HirId, tree> compiled_labels;
  std::vector<::std::vector<tree>> statements;
  std::vector<tree> scope_stack;
  std::vector<::Bvariable *> loop_value_stack;
  std::vector<tree> loop_begin_labels;
  std::map<DefId, std::vector<std::pair<const TyTy::BaseType *, tree>>>
    mono_fns;
  std::map<DefId, std::vector<std::pair<const TyTy::ClosureType *, tree>>>
    mono_closure_fns;
  std::map<HirId, tree> implicit_pattern_bindings;
  std::map<hashval_t, tree> main_variants;

  // closure bindings
  std::vector<HirId> closure_scope_bindings;
  std::map<HirId, std::map<HirId, tree>> closure_bindings;

  // To GCC middle-end
  std::vector<tree> type_decls;
  std::vector<::Bvariable *> var_decls;
  std::vector<tree> const_decls;
  std::vector<tree> func_decls;

  // Nonzero iff we are currently compiling something inside a constant context.
  unsigned int const_context = 0;
};

} // namespace Compile
} // namespace Rust

#endif // RUST_COMPILE_CONTEXT