/*
    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 .
    Copyright 2017-2020 Telegram Systems LLP
*/
#pragma once
namespace tlbc {
extern std::set forbidden_cpp_idents, local_forbidden_cpp_idents;
struct CppIdentSet {
  std::set cpp_idents;
  const std::set* extra_forbidden_idents;
  CppIdentSet(const std::set* forbid = nullptr) : extra_forbidden_idents(forbid) {
  }
  static std::string compute_cpp_ident(std::string orig_ident, int count = 0);
  std::string new_ident(std::string orig_ident, int count = 0, std::string suffix = "");
  bool insert(std::string ident) {
    return cpp_idents.insert(ident).second;
  }
  bool defined(std::string ident) {
    return cpp_idents.count(ident);
  }
  bool is_good_ident(std::string ident);
  void clear() {
    cpp_idents.clear();
  }
};
extern CppIdentSet global_cpp_ids;
struct Action {
  int fixed_size;
  bool is_pure;
  bool is_constraint;
  std::string action;
  Action(int _size) : fixed_size(_size), is_pure(false), is_constraint(false) {
  }
  Action(std::string _action, bool _cst = false)
      : fixed_size(-1), is_pure(false), is_constraint(_cst), action(_action) {
  }
  Action(const std::ostringstream& ss, bool _cst = false)
      : fixed_size(-1), is_pure(false), is_constraint(_cst), action(ss.str()) {
  }
  Action(std::ostringstream&& ss, bool _cst = false)
      : fixed_size(-1), is_pure(false), is_constraint(_cst), action(std::move(ss).str()) {
  }
  void show(std::ostream& os) const;
  bool may_combine(const Action& next) const;
  bool operator+=(const Action& next);
};
enum cpp_val_type {
  ct_unknown,
  ct_void = 1,
  ct_slice = 2,
  ct_cell = 3,
  ct_typeref = 4,
  ct_typeptr = 5,
  ct_bits = 6,
  ct_bitstring = 7,
  ct_integer = 8,
  ct_bool = 10,
  ct_enum = 11,
  ct_int32 = 12,
  ct_uint32 = 13,
  ct_int64 = 14,
  ct_uint64 = 15,
  ct_subrecord = 16
};
struct CppValType {
  cpp_val_type vt;
  int size;
  CppValType(cpp_val_type _vt = ct_unknown, int _size = -1) : vt(_vt), size(_size) {
  }
  cpp_val_type get() const {
    return vt;
  }
  void show(std::ostream& os, bool pass_value = false) const;
  bool needs_move() const;
};
extern std::ostream& operator<<(std::ostream& os, CppValType cvt);
class CppTypeCode {
  Type& type;
  bool ok;
  bool builtin;
  bool inline_get_tag;
  bool inline_skip;
  bool inline_validate_skip;
  bool simple_get_size;
  bool simple_cons_tags;
  bool incremental_cons_tags;
 public:
  int params;
  int tot_params;
  int ret_params;
  int cons_num;
  int common_cons_len;
  std::vector cons_enum_name;
  std::vector cons_enum_value;
  std::vector cons_tag_map;
  std::vector cons_tag_exact;
  std::vector cons_idx_by_enum;
  std::string cpp_type_var_name;
  std::string cpp_type_class_name;
  std::string cpp_type_template_name;
  struct ConsRecord;
  struct ConsField {
    const Field& field;
    const ConsRecord* subrec;
    std::string name;
    cpp_val_type ctype;
    int size;
    int orig_idx;
    bool implicit;
    ConsField(const Field& _field, std::string _name, cpp_val_type _ctype, int _size, int _idx,
              const ConsRecord* _subrec = nullptr, bool _implicit = false)
        : field(_field), subrec(_subrec), name(_name), ctype(_ctype), size(_size), orig_idx(_idx), implicit(_implicit) {
      assert(ctype != ct_subrecord || subrec);
    }
    CppValType get_cvt() const {
      return {ctype, size};
    }
    void print_type(std::ostream& os, bool pass_value = false) const;
  };
  struct ConsRecord {
    const CppTypeCode& cpp_type;
    const Constructor& constr;
    int cons_idx;
    bool is_trivial;
    bool is_small;
    bool triv_conflict;
    bool has_trivial_name;
    bool inline_record;
    bool declared;
    cpp_val_type equiv_cpp_type;
    std::vector equiv_cpp_types;
    std::string cpp_name;
    std::vector cpp_fields;
    ConsRecord(const CppTypeCode& _cpp_type, const Constructor& _constr, int idx, bool _triv = false)
        : cpp_type(_cpp_type), constr(_constr), cons_idx(idx), is_trivial(_triv), declared(false) {
    }
    bool recover_idents(CppIdentSet& idents) const;
    void declare_record(std::ostream& os, std::string nl, int options);
    bool declare_record_unpack(std::ostream& os, std::string nl, int options);
    bool declare_record_pack(std::ostream& os, std::string nl, int options);
    void print_full_name(std::ostream& os) const;
  };
  std::vector records;
 private:
  std::vector type_param_name;
  std::vector type_param_is_nat;
  std::vector type_param_is_neg;
  std::string template_args;
  std::string constructor_args;
  std::string skip_extra_args;
  std::string skip_extra_args_pass;
  CppIdentSet local_cpp_ids;
  bool init();
 public:
  CppTypeCode(Type& _type) : type(_type), local_cpp_ids(&local_forbidden_cpp_idents) {
    ok = init();
  }
  bool is_ok() const {
    return ok;
  }
  void generate(std::ostream& os, int options = 0);
 private:
  bool compute_simple_cons_tags();
  bool check_incremental_cons_tags() const;
  unsigned long long compute_selector_mask() const;
  void assign_class_name();
  void assign_cons_names();
  void assign_class_field_names();
  void assign_cons_values();
  void assign_record_cons_names();
  void generate_cons_enum(std::ostream& os);
  void generate_type_constructor(std::ostream& os, int options);
  void generate_type_fields(std::ostream& os, int options);
  void generate_header(std::ostream& os, int options = 0);
  void generate_body(std::ostream& os, int options = 0);
  void generate_cons_len_array(std::ostream& os, std::string nl, int options = 0);
  void generate_cons_tag_array(std::ostream& os, std::string nl, int options = 0);
  void generate_cons_tag_info(std::ostream& os, std::string nl, int options = 0);
  void generate_skip_method(std::ostream& os, int options = 0);
  void generate_skip_cons_method(std::ostream& os, std::string nl, int cidx, int options);
  void generate_cons_tag_check(std::ostream& os, std::string nl, int cidx, bool force = false);
  void generate_check_tag_method(std::ostream& os);
  void generate_unpack_method(std::ostream& os, ConsRecord& rec, int options);
  void generate_pack_method(std::ostream& os, ConsRecord& rec, int options);
  void generate_ext_fetch_to(std::ostream& os, int options);
  void generate_fetch_enum_method(std::ostream& os, int options);
  void generate_store_enum_method(std::ostream& os, int options);
  void generate_print_type_body(std::ostream& os, std::string nl);
  void generate_print_method(std::ostream& os, int options = 0);
  void generate_print_cons_method(std::ostream& os, std::string nl, int cidx, int options);
  void generate_get_tag_body(std::ostream& os, std::string nl);
  void generate_get_tag_subcase(std::ostream& os, std::string nl, const BinTrie* trie, int depth) const;
  void generate_get_tag_param(std::ostream& os, std::string nl, unsigned long long tag,
                              unsigned long long params = std::numeric_limits::max()) const;
  void generate_get_tag_param1(std::ostream& os, std::string nl, const char A[4],
                               const std::string param_names[1]) const;
  void generate_get_tag_param2(std::ostream& os, std::string nl, const char A[4][4],
                               const std::string param_names[2]) const;
  void generate_get_tag_param3(std::ostream& os, std::string nl, const char A[4][4][4],
                               const std::string param_names[3]) const;
  bool match_param_pattern(std::ostream& os, std::string nl, const char A[4], int mask, std::string pattern,
                           std::string param_name) const;
  std::string get_nat_param_name(int idx) const;
  void generate_tag_pfx_selector(std::ostream& os, std::string nl, const BinTrie& trie, int d, int min_size) const;
  bool generate_get_tag_pfx_distinguisher(std::ostream& os, std::string nl, const std::vector& constr_list,
                                          bool in_block) const;
 private:
  std::vector actions;
  int incomplete;
  int tmp_ints;
  bool needs_tmp_cell;
  std::vector tmp_vars;
  std::vector field_vars;
  std::vector field_var_set;
  std::vector param_var_set;
  std::vector param_constraint_used;
  std::vector> postponed_equate;
  CppIdentSet tmp_cpp_ids;
  void clear_context();
  void init_cons_context(const Constructor& constr);
  std::string new_tmp_var(std::string hint);
  std::string new_tmp_var();
  void add_action(const Action& act);
  void output_actions(std::ostream& os, std::string nl, int options);
  void output_cpp_expr(std::ostream& os, const TypeExpr* expr, int prio = 0, bool allow_type_neg = false) const;
  void output_cpp_sizeof_expr(std::ostream& os, const TypeExpr* expr, int prio) const;
  void output_negative_type_arguments(std::ostream& os, const TypeExpr* expr);
  bool can_compute(const TypeExpr* expr) const;
  bool can_use_to_compute(const TypeExpr* expr, int i) const;
  bool can_compute_sizeof(const TypeExpr* expr) const;
  bool is_self(const TypeExpr* expr, const Constructor& constr) const;
  void add_compute_actions(const TypeExpr* expr, int i, std::string bind_to);
  void identify_cons_params(const Constructor& constr, int options);
  void identify_cons_neg_params(const Constructor& constr, int options);
  void bind_record_fields(const ConsRecord& rec, int options);
  void add_cons_tag_check(const Constructor& constr, int cidx, int options);
  void add_cons_tag_store(const Constructor& constr, int cidx);
  std::string add_fetch_nat_field(const Constructor& constr, const Field& field, int options);
  void add_store_nat_field(const Constructor& constr, const Field& field, int options);
  void add_remaining_param_constraints_check(const Constructor& constr, int options);
  void compute_implicit_field(const Constructor& constr, const Field& field, int options);
  bool add_constraint_check(const Constructor& constr, const Field& field, int options);
  void add_postponed_equate_actions();
  void output_fetch_field(std::ostream& os, std::string field_name, const TypeExpr* expr, cpp_val_type cvt);
  void output_fetch_subrecord(std::ostream& os, std::string field_name, const ConsRecord* subrec);
  void output_store_field(std::ostream& os, std::string field_name, const TypeExpr* expr, cpp_val_type cvt);
  void add_store_subrecord(std::string field_name, const ConsRecord* subrec);
  void generate_skip_field(const Constructor& constr, const Field& field, int options);
  void generate_print_field(const Constructor& constr, const Field& field, int options);
  bool output_print_simple_field(std::ostream& os, const Field& field, std::string field_name, const TypeExpr* expr);
  void generate_unpack_field(const ConsField& fi, const Constructor& constr, const Field& field, int options);
  void generate_pack_field(const ConsField& fi, const Constructor& constr, const Field& field, int options);
};
extern std::vector> cpp_type;
extern bool add_type_members;
}  // namespace tlbc