void __fastcall TConsoleRunner::ScriptTerminalQueryUser(TObject * /*Sender*/,
  const UnicodeString Query, TStrings * MoreMessages, unsigned int Answers,
  const TQueryParams * Params, unsigned int & Answer, TQueryType /*QueryType*/,
  void * /*Arg*/)
{
  UnicodeString AQuery = Query;
  unsigned int Timer = 0;
  unsigned int Timeout = 0;
  unsigned int TimeoutA = 0;
  unsigned int NoBatchA = 0;

  if (Params != NULL)
  {
    if (Params->Timeout > 0)
    {
      Timeout = Params->Timeout;
      TimeoutA = Params->TimeoutAnswer;
    }

    if (Params->Timer > 0)
    {
      Timer = Params->Timer;
      if (Params->TimerAnswers > 0)
      {
        Answers = Params->TimerAnswers;
      }
      // not considering TimerQueryType as we do not use QueryType anyway
      if (!Params->TimerMessage.IsEmpty())
      {
        AQuery = Params->TimerMessage;
      }
    }

    if (FLAGSET(Params->Params, qpFatalAbort))
    {
      AQuery = FMTLOAD(WARN_FATAL_ERROR, (AQuery));
    }

    NoBatchA = Params->NoBatchAnswers;

    if (FLAGSET(Params->Params, qpWaitInBatch))
    {
      DebugAssert(Timeout == 0);
      Timeout = InputTimeout();
      if (Timeout != 0)
      {
        // See a duplicate AbortAnswer call below
        TimeoutA = AbortAnswer(Answers & ~NoBatchA);
      }
    }
  }

  AQuery = UnformatMessage(AQuery);
  AQuery = RemoveInteractiveMsgTag(AQuery);

  ApplyTabs(AQuery, L' ', NULL, NULL);

  unsigned int AAnswers = Answers;

  UnicodeString Message = AQuery;
  PrintMessage(AQuery);
  if ((MoreMessages != NULL) && (MoreMessages->Count > 0))
  {
    PrintMessage(MoreMessages->Text);
    Message += L"\n" + MoreMessages->Text;
  }

  std::vector<unsigned int> Buttons;
  std::vector<UnicodeString> Captions;
  std::vector<TButtonSubmitEvent> OnSubmits;

  for (unsigned int Answer = qaFirst; Answer <= qaLast; Answer = Answer << 1)
  {
    if (FLAGSET(Answers, Answer))
    {
      UnicodeString Name; // unused
      UnicodeString Caption;
      AnswerNameAndCaption(Answer, Name, Caption);
      Captions.push_back(Caption);
      Buttons.push_back(Answer);
      OnSubmits.push_back(NULL);
      AAnswers -= Answer;
    }
  }

  DebugUsedParam(AAnswers);
  DebugAssert(AAnswers == 0);
  DebugAssert(!Buttons.empty());

  if ((Params != NULL) && (Params->Aliases != NULL))
  {
    for (unsigned int bi = 0; bi < Buttons.size(); bi++)
    {
      for (unsigned int ai = 0; ai < Params->AliasesCount; ai++)
      {
        if (Params->Aliases[ai].Button == Buttons[bi])
        {
          if (!Params->Aliases[ai].Alias.IsEmpty())
          {
            Captions[bi] = Params->Aliases[ai].Alias;
          }
          OnSubmits[bi] = Params->Aliases[ai].OnSubmit;
          break;
        }
      }
    }
  }

  UnicodeString Accels;
  for (unsigned int Index = 0; Index < Buttons.size(); Index++)
  {
    UnicodeString & Caption = Captions[Index];
    int P = Caption.Pos(L'&');
    if ((P > 0) && (P < Caption.Length()))
    {
      wchar_t Accel = AnsiUpperCase(Caption)[P + 1];
      if (Accels.Pos(Accel) > 0)
      {
        Caption.Delete(P, 1);
        Accels += L' ';
      }
      else
      {
        Accels += Accel;
      }
    }
    else
    {
      Accels += L' ';
    }
  }

  DebugAssert(Accels.Length() == static_cast<int>(Buttons.size()));
  int NumberAccel = 0;
  unsigned int CancelA = CancelAnswer(Answers);
  int CancelIndex;
  // AbortAnswer call duplicated in qpWaitInBatch branch above
  unsigned int AbortA = AbortAnswer(Answers & ~NoBatchA);
  int AbortIndex;
  unsigned int ContinueA = ContinueAnswer(Answers & ~NoBatchA);
  int ContinueIndex;
  int TimeoutIndex = 0;

  for (unsigned int Index = 0; Index < Buttons.size(); Index++)
  {
    UnicodeString & Caption = Captions[Index];

    if (Accels[Index + 1] == L' ')
    {
      for (int Index2 = 1; Index2 <= Caption.Length(); Index2++)
      {
        wchar_t C = AnsiUpperCase(Caption)[Index2];
        if (IsLetter(C) && (Accels.Pos(C) == 0))
        {
          Caption.Insert(L"&", Index2);
          Accels[Index + 1] = C;
          break;
        }
      }
    }

    if (Accels[Index + 1] == L' ')
    {
      for (int Index2 = 1; Index2 <= Caption.Length(); Index2++)
      {
        wchar_t C = AnsiUpperCase(Caption)[Index2];
        if ((C != L' ') && (Accels.Pos(C) == 0))
        {
          Caption.Insert(L"&", Index2);
          Accels[Index + 1] = C;
          break;
        }
      }
    }

    if (Accels[Index + 1] == L' ')
    {
      NumberAccel++;
      DebugAssert(NumberAccel <= 9);
      Caption = FORMAT(L"&%d%s", (NumberAccel, Caption));
      Accels[Index + 1] = Caption[2];
    }

    if (Buttons[Index] == CancelA)
    {
      CancelIndex = Index + 1;
    }
    if (Buttons[Index] == AbortA)
    {
      AbortIndex = Index + 1;
    }
    if (Buttons[Index] == ContinueA)
    {
      ContinueIndex = Index + 1;
    }
    if (Buttons[Index] == TimeoutA)
    {
      TimeoutIndex = Index + 1;
    }
  }

  DebugAssert(Accels.Pos(L' ') == 0);

  bool Timeouting = (Timeout > 0);
  bool FirstOutput = true;

  do
  {
    Answer = 0;
    int AnswerIndex;
    bool Retry;

    do
    {
      Retry = false;

      if (FirstOutput || FConsole->LiveOutput())
      {
        UnicodeString Output;
        for (unsigned int i = 0; i < Buttons.size(); i++)
        {
          if (i > 0)
          {
            Output += L", ";
          }

          UnicodeString Caption = Captions[i];
          int P = Caption.Pos(L'&');
          if (DebugAlwaysTrue(P >= 0))
          {
            Caption[P] = L'(';
            Caption.Insert(L")", P + 2);
          }

          if (i + 1 == static_cast<unsigned int>(TimeoutIndex))
          {
            DebugAssert(Timeouting);
            Caption = FMTLOAD(TIMEOUT_BUTTON, (Caption, int(Timeout / MSecsPerSec)));
          }

          Output += Caption;
        }
        Output += L": ";

        // note that length of string may decrease over time due to number of
        // seconds length, but supposing it decreases by one character at time
        // at most, we do not mind as the prompt is terminated with space

        // If output is not live (file or pipe), do not use 'from beginning'
        // as it means that the output is not actually stored until new line
        // is sent (and we will not [because we cannot] rewrite the output anyway)
        Print(Output, !FirstOutput);
        FirstOutput = false;
      }

      if (!Timeouting && (FScript->Batch == TScript::BatchContinue))
      {
        AnswerIndex = ContinueIndex;
      }
      else if (!Timeouting && (FScript->Batch != TScript::BatchOff))
      {
        AnswerIndex = AbortIndex;
      }
      else if (Timeouting && (Timeout < MSecsPerSec))
      {
        AnswerIndex = TimeoutIndex;
      }
      else
      {
        unsigned int ActualTimer;
        if (Timeouting)
        {
          if (Timer == 0)
          {
            if (FConsole->NoInteractiveInput())
            {
              ActualTimer = Timeout;
            }
            else
            {
              ActualTimer = MSecsPerSec;
            }
          }
          else
          {
            if (Timer < MSecsPerSec)
            {
              ActualTimer = Timer;
            }
            else
            {
              ActualTimer = MSecsPerSec;
            }
          }
        }
        else
        {
          ActualTimer = Timer;
        }
        // Not to get preliminary "host is not responding" messages to .NET assembly
        if (FConsole->NoInteractiveInput() && (Timer > 0))
        {
          Sleep(Timer);
          AnswerIndex = -2;
        }
        else
        {
          AnswerIndex =
            FConsole->Choice(Accels, CancelIndex, -1, ContinueIndex, -2, Timeouting, ActualTimer, Message);
        }
        if (AnswerIndex == -1)
        {
          NotifyAbort();
          AnswerIndex = AbortIndex;
        }
        else if (AnswerIndex == -2)
        {
          if (Timeouting)
          {
            DebugAssert(Timeout >= MSecsPerSec);
            Timeout -= ActualTimer;
            Retry = true;
          }

          // this does not take Timer into account,
          // but as of now Timer is used for TSecureShell timeout prompt only,
          // where Timer is less than MSecsPerSec
          if (Timer > 0)
          {
            DebugAssert((Params != NULL) && (Params->TimerEvent != NULL));
            if ((Params != NULL) && (Params->TimerEvent != NULL))
            {
              unsigned int AAnswer = 0;
              Params->TimerEvent(AAnswer);
              if (AAnswer != 0)
              {
                Answer = AAnswer;
                Retry = false;
              }
              else
              {
                Retry = true;
              }
            }
          }
        }
      }
    }
    while (Retry);

    if (Answer == 0)
    {
      DebugAssert((AnswerIndex >= 1) && (AnswerIndex <= Accels.Length()));
      UnicodeString AnswerCaption = Captions[AnswerIndex - 1];
      int P = AnswerCaption.Pos(L"&");
      DebugAssert(P >= 0);
      AnswerCaption.Delete(P, 1);
      FConsole->PrintLine(AnswerCaption);
      FirstOutput = true;

      if (OnSubmits[AnswerIndex - 1] != NULL)
      {
        OnSubmits[AnswerIndex - 1](NULL, Answer);
      }
      else
      {
        Answer = Buttons[AnswerIndex - 1];
      }
    }
    else
    {
      FConsole->PrintLine();
    }
  }
  while (Answer == 0);

  if ((Answer == AbortA) &&
      ((Params == NULL) || FLAGCLEAR(Params->Params, qpIgnoreAbort)))
  {
    if (FScript->Terminal != NULL)
    {
      TStrings * Messages = new TStringList();
      try
      {
        Messages->Add(Query);
        if (MoreMessages != NULL)
        {
          Messages->AddStrings(MoreMessages);
        }
        FScript->Terminal->ActionLog->AddFailure(Messages);
      }
      __finally
      {
        delete Messages;
      }
    }

    FCommandError = true;
  }
}