#include <vector>
#include <string>

class VarDeclarationNode
:
public BaseNode{
    public:
    const std::string& VarName() const;
    const std::string& TypeName() const;
    void Visit(BaseVisitor* visitor) const override {
      visitor->Visit(this);
    }
};

class MethodDeclarationNode
:
public BaseNode{
    public:
    const std::string& MethodName() const;
    const std::string& ReturnTypeName() const;
    const std::vector<BaseNode*>& Arguments() const;
    void Visit(BaseVisitor* visitor) const override {
      visitor->Visit(this);
    }
};

using std::vector;
using std::string;

class FormatVisitor
:
public BaseVisitor{
 private:
    vector < string > code_{};
    uint32_t padding_{};
    uint32_t workingStr_{};
    int const classSpace = 1;
    int const classPadding = 4;
    string startDeclaration(const string& type, const string& name) {
      string
      str(type + " " + name);
      return str;
    }
    string stringWithPadd(const string& sourceStr, uint32_t param1,
    uint32_t& param2, char param3) {
      string str = sourceStr;
      int const start = 0;
      str.insert(param1, param2, param3);
      return str;
    }
 public:
    void Visit(const BaseNode* node) override {
      node->Visit(this);
    }
    void Visit(const ClassDeclarationNode* node) override {
      auto tmp = startDeclaration("class", node->ClassName()) + " {";
      code_[workingStr_] += tmp;
      //tmp = std::string(" public:").insert(0, cur_pad, ' ');  // ?
      tmp = stringWithPadd(string(" public:"), 0, padding_, ' ');
      code_.push_back(std::move(tmp));

      workingStr_ += classSpace;
      padding_ += classPadding;

      for (auto field : node->PublicFields()) {
        workingStr_++;
        auto ss = stringWithPadd(string(""), 0, padding_, ' ');
        code_.push_back(std::move(ss));
        Visit(field);
        code_[workingStr_].push_back(';');
      }

      if (!node->ProtectedFields().empty()) {
        code_.push_back("");
        code_.push_back(" protected:");
        workingStr_ += 2;

        for (auto field : node->ProtectedFields()) {
          workingStr_++;
          auto ss = stringWithPadd(string(""), 0, padding_, ' ');
          //code_.push_back(std::string("").insert(0, cur_pad, ' '));
          code_.push_back(std::move(ss));
          Visit(field);
          code_[workingStr_].push_back(';');
        }
      }
      if (node->PrivateFields().size() != 0) {
        code_.push_back("");
        code_.push_back(" private:");
        workingStr_++;
        workingStr_++;
        for (auto field : node->PrivateFields()) {
          workingStr_++;
          auto ss = stringWithPadd(string(""), 0, padding_, ' ');
          code_.push_back(std::move(ss));
          //code_.push_back(std::string("").insert(0, cur_pad, ' '));
          Visit(field);
          code_[workingStr_].push_back(';');
        }
      }
      padding_ -= classPadding;
      auto ss = stringWithPadd(string("}"), 0, padding_, ' ');
      //code_.push_back(std::string("}").insert(0, cur_pad, ' '));
      code_.push_back(std::move(ss))
      cur_str++;
      if (0 == padding_)
        code_[workingStr_].push_back(';');

    };
    void Visit(const VarDeclarationNode* node) override {
      code_[workingStr_] += startDeclaration(node->TypeName(), node->VarName());
    };
    void Visit(const MethodDeclarationNode* node) override {
      string tmp = startDeclaration(node->ReturnTypeName(), node->MethodName());
      tmp += "(";
      code_[workingStr_] += tmp;
      for (auto*args : node->Arguments()) {
        Visit(args);
        code_[workingStr_] += ", ";
      }

      if (!node->Arguments().empty()) {
        code_[workingStr_].pop_back();
        code_[workingStr_].pop_back();
      }
      code_[workingStr_].push_back(')');
    };
    const std::vector<std::string>& GetFormattedCode() const {
      return code_;
    }
};