#include <vector>
#include <utility>
#include <string>

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 fieldSpace = 2;
    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 const& param2, char param3) {
      string str = sourceStr;
      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 = stringWithPadd(string("  public:"), 0, padding_, ' ');
      code_.push_back(std::move(tmp));

      workingStr_ += classSpace;
      padding_ += classPadding;

      for (auto field : node->PublicFields()) {
        workingStr_++;

        code_.push_back(string("").insert(0, padding_, ' '));
        Visit(field);
        code_[workingStr_].push_back(';');
      }

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

        for (auto field : node->ProtectedFields()) {
          workingStr_++;
          auto ss = stringWithPadd(string(""), 0, padding_, ' ');

          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_ += fieldSpace;
        for (auto field : node->PrivateFields()) {
          workingStr_++;
          auto ss = stringWithPadd(string(""), 0, padding_, ' ');
          code_.push_back(std::move(ss));

          Visit(field);
          code_[workingStr_].push_back(';');
        }
      }

      padding_ -= classPadding;
      auto ss = stringWithPadd(string("}"), 0, padding_, ' ');

      code_.push_back(std::move(ss));
      workingStr_++;
      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_;
    }
};