#include "ActionBank.hpp"
#include "Config.hpp"
#include "util.hpp"
#include "ProgramParameters.hpp"

Action::BasicAction ActionBank::moveHead(int movement)
{
  auto apply = [movement](Config & c, Action::BasicAction &)
    {c.moveHead(movement);};
  auto undo = [movement](Config & c, Action::BasicAction &)
    {c.moveHead(-movement);};
  auto appliable = [movement](Config &, Action::BasicAction &)
    {return true;};
  Action::BasicAction basicAction =
    {Action::BasicAction::Type::MoveHead, "", apply, undo, appliable};

  return basicAction;
}

Action::BasicAction ActionBank::increaseTapesIfNeeded(int size)
{
  auto apply = [size](Config & c, Action::BasicAction &)
    {
      for (auto & tape : c.tapes)
        for (int i = 0; i <= size-(tape.refSize()-c.getHead()); i++)
        {
          tape.addToRef("");
          tape.addToHyp("");
        }
    };
  auto undo = [](Config &, Action::BasicAction &)
    {};
  auto appliable = [](Config &, Action::BasicAction &)
    {return true;};
  Action::BasicAction basicAction =
    {Action::BasicAction::Type::Write, "", apply, undo, appliable};

  return basicAction;
}

Action::BasicAction ActionBank::moveRawInputHead(int movement)
{
  auto apply = [movement](Config & c, Action::BasicAction &)
    {c.moveRawInputHead(movement);};
  auto undo = [movement](Config & c, Action::BasicAction &)
    {c.moveRawInputHead(-movement);};
  auto appliable = [movement](Config & c, Action::BasicAction &)
    {return c.rawInputHeadIndex+movement <= (int)c.rawInput.size();};
  Action::BasicAction basicAction =
    {Action::BasicAction::Type::MoveHead, "", apply, undo, appliable};

  return basicAction;
}

Action::BasicAction ActionBank::checkRawInputHeadIsSpace()
{
  auto apply = [](Config &, Action::BasicAction &)
    {};
  auto undo = [](Config &, Action::BasicAction &)
    {};
  auto appliable = [](Config & c, Action::BasicAction &)
    {
      return isUtf8Space(c.rawInput.begin()+c.rawInputHeadIndex);
    };
  Action::BasicAction basicAction =
    {Action::BasicAction::Type::Write, "", apply, undo, appliable};

  return basicAction;
}

Action::BasicAction ActionBank::checkRawInputHeadIsSeparator()
{
  auto apply = [](Config &, Action::BasicAction &)
    {};
  auto undo = [](Config &, Action::BasicAction &)
    {};
  auto appliable = [](Config & c, Action::BasicAction &)
    {
      return isUtf8Separator(c.rawInput.begin()+c.rawInputHeadIndex);
    };
  Action::BasicAction basicAction =
    {Action::BasicAction::Type::Write, "", apply, undo, appliable};

  return basicAction;
}

Action::BasicAction ActionBank::rawInputBeginsWith(std::string word)
{
  auto apply = [](Config &, Action::BasicAction &)
    {};
  auto undo = [](Config &, Action::BasicAction &)
    {};
  auto appliable = [word](Config & c, Action::BasicAction &)
    {
      if (c.rawInputHeadIndex+word.size() >= c.rawInput.size()) 
        return false;

      for (unsigned int i = 0; i < word.size(); i++)
        if (c.rawInput[c.rawInputHeadIndex+i] != word[i])
          return false;

      return true;
    };
  Action::BasicAction basicAction =
    {Action::BasicAction::Type::Write, "", apply, undo, appliable};

  return basicAction;
}

Action::BasicAction ActionBank::bufferWrite(std::string tapeName, std::string value, int relativeIndex)
{
  auto apply = [tapeName, value, relativeIndex](Config & c, Action::BasicAction &)
    {
      simpleBufferWrite(c, tapeName, value, relativeIndex);
    };
  auto undo = [tapeName, relativeIndex](Config & c, Action::BasicAction &)
    {
      simpleBufferWrite(c, tapeName, "", relativeIndex);
    };
  auto appliable = [tapeName, relativeIndex](Config & c, Action::BasicAction &)
    {
      return simpleBufferWriteAppliable(c, tapeName, relativeIndex);
    };
  Action::BasicAction basicAction =
    {Action::BasicAction::Type::Write, value, apply, undo, appliable};

  return basicAction;
}

Action::BasicAction ActionBank::stackWrite(std::string tapeName, std::string value, int stackIndex)
{
  auto apply = [tapeName, value, stackIndex](Config & c, Action::BasicAction &)
    {
      int bufferIndex = c.stackGetElem(stackIndex);
      int relativeIndex = bufferIndex - c.getHead();
      simpleBufferWrite(c, tapeName, value, relativeIndex);
    };
  auto undo = [tapeName, stackIndex](Config & c, Action::BasicAction &)
    {
      int bufferIndex = c.stackGetElem(stackIndex);
      int relativeIndex = bufferIndex - c.getHead();
      simpleBufferWrite(c, tapeName, "", relativeIndex);
    };
  auto appliable = [tapeName, stackIndex](Config & c, Action::BasicAction &)
    {
      if (!c.stackHasIndex(stackIndex))
        return false;
      int bufferIndex = c.stackGetElem(stackIndex);
      int relativeIndex = bufferIndex - c.getHead();
      return simpleBufferWriteAppliable(c, tapeName, relativeIndex);
    };

  Action::BasicAction basicAction =
    {Action::BasicAction::Type::Write, value, apply, undo, appliable};

  return basicAction;
}

Action::BasicAction ActionBank::pushHead()
{
    auto apply = [](Config & c, Action::BasicAction &)
      {c.stackPush(c.getHead());};
    auto undo = [](Config & c, Action::BasicAction &)
      {c.stackPop();};
    auto appliable = [](Config & c, Action::BasicAction &)
      {return !(c.stackSize() >= ProgramParameters::maxStackSize || (!c.stackEmpty() && c.stackTop() == c.getHead()));};
    Action::BasicAction basicAction =
      {Action::BasicAction::Type::Push, "", apply, undo, appliable};

    return basicAction;
}

Action::BasicAction ActionBank::stackPop(bool checkGov)
{
  auto apply = [](Config & c, Action::BasicAction & ba)
    {ba.data = std::to_string(c.stackTop());
     c.stackPop();};
  auto undo = [](Config & c, Action::BasicAction & ba)
    {c.stackPush(std::stoi(ba.data));};
  auto appliable = [checkGov](Config & c, Action::BasicAction &)
    {
      if (c.stackEmpty())
        return false;
      if (!checkGov)
        return true;

      return split(c.getTape("ID").getRef(c.stackTop()-c.getHead()), '.').size() > 1 || split(c.getTape("ID").getRef(c.stackTop()-c.getHead()), '-').size() > 1 || (!c.getTape("GOV").getHyp(c.stackTop()-c.getHead()).empty() && c.stackTop() != c.getHead());
    };
  Action::BasicAction basicAction =
    {Action::BasicAction::Type::Pop, "", apply, undo, appliable};

  return basicAction;
}

Action::BasicAction ActionBank::checkNotEmpty(std::string tape, int relativeIndex)
{
  auto apply = [](Config &, Action::BasicAction &)
    {};
  auto undo = [](Config &, Action::BasicAction &)
    {};
  auto appliable = [tape, relativeIndex](Config & c, Action::BasicAction &)
    {
      return !c.getTape(tape).getHyp(relativeIndex).empty();
    };
  Action::BasicAction basicAction =
    {Action::BasicAction::Type::Write, "", apply, undo, appliable};

  return basicAction;
}

std::vector<Action::BasicAction> ActionBank::str2sequence(const std::string & name)
{
  auto invalidNameAndAbort = [&](const char * errInfo)
  {
    fprintf(stderr, "ERROR (%s) : unknown action name \'%s\' Aborting.\n", errInfo, name.c_str());

    exit(1);
  };

  std::vector<Action::BasicAction> sequence;

  char b1[1024];
  char b2[1024];
  char b3[1024];
  char b4[1024];

  if (sscanf(name.c_str(), "%s", b1) != 1)
    invalidNameAndAbort(ERRINFO);

  if(std::string(b1) == "WRITE")
  {
    if (sscanf(name.c_str(), "%s %s %s %s", b1, b4, b2, b3) != 4)
      invalidNameAndAbort(ERRINFO);

    std::string tapeName(b2);
    std::string value(b3);
    auto object = split(b4, '.');

    if (object.size() != 2)
      invalidNameAndAbort(ERRINFO);

    int relativeIndex = std::stoi(object[1]);

    if (object[0] == "b")
      sequence.emplace_back(bufferWrite(tapeName, value, relativeIndex));
    else if (object[0] == "s")
      sequence.emplace_back(stackWrite(tapeName, value, relativeIndex));     
  }
  else if(std::string(b1) == "MULTIWRITE")
  {
    int startRelIndex;
    int endRelIndex;
    
    if (sscanf(name.c_str(), "%s %d %d %s", b1, &startRelIndex, &endRelIndex, b2) != 4)
      invalidNameAndAbort(ERRINFO);

    std::string tapeName(b2);

    auto splits = split(name);

    for(int i = startRelIndex; i <= endRelIndex; i++)
      sequence.emplace_back(bufferWrite(tapeName, splits[4+i-startRelIndex], i));
  }
  else if(std::string(b1) == "RULE")
  {
    if (sscanf(name.c_str(), "%s %s ON %s %[^\n]", b1, b2, b3, b4) != 4)
      invalidNameAndAbort(ERRINFO);

    std::string targetTapeName(b2);
    std::string fromTapeName(b3);
    std::string rule(b4);

    auto apply = [fromTapeName, targetTapeName, rule](Config & c, Action::BasicAction &)
      {writeRuleResult(c, fromTapeName, targetTapeName, rule, 0);};
    auto undo = [targetTapeName](Config & c, Action::BasicAction &)
      {simpleBufferWrite(c, targetTapeName, "", 0);};
    auto appliable = [fromTapeName,rule](Config & c, Action::BasicAction &)
      {return isRuleAppliable(c, fromTapeName, 0, rule);};
    Action::BasicAction basicAction =
      {Action::BasicAction::Type::Write, rule, apply, undo, appliable};

    sequence.emplace_back(basicAction);
  }
  else if(std::string(b1) == "NOTFOUND")
  {
  }
  else if(std::string(b1) == "NOTHING")
  {
  }
  else if(std::string(b1) == "EPSILON")
  {
  }
  else if(std::string(b1) == "MOVE")
  {
    int movement;
    if (sscanf(name.c_str(), "MOVE %s %d", b2, &movement) != 2)
      invalidNameAndAbort(ERRINFO);

    sequence.emplace_back(moveHead(movement));
  }
  else if(std::string(b1) == "IGNORECHAR")
  {
    sequence.emplace_back(checkRawInputHeadIsSeparator());
    sequence.emplace_back(moveRawInputHead(1));
  }
  else if(std::string(b1) == "ENDWORD")
  {
    sequence.emplace_back(checkNotEmpty("FORM", 0));
    sequence.emplace_back(increaseTapesIfNeeded(1));

    auto apply = [](Config & c, Action::BasicAction &)
      {simpleBufferWrite(c, "ID", std::to_string(c.currentWordIndex), 0);};
    auto undo = [](Config & c, Action::BasicAction &)
      {simpleBufferWrite(c, "ID", std::string(""), 0);};
    auto appliable = [](Config & c, Action::BasicAction &)
      {return simpleBufferWriteAppliable(c, "ID", 0);};
    Action::BasicAction basicAction =
      {Action::BasicAction::Type::Write, "", apply, undo, appliable};

    sequence.emplace_back(basicAction);
  }
  else if(std::string(b1) == "ADDCHARTOWORD")
  {
    sequence.emplace_back(increaseTapesIfNeeded(0));

    auto apply = [](Config & c, Action::BasicAction &)
      {addCharToBuffer(c, "FORM", 0);};
    auto undo = [](Config & c, Action::BasicAction &)
      {removeCharFromBuffer(c, "FORM", 0);};
    auto appliable = [](Config & c, Action::BasicAction &)
      {return c.getTape("FORM").getHyp(0).size() <= 2000;};
    Action::BasicAction basicAction =
      {Action::BasicAction::Type::Write, "", apply, undo, appliable};

    sequence.emplace_back(basicAction);
    sequence.emplace_back(moveRawInputHead(1));
  }
  else if(std::string(b1) == "SPLITWORD")
  {
    if (sscanf(name.c_str(), "SPLITWORD %s", b2) != 1)
      invalidNameAndAbort(ERRINFO);

    auto splited = split(b2, '@');
    int nbSymbols = getNbSymbols(splited[0]);

    sequence.emplace_back(rawInputBeginsWith(splited[0]));

    sequence.emplace_back(moveRawInputHead(nbSymbols));

    sequence.emplace_back(increaseTapesIfNeeded(splited.size()));

    for (unsigned int i = 0; i < splited.size(); i++)
    {
      sequence.emplace_back(bufferWrite("FORM", splited[i], i));

      int splitedSize = (int)splited.size();
      auto apply = [i, splitedSize](Config & c, Action::BasicAction &)
        {simpleBufferWrite(c, "ID", i == 0 ? std::to_string(c.currentWordIndex) + "-" + std::to_string(c.currentWordIndex+splitedSize-2) : std::to_string(c.currentWordIndex+i-1), i);};
      auto undo = [i](Config & c, Action::BasicAction &)
        {simpleBufferWrite(c, "ID", std::string(""), i);};
      auto appliable = [i](Config & c, Action::BasicAction &)
        {return simpleBufferWriteAppliable(c, "ID", i);};
      Action::BasicAction basicAction =
        {Action::BasicAction::Type::Write, "", apply, undo, appliable};

      sequence.emplace_back(basicAction);
    }
  }
  else if(std::string(b1) == "MOVERAW")
  {
    int movement;
    if (sscanf(name.c_str(), "MOVERAW %d", &movement) != 1)
      invalidNameAndAbort(ERRINFO);

    sequence.emplace_back(moveRawInputHead(movement));
  }
  else if(std::string(b1) == "ERROR")
  {
    auto apply = [](Config &, Action::BasicAction &)
      {fprintf(stderr, "ERROR\n");};
    auto undo = [](Config &, Action::BasicAction &)
      {};
    auto appliable = [](Config &, Action::BasicAction &)
      {return true;};
    Action::BasicAction basicAction =
      {Action::BasicAction::Type::Push, "", apply, undo, appliable};

    sequence.emplace_back(basicAction);
  }
  else if(std::string(b1) == "CORRECT")
  {
    auto apply = [](Config &, Action::BasicAction &)
      {fprintf(stderr, "CORRECT\n");};
    auto undo = [](Config &, Action::BasicAction &)
      {};
    auto appliable = [](Config &, Action::BasicAction &)
      {return true;};
    Action::BasicAction basicAction =
      {Action::BasicAction::Type::Push, "", apply, undo, appliable};

    sequence.emplace_back(basicAction);
  }
  else if(std::string(b1) == "SHIFT")
  {
    sequence.emplace_back(pushHead());
  }
  else if(std::string(b1) == "REDUCE")
  {
    sequence.emplace_back(stackPop(true));
  }
  else if(std::string(b1) == "LEFT")
  {
    auto apply = [](Config & c, Action::BasicAction &)
      {
        int b0 = c.getHead();
        int s0 = c.stackTop();
        simpleBufferWrite(c, "GOV", std::to_string(b0-s0), s0-b0);
      };
    auto undo = [](Config & c, Action::BasicAction &)
      {
        int b0 = c.getHead();
        int s0 = c.stackTop();
        simpleBufferWrite(c, "GOV", "", s0-b0);
      };
    auto appliable = [](Config & c, Action::BasicAction &)
      {
        if (c.stackEmpty() || c.endOfTapes())
          return false;
        int b0 = c.getHead();
        int s0 = c.stackTop();

        if (split(c.getTape("ID").getRef(0), '-').size() > 1)
          return false;
        if (split(c.getTape("ID").getRef(c.stackTop()-c.getHead()), '-').size() > 1)
          return false;
        if (split(c.getTape("ID").getRef(0), '.').size() > 1)
          return false;
        if (split(c.getTape("ID").getRef(c.stackTop()-c.getHead()), '.').size() > 1)
          return false;

        return simpleBufferWriteAppliable(c, "GOV", s0-b0);
      };
    Action::BasicAction basicAction =
      {Action::BasicAction::Type::Write, "", apply, undo, appliable};

    sequence.emplace_back(basicAction);

    if (sscanf(name.c_str(), "%s %s", b1, b2) == 2)
    {
      auto apply2 = [b2](Config & c, Action::BasicAction &)
        {
          int b0 = c.getHead();
          int s0 = c.stackTop();
          simpleBufferWrite(c, "LABEL", b2, s0-b0);
        };
      auto undo2 = [](Config & c, Action::BasicAction &)
        {
          int b0 = c.getHead();
          int s0 = c.stackTop();
          simpleBufferWrite(c, "LABEL", "", s0-b0);
        };
      auto appliable2 = [](Config & c, Action::BasicAction &)
        {
          if (c.stackEmpty())
            return false;
          int b0 = c.getHead();
          int s0 = c.stackTop();
          return simpleBufferWriteAppliable(c, "LABEL", s0-b0);
        };
      Action::BasicAction basicAction2 =
        {Action::BasicAction::Type::Write, b2, apply2, undo2, appliable2};

      sequence.emplace_back(basicAction2);
    }

    sequence.emplace_back(stackPop(false));
  }
  else if(std::string(b1) == "RIGHT")
  {
    auto apply = [](Config & c, Action::BasicAction &)
      {
        int b0 = c.getHead();
        int s0 = c.stackTop();
        simpleBufferWrite(c, "GOV", std::to_string(s0-b0), 0);
      };
    auto undo = [](Config & c, Action::BasicAction &)
      {
        simpleBufferWrite(c, "GOV", "", 0);
      };
    auto appliable = [](Config & c, Action::BasicAction &)
      {
        if (c.stackEmpty())
          return false;
        if (split(c.getTape("ID").getRef(0), '-').size() > 1)
          return false;
        if (split(c.getTape("ID").getRef(c.stackTop()-c.getHead()), '-').size() > 1)
          return false;
        if (split(c.getTape("ID").getRef(0), '.').size() > 1)
          return false;
        if (split(c.getTape("ID").getRef(c.stackTop()-c.getHead()), '.').size() > 1)
          return false;
        return simpleBufferWriteAppliable(c, "GOV", 0);
      };
    Action::BasicAction basicAction =
      {Action::BasicAction::Type::Write, "", apply, undo, appliable};

    sequence.emplace_back(basicAction);

    if (sscanf(name.c_str(), "%s %s", b1, b2) == 2)
    {
      auto apply2 = [b2](Config & c, Action::BasicAction &)
        {
          simpleBufferWrite(c, "LABEL", b2, 0);
        };
      auto undo2 = [](Config & c, Action::BasicAction &)
        {
          simpleBufferWrite(c, "LABEL", "", 0);
        };
      auto appliable2 = [](Config & c, Action::BasicAction &)
        {
          return simpleBufferWriteAppliable(c, "LABEL", 0);
        };
      Action::BasicAction basicAction2 =
        {Action::BasicAction::Type::Write, b2, apply2, undo2, appliable2};

      sequence.emplace_back(basicAction2);
    }

    sequence.emplace_back(pushHead());
  }
  else if(std::string(b1) == "EOS")
  {
    // Puting the EOS tag on the last element of the sentence.
    auto apply0 = [b2](Config & c, Action::BasicAction &)
      {
        int b0 = c.getHead();
        int s0 = c.stackTop();
        simpleBufferWrite(c, ProgramParameters::sequenceDelimiterTape, ProgramParameters::sequenceDelimiter, s0-b0);
      };
    auto undo0 = [](Config & c, Action::BasicAction)
      {
        int b0 = c.getHead();
        int s0 = c.stackTop();
        simpleBufferWrite(c, ProgramParameters::sequenceDelimiterTape, "", s0-b0);
      };
    auto appliable0 = [](Config & c, Action::BasicAction &)
      {
        return !c.isFinal() && !c.stackEmpty();
      };
    Action::BasicAction basicAction0 =
      {Action::BasicAction::Type::Write, "", apply0, undo0, appliable0};

    sequence.emplace_back(basicAction0);

    // Chosing root of the sentence and attaching floating words to it.
    auto apply = [](Config & c, Action::BasicAction & ba)
      {
        ba.data = "";
        auto & govs = c.getTape("GOV");
        auto & ids = c.getTape("ID");
        int b0 = c.getHead();
        int rootIndex = -1;
        for (int i = c.stackSize()-1; i >= 0; i--)
        {
          auto s = c.stackGetElem(i);
          if (split(ids.getRef(s-b0), '-').size() > 1)
            continue;
          if (split(ids.getRef(s-b0), '.').size() > 1)
            continue;
          if (govs.getHyp(s-b0).empty() || govs.getHyp(s-b0) == "0")
          {
            if (rootIndex == -1)
              rootIndex = s;
            else
            {
              simpleBufferWrite(c, "GOV", std::to_string(rootIndex-s), s-b0);
              simpleBufferWrite(c, "LABEL", "_", s-b0);
              ba.data += "+"+std::to_string(s-b0);
            }
          }
        }

        if (rootIndex == -1)
        {
          if (c.stackEmpty())
          {
            c.printForDebug(stderr);
            fprintf(stderr, "ERROR (%s) : no suitable candidate for root. Aborting.\n", ERRINFO);
            exit(1);
          }

          rootIndex = c.stackGetElem(c.stackSize()-1);
        }

        simpleBufferWrite(c, "GOV", "0", rootIndex-b0);
        simpleBufferWrite(c, "LABEL", "root", rootIndex-b0);

        // Delete the arcs from the previous sentence to the new sentence
        // TODO
      };
    auto undo = [](Config & c, Action::BasicAction & ba)
      {
        auto & govs = c.getTape("GOV");
        int b0 = c.getHead();
        for (int i = c.stackSize()-1; i >= 0; i--)
        {
          auto s = c.stackGetElem(i);
          if (govs.getHyp(s-b0) == "0")
          {
            simpleBufferWrite(c, "GOV", "", s-b0);
            simpleBufferWrite(c, "LABEL", "", s-b0);
            break;
          }
        }
        auto deps = split(ba.data, '+');
        for (auto s : deps)
          if (!s.empty())
          {
            simpleBufferWrite(c, "GOV", "", std::stoi(s));
            simpleBufferWrite(c, "LABEL", "", std::stoi(s));
          }
        ba.data.clear();
      };
    auto appliable = [](Config & c, Action::BasicAction &)
      {
        return !c.stackEmpty();
      };
    Action::BasicAction basicAction =
      {Action::BasicAction::Type::Write, "", apply, undo, appliable};

    sequence.emplace_back(basicAction);

    // Empty the stack.
    auto apply4 = [](Config & c, Action::BasicAction & ba)
      {
        ba.data = "";
        for (int i = c.stackSize()-1; i >= 0; i--)
        {
          auto s = c.stackGetElem(i);
          ba.data += std::to_string(s) + " ";
        }

        while (!c.stackEmpty())
          c.stackPop();
      };
    auto undo4 = [](Config & c, Action::BasicAction & ba)
      {
        auto elems = split(ba.data);
        for (auto elem : elems)
          if (!elem.empty())
            c.stackPush(std::stoi(elem));
        ba.data.clear();
      };
    auto appliable4 = [](Config & c, Action::BasicAction &)
      {
        return !c.isFinal() && !c.stackEmpty();
      };
    Action::BasicAction basicAction4 =
      {Action::BasicAction::Type::Pop, "", apply4, undo4, appliable4};

    sequence.emplace_back(basicAction4);

    // Update the IDs of the words in the new sentence
    auto apply5 = [](Config & c, Action::BasicAction &)
      {
        c.updateIdsInSequence();
      };
    auto undo5 = [](Config &, Action::BasicAction &)
      {
      };
    auto appliable5 = [](Config &, Action::BasicAction &)
      {
        return true;
      };
    Action::BasicAction basicAction5 =
      {Action::BasicAction::Type::Write, "", apply5, undo5, appliable5};

    sequence.emplace_back(basicAction5);
  }
  else if(std::string(b1) == "BACK")
  {
    if (sscanf(name.c_str(), "%s %s", b1, b2) != 2)
      invalidNameAndAbort(ERRINFO);

    if (isNum(b2))
    {
      int dist = std::stoi(b2);

      auto apply = [dist](Config & c, Action::BasicAction &)
        {
          std::string classifierName = c.pastActions.top().first;
          if (ProgramParameters::debug)
            fprintf(stderr, "classifierName = <%s>\n", classifierName.c_str());

          static auto undoOneTime = [](Config & c, const std::string & classifierName)
          {
            if (ProgramParameters::debug)
              fprintf(stderr, "classifierName = <%s>\n", classifierName.c_str());

            while (true)
            {
              auto a = c.pastActions.pop();

              a.second.undoOnlyStack(c);

              if (a.first == classifierName)
                return;
            }
          };

          static auto undoForReal = [](Config & c, const std::string & classifierName)
          {
            if (ProgramParameters::debug)
              fprintf(stderr, "classifierName = <%s>\n", classifierName.c_str());

            while (true)
            {
              if (c.pastActions.empty())
              {
                fprintf(stderr, "ERROR (%s) : trying to undo action while pastActions is empty. Aborting.\n", ERRINFO);
                exit(1);
              }
              auto a = c.pastActions.pop();

              if (a.first == classifierName)
              {
                a.second.undo(c);
                return;
              }
              a.second.undoOnlyStack(c);
            }
          };

          undoOneTime(c, classifierName);
          for (int i = 0; i < dist-1; i++)
            undoOneTime(c, classifierName);

          undoForReal(c, classifierName);
        };
      auto undo = [dist](Config &, Action::BasicAction &)
        {
        };
      auto appliable = [dist](Config & c, Action::BasicAction)
        {
          if (c.pastActions.size() == 0)
            return false;

          if (c.getCurrentStateHistory().size() > 0 && c.getCurrentStateHistory().top() != "EPSILON")
          {
            return false;
          }

          const std::string & classifierName = c.pastActions.top().first;

          if (c.hashHistory.contains(c.computeHash()))
            return false;

          unsigned int topIndex = 0;

          static auto undoOneTime = [](Config & c, const std::string & classifierName, unsigned int & topIndex)
          {
            while (true)
            {
              topIndex++;

              if (topIndex > c.pastActions.size())
                return;

              if (c.pastActions.getElem(topIndex-1).first == classifierName)
                return;
            }
          };

          undoOneTime(c, classifierName, topIndex);
          for (int i = 0; i < dist-1; i++)
            undoOneTime(c, classifierName, topIndex);

          undoOneTime(c, classifierName, topIndex);

          if (topIndex >= c.pastActions.size())
            return false;

          return true;
        };
        Action::BasicAction basicAction =
          {Action::BasicAction::Type::Back, name, apply, undo, appliable};

        sequence.emplace_back(basicAction);
    }
    else
    {
      invalidNameAndAbort(ERRINFO);
    }
  }
  else
    invalidNameAndAbort(ERRINFO);

  return sequence;
}

void ActionBank::simpleBufferWrite(Config & config, const std::string & tapeName, 
  const std::string & value, int relativeIndex)
{
  auto & tape = config.getTape(tapeName);

  tape.setHyp(relativeIndex, value);
}

bool ActionBank::simpleBufferWriteAppliable(Config & config,
  const std::string & tapeName, int relativeIndex)
{
  auto & tape = config.getTape(tapeName);

  int index = config.getHead() + relativeIndex;

  return !(index < 0) && index < tape.size() && tape.getHyp(relativeIndex).empty();
}

bool ActionBank::isRuleAppliable(Config & config,
  const std::string & tapeName, int relativeIndex, const std::string & rule)
{
  if (!simpleBufferWriteAppliable(config, tapeName, relativeIndex))
    return false;
  return ruleIsAppliable(config.getTape(tapeName)[relativeIndex], rule);
}

void ActionBank::writeRuleResult(Config & config, const std::string & fromTapeName, const std::string & targetTapeName, const std::string & rule, int relativeIndex)
{
  auto & fromTape = config.getTape(fromTapeName);
  auto & toTape = config.getTape(targetTapeName);

  auto & from = fromTape.getRef(relativeIndex);

  toTape.setHyp(relativeIndex, applyRule(from, rule));
}

void ActionBank::addCharToBuffer(Config & config, const std::string & tapeName, int relativeIndex)
{
  auto & tape = config.getTape(tapeName);
  auto & from = tape.getHyp(relativeIndex);

  int nbChar = getEndIndexOfNthSymbolFrom(config.rawInput.begin()+config.rawInputHeadIndex,config.rawInput.end(), 0)+1;

  std::string suffix = std::string(config.rawInput.begin()+config.rawInputHeadIndex, config.rawInput.begin()+config.rawInputHeadIndex+nbChar);

  tape.setHyp(relativeIndex, from+suffix);
}

void ActionBank::removeCharFromBuffer(Config & config, const std::string & tapeName, int relativeIndex)
{
  auto & tape = config.getTape(tapeName);
  auto from = tape.getRef(relativeIndex);

  std::string suffix = std::string(config.rawInput.begin()+config.rawInputHeadIndex, config.rawInput.begin()+config.rawInputHeadIndex+getEndIndexOfNthSymbolFrom(config.rawInput.begin()+config.rawInputHeadIndex,config.rawInput.end(), 0));

  for (char c : suffix)
    from.pop_back();

  tape.setHyp(relativeIndex, from);
}

int ActionBank::getLinkLength(const Config & c, const std::string & action)
{
  auto splitted = split(action, ' ');
  auto & name = splitted[0];
  if (name == "LEFT" || name == "RIGHT" || name == "EOS")
  {
    if (c.stackEmpty())
    {
      fprintf(stderr, "ERROR (%s) : stack is empty. Aborting.\n", ERRINFO);
      exit(1);
    }

    int stackIndex = c.stackGetElem(0);
    return std::abs(c.getHead() - stackIndex);
  }

  return 0;
}