/*
    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
#ifdef TOLK_DEBUG
#include "ast.h"
#include "ast-visitor.h"
#include "type-system.h"
#include 
/*
 *   ASTStringifier is used to print out the whole vertex tree in a human-readable format.
 *   To stringify any vertex, call v->debug_print(), which uses this class.
 */
namespace tolk {
class ASTStringifier final : public ASTVisitor {
  constexpr static std::pair name_pairs[] = {
    {ast_identifier, "ast_identifier"},
    // expressions
    {ast_empty_expression, "ast_empty_expression"},
    {ast_parenthesized_expression, "ast_parenthesized_expression"},
    {ast_tensor, "ast_tensor"},
    {ast_typed_tuple, "ast_typed_tuple"},
    {ast_reference, "ast_reference"},
    {ast_local_var_lhs, "ast_local_var_lhs"},
    {ast_local_vars_declaration, "ast_local_vars_declaration"},
    {ast_int_const, "ast_int_const"},
    {ast_string_const, "ast_string_const"},
    {ast_bool_const, "ast_bool_const"},
    {ast_null_keyword, "ast_null_keyword"},
    {ast_argument, "ast_argument"},
    {ast_argument_list, "ast_argument_list"},
    {ast_dot_access, "ast_dot_access"},
    {ast_function_call, "ast_function_call"},
    {ast_underscore, "ast_underscore"},
    {ast_assign, "ast_assign"},
    {ast_set_assign, "ast_set_assign"},
    {ast_unary_operator, "ast_unary_operator"},
    {ast_binary_operator, "ast_binary_operator"},
    {ast_ternary_operator, "ast_ternary_operator"},
    {ast_cast_as_operator, "ast_cast_as_operator"},
    // statements
    {ast_empty_statement, "ast_empty_statement"},
    {ast_sequence, "ast_sequence"},
    {ast_return_statement, "ast_return_statement"},
    {ast_if_statement, "ast_if_statement"},
    {ast_repeat_statement, "ast_repeat_statement"},
    {ast_while_statement, "ast_while_statement"},
    {ast_do_while_statement, "ast_do_while_statement"},
    {ast_throw_statement, "ast_throw_statement"},
    {ast_assert_statement, "ast_assert_statement"},
    {ast_try_catch_statement, "ast_try_catch_statement"},
    {ast_asm_body, "ast_asm_body"},
    // other
    {ast_genericsT_item, "ast_genericsT_item"},
    {ast_genericsT_list, "ast_genericsT_list"},
    {ast_instantiationT_item, "ast_instantiationT_item"},
    {ast_instantiationT_list, "ast_instantiationT_list"},
    {ast_parameter, "ast_parameter"},
    {ast_parameter_list, "ast_parameter_list"},
    {ast_annotation, "ast_annotation"},
    {ast_function_declaration, "ast_function_declaration"},
    {ast_global_var_declaration, "ast_global_var_declaration"},
    {ast_constant_declaration, "ast_constant_declaration"},
    {ast_tolk_required_version, "ast_tolk_required_version"},
    {ast_import_directive, "ast_import_directive"},
    {ast_tolk_file, "ast_tolk_file"},
  };
  static_assert(std::size(name_pairs) == ast_tolk_file + 1, "name_pairs needs to be updated");
  constexpr static std::pair annotation_kinds[] = {
    {AnnotationKind::inline_simple, "@inline"},
    {AnnotationKind::inline_ref, "@inline_ref"},
    {AnnotationKind::method_id, "@method_id"},
    {AnnotationKind::pure, "@pure"},
    {AnnotationKind::deprecated, "@deprecated"},
  };
  static_assert(std::size(annotation_kinds) == static_cast(AnnotationKind::unknown), "annotation_kinds needs to be updated");
  template
  constexpr static const char* ast_node_type_to_string() {
    return name_pairs[node_type].second;
  }
  int depth = 0;
  std::string out;
  bool colored = false;
  template
  void handle_vertex(V v) {
    out += std::string(depth * 2, ' ');
    out += ast_node_type_to_string();
    if (std::string postfix = specific_str(v); !postfix.empty()) {
      out += colored ? "  \x1b[34m" : " // ";
      out += postfix;
      out += colored ? "\x1b[0m" : "";
    }
    out += '\n';
    depth++;
    visit_children(v);
    depth--;
  }
  static std::string specific_str(AnyV v) {
    switch (v->type) {
      case ast_identifier:
        return static_cast(v->as()->name);
      case ast_reference: {
        std::string result(v->as()->get_name());
        if (v->as()->has_instantiationTs()) {
          result += specific_str(v->as()->get_instantiationTs());
        }
        return result;
      }
      case ast_int_const:
        return static_cast(v->as()->orig_str);
      case ast_string_const:
        if (char modifier = v->as()->modifier) {
          return "\"" + static_cast(v->as()->str_val) + "\"" + std::string(1, modifier);
        } else {
          return "\"" + static_cast(v->as()->str_val) + "\"";
        }
      case ast_bool_const:
        return v->as()->bool_val ? "true" : "false";
      case ast_dot_access: {
        std::string result = "." + static_cast(v->as()->get_field_name());
        if (v->as()->has_instantiationTs()) {
          result += specific_str(v->as()->get_instantiationTs());
        }
        return result;
      }
      case ast_function_call: {
        std::string inner = specific_str(v->as()->get_callee());
        if (int n_args = v->as()->get_num_args()) {
          return inner + "(..."  + std::to_string(n_args) + ")";
        }
        return inner + "()";
      }
      case ast_global_var_declaration:
        return static_cast(v->as()->get_identifier()->name);
      case ast_constant_declaration:
        return static_cast(v->as()->get_identifier()->name);
      case ast_assign:
        return "=";
      case ast_set_assign:
        return static_cast(v->as()->operator_name) + "=";
      case ast_unary_operator:
        return static_cast(v->as()->operator_name);
      case ast_binary_operator:
        return static_cast(v->as()->operator_name);
      case ast_cast_as_operator:
        return v->as()->cast_to_type->as_human_readable();
      case ast_sequence:
        return "↓" + std::to_string(v->as()->get_items().size());
      case ast_instantiationT_item:
        return v->as()->substituted_type->as_human_readable();
      case ast_if_statement:
        return v->as()->is_ifnot ? "ifnot" : "";
      case ast_annotation:
        return annotation_kinds[static_cast(v->as()->kind)].second;
      case ast_parameter: {
        std::ostringstream os;
        os << v->as()->declared_type;
        return static_cast(v->as()->param_name) + ": " + os.str();
      }
      case ast_function_declaration: {
        std::string param_names;
        for (int i = 0; i < v->as()->get_num_params(); i++) {
          if (!param_names.empty())
            param_names += ",";
          param_names += v->as()->get_param(i)->param_name;
        }
        return "fun " + static_cast(v->as()->get_identifier()->name) + "(" + param_names + ")";
      }
      case ast_local_var_lhs: {
        std::ostringstream os;
        os << (v->as()->inferred_type ? v->as()->inferred_type : v->as()->declared_type);
        if (v->as()->get_name().empty()) {
          return "_: " + os.str();
        }
        return static_cast(v->as()->get_name()) + ":" + os.str();
      }
      case ast_instantiationT_list: {
        std::string result = "<";
        for (AnyV item : v->as()->get_items()) {
          if (result.size() > 1)
            result += ",";
          result += item->as()->substituted_type->as_human_readable();
        }
        return result + ">";
      }
      case ast_tolk_required_version:
        return static_cast(v->as()->semver);
      case ast_import_directive:
        return static_cast(v->as()->get_file_leaf()->str_val);
      case ast_tolk_file:
        return v->as()->file->rel_filename;
      default:
        return {};
    }
  }
public:
  explicit ASTStringifier(bool colored) : colored(colored) {
  }
  std::string to_string_with_children(AnyV v) {
    out.clear();
    visit(v);
    return std::move(out);
  }
  static std::string to_string_without_children(AnyV v) {
    std::string result = ast_node_type_to_string(v->type);
    if (std::string postfix = specific_str(v); !postfix.empty()) {
      result += ' ';
      result += specific_str(v);
    }
    return result;
  }
  static const char* ast_node_type_to_string(ASTNodeType node_type) {
    return name_pairs[node_type].second;
  }
  void visit(AnyV v) override {
    switch (v->type) {
      case ast_identifier:                    return handle_vertex(v->as());
      // expressions
      case ast_empty_expression:              return handle_vertex(v->as());
      case ast_parenthesized_expression:      return handle_vertex(v->as());
      case ast_tensor:                        return handle_vertex(v->as());
      case ast_typed_tuple:                   return handle_vertex(v->as());
      case ast_reference:                     return handle_vertex(v->as());
      case ast_local_var_lhs:                 return handle_vertex(v->as());
      case ast_local_vars_declaration:        return handle_vertex(v->as());
      case ast_int_const:                     return handle_vertex(v->as());
      case ast_string_const:                  return handle_vertex(v->as());
      case ast_bool_const:                    return handle_vertex(v->as());
      case ast_null_keyword:                  return handle_vertex(v->as());
      case ast_argument:                      return handle_vertex(v->as());
      case ast_argument_list:                 return handle_vertex(v->as());
      case ast_dot_access:                    return handle_vertex(v->as());
      case ast_function_call:                 return handle_vertex(v->as());
      case ast_underscore:                    return handle_vertex(v->as());
      case ast_assign:                        return handle_vertex(v->as());
      case ast_set_assign:                    return handle_vertex(v->as());
      case ast_unary_operator:                return handle_vertex(v->as());
      case ast_binary_operator:               return handle_vertex(v->as());
      case ast_ternary_operator:              return handle_vertex(v->as());
      case ast_cast_as_operator:              return handle_vertex(v->as());
      // statements
      case ast_empty_statement:               return handle_vertex(v->as());
      case ast_sequence:                      return handle_vertex(v->as());
      case ast_return_statement:              return handle_vertex(v->as());
      case ast_if_statement:                  return handle_vertex(v->as());
      case ast_repeat_statement:              return handle_vertex(v->as());
      case ast_while_statement:               return handle_vertex(v->as());
      case ast_do_while_statement:            return handle_vertex(v->as());
      case ast_throw_statement:               return handle_vertex(v->as());
      case ast_assert_statement:              return handle_vertex(v->as());
      case ast_try_catch_statement:           return handle_vertex(v->as());
      case ast_asm_body:                      return handle_vertex(v->as());
      // other
      case ast_genericsT_item:                return handle_vertex(v->as());
      case ast_genericsT_list:                return handle_vertex(v->as());
      case ast_instantiationT_item:           return handle_vertex(v->as());
      case ast_instantiationT_list:           return handle_vertex(v->as());
      case ast_parameter:                     return handle_vertex(v->as());
      case ast_parameter_list:                return handle_vertex(v->as());
      case ast_annotation:                    return handle_vertex(v->as());
      case ast_function_declaration:          return handle_vertex(v->as());
      case ast_global_var_declaration:        return handle_vertex(v->as());
      case ast_constant_declaration:          return handle_vertex(v->as());
      case ast_tolk_required_version:         return handle_vertex(v->as());
      case ast_import_directive:              return handle_vertex(v->as());
      case ast_tolk_file:                     return handle_vertex(v->as());
      default:
        throw UnexpectedASTNodeType(v, "ASTStringifier::visit");
    }
  }
};
} // namespace tolk
#endif // TOLK_DEBUG