/*
    This file is part of TON Blockchain Library.
    TON Blockchain Library is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 2 of the License, or
    (at your option) any later version.
    TON Blockchain Library 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 Lesser General Public License for more details.
    You should have received a copy of the GNU Lesser General Public License
    along with TON Blockchain Library.  If not, see .
*/
#pragma once
#include "src-file.h"
#include "fwd-declarations.h"
#include "constant-evaluator.h"
#include "crypto/common/refint.h"
#include 
#include 
#include 
namespace tolk {
struct Symbol {
  std::string name;
  SrcLocation loc;
  Symbol(std::string name, SrcLocation loc)
    : name(std::move(name))
    , loc(loc) {
  }
  virtual ~Symbol() = default;
  template
  const T* as() const {
#ifdef TOLK_DEBUG
    assert(dynamic_cast(this) != nullptr);
#endif
    return dynamic_cast(this);
  }
  template
  const T* try_as() const {
    return dynamic_cast(this);
  }
};
struct LocalVarData final : Symbol {
  enum {
    flagMutateParameter = 1,    // parameter was declared with `mutate` keyword
    flagImmutable = 2,          // variable was declared via `val` (not `var`)
  };
  TypePtr declared_type;            // either at declaration `var x:int`, or if omitted, from assigned value `var x=2`
  int flags;
  int param_idx;                    // 0...N for function parameters, -1 for local vars
  std::vector ir_idx;
  LocalVarData(std::string name, SrcLocation loc, TypePtr declared_type, int flags, int param_idx)
    : Symbol(std::move(name), loc)
    , declared_type(declared_type)
    , flags(flags)
    , param_idx(param_idx) {
  }
  bool is_parameter() const { return param_idx >= 0; }
  bool is_immutable() const { return flags & flagImmutable; }
  bool is_mutate_parameter() const { return flags & flagMutateParameter; }
  LocalVarData* mutate() const { return const_cast(this); }
  void assign_ir_idx(std::vector&& ir_idx);
  void assign_resolved_type(TypePtr declared_type);
  void assign_inferred_type(TypePtr inferred_type);
};
struct FunctionBodyCode;
struct FunctionBodyAsm;
struct FunctionBodyBuiltin;
struct GenericsDeclaration;
struct GenericsInstantiation;
typedef std::variant<
  FunctionBodyCode*,
  FunctionBodyAsm*,
  FunctionBodyBuiltin*
> FunctionBody;
struct FunctionData final : Symbol {
  static constexpr int EMPTY_METHOD_ID = -10;
  enum {
    flagInline = 1,             // marked `@inline`
    flagInlineRef = 2,          // marked `@inline_ref`
    flagTypeInferringDone = 4,  // type inferring step of function's body (all AST nodes assigning v->inferred_type) is done
    flagUsedAsNonCall = 8,      // used not only as `f()`, but as a 1-st class function (assigned to var, pushed to tuple, etc.)
    flagMarkedAsPure = 16,      // declared as `pure`, can't call impure and access globals, unused invocations are optimized out
    flagImplicitReturn = 32,    // control flow reaches end of function, so it needs implicit return at the end
    flagGetMethod = 64,         // was declared via `get func(): T`, method_id is auto-assigned
    flagIsEntrypoint = 128,     // it's `main` / `onExternalMessage` / etc.
    flagHasMutateParams = 256,  // has parameters declared as `mutate`
    flagAcceptsSelf = 512,      // is a member function (has `self` first parameter)
    flagReturnsSelf = 1024,     // return type is `self` (returns the mutated 1st argument), calls can be chainable
    flagReallyUsed = 2048,      // calculated via dfs from used functions; declared but unused functions are not codegenerated
  };
  int method_id = EMPTY_METHOD_ID;
  int flags;
  std::vector parameters;
  std::vector arg_order, ret_order;
  TypePtr declared_return_type;               // may be nullptr, meaning "auto infer"
  TypePtr inferred_return_type = nullptr;     // assigned on type inferring
  TypePtr inferred_full_type = nullptr;       // assigned on type inferring, it's TypeDataFunCallable(params -> return)
  const GenericsDeclaration* genericTs;
  const GenericsInstantiation* instantiationTs;
  FunctionBody body;
  AnyV ast_root;                                            // V for user-defined (not builtin)
  FunctionData(std::string name, SrcLocation loc, TypePtr declared_return_type, std::vector parameters, int initial_flags, const GenericsDeclaration* genericTs, const GenericsInstantiation* instantiationTs, FunctionBody body, AnyV ast_root)
    : Symbol(std::move(name), loc)
    , flags(initial_flags)
    , parameters(std::move(parameters))
    , declared_return_type(declared_return_type)
    , genericTs(genericTs)
    , instantiationTs(instantiationTs)
    , body(body)
    , ast_root(ast_root) {
  }
  std::string as_human_readable() const;
  const std::vector* get_arg_order() const {
    return arg_order.empty() ? nullptr : &arg_order;
  }
  const std::vector* get_ret_order() const {
    return ret_order.empty() ? nullptr : &ret_order;
  }
  int get_num_params() const { return static_cast(parameters.size()); }
  const LocalVarData& get_param(int idx) const { return parameters[idx]; }
  bool is_code_function() const { return std::holds_alternative(body); }
  bool is_asm_function() const { return std::holds_alternative(body); }
  bool is_builtin_function() const { return ast_root == nullptr; }
  bool is_generic_function() const { return genericTs != nullptr; }
  bool is_instantiation_of_generic_function() const { return instantiationTs != nullptr; }
  bool is_inline() const { return flags & flagInline; }
  bool is_inline_ref() const { return flags & flagInlineRef; }
  bool is_type_inferring_done() const { return flags & flagTypeInferringDone; }
  bool is_used_as_noncall() const { return flags & flagUsedAsNonCall; }
  bool is_marked_as_pure() const { return flags & flagMarkedAsPure; }
  bool is_implicit_return() const { return flags & flagImplicitReturn; }
  bool is_get_method() const { return flags & flagGetMethod; }
  bool is_method_id_not_empty() const { return method_id != EMPTY_METHOD_ID; }
  bool is_entrypoint() const { return flags & flagIsEntrypoint; }
  bool has_mutate_params() const { return flags & flagHasMutateParams; }
  bool does_accept_self() const { return flags & flagAcceptsSelf; }
  bool does_return_self() const { return flags & flagReturnsSelf; }
  bool does_mutate_self() const { return (flags & flagAcceptsSelf) && parameters[0].is_mutate_parameter(); }
  bool is_really_used() const { return flags & flagReallyUsed; }
  bool does_need_codegen() const;
  FunctionData* mutate() const { return const_cast(this); }
  void assign_resolved_type(TypePtr declared_return_type);
  void assign_inferred_type(TypePtr inferred_return_type, TypePtr inferred_full_type);
  void assign_is_used_as_noncall();
  void assign_is_implicit_return();
  void assign_is_type_inferring_done();
  void assign_is_really_used();
  void assign_arg_order(std::vector&& arg_order);
};
struct GlobalVarData final : Symbol {
  enum {
    flagReallyUsed = 1,          // calculated via dfs from used functions; unused globals are not codegenerated
  };
  TypePtr declared_type; // always exists, declaring globals without type is prohibited
  int flags = 0;
  GlobalVarData(std::string name, SrcLocation loc, TypePtr declared_type)
    : Symbol(std::move(name), loc)
    , declared_type(declared_type) {
  }
  bool is_really_used() const { return flags & flagReallyUsed; }
  GlobalVarData* mutate() const { return const_cast(this); }
  void assign_resolved_type(TypePtr declared_type);
  void assign_is_really_used();
};
struct GlobalConstData final : Symbol {
  ConstantValue value;
  TypePtr declared_type; // may be nullptr
  GlobalConstData(std::string name, SrcLocation loc, TypePtr declared_type, ConstantValue&& value)
    : Symbol(std::move(name), loc)
    , value(std::move(value))
    , declared_type(declared_type) {
  }
  bool is_int_const() const { return value.is_int(); }
  bool is_slice_const() const { return value.is_slice(); }
  td::RefInt256 as_int_const() const { return value.as_int(); }
  const std::string& as_slice_const() const { return value.as_slice(); }
  GlobalConstData* mutate() const { return const_cast(this); }
  void assign_resolved_type(TypePtr declared_type);
};
class GlobalSymbolTable {
  std::unordered_map entries;
  static uint64_t key_hash(std::string_view name_key) {
    return std::hash{}(name_key);
  }
public:
  void add_function(const FunctionData* f_sym);
  void add_global_var(const GlobalVarData* g_sym);
  void add_global_const(const GlobalConstData* c_sym);
  const Symbol* lookup(std::string_view name) const {
    const auto it = entries.find(key_hash(name));
    return it == entries.end() ? nullptr : it->second;
  }
};
const Symbol* lookup_global_symbol(std::string_view name);
}  // namespace tolk