/******************************************************************************
 * Top contributors (to current version):
 *   Aina Niemetz, Andrew Reynolds, Andres Noetzli
 *
 * This file is part of the cvc5 project.
 *
 * Copyright (c) 2009-2025 by the authors listed in the file AUTHORS
 * in the top-level source directory and their institutional affiliations.
 * All rights reserved.  See the file COPYING in the top-level source
 * directory for licensing information.
 * ****************************************************************************
 *
 * Black box testing of cvc5::InteractiveShell.
 */

#include <cvc5/cvc5.h>
#include <cvc5/cvc5_parser.h>

#include <memory>
#include <sstream>
#include <vector>

#include "main/command_executor.h"
#include "main/interactive_shell.h"
#include "options/base_options.h"
#include "options/language.h"
#include "options/options.h"
#include "test.h"

using namespace cvc5::parser;

namespace cvc5::internal {
namespace test {

class TestMainBlackInteractiveShell : public TestInternal
{
 protected:
  void SetUp() override
  {
    TestInternal::SetUp();

    d_sin = std::make_unique<std::stringstream>();
    d_sout = std::make_unique<std::stringstream>();

    d_solver.reset(new cvc5::Solver(d_tm));
    d_solver->setOption("input-language", "smt2");
    d_cexec.reset(new main::CommandExecutor(d_solver));
  }

  void TearDown() override
  {
    d_sin.reset(nullptr);
    d_sout.reset(nullptr);
    // ensure that command executor is destroyed before solver
    d_cexec.reset(nullptr);
    d_solver.reset(nullptr);
  }

  /**
   * Read up to maxIterations+1 from the shell and throw an assertion error if
   * it's fewer than minIterations and more than maxIterations.  Note that an
   * empty string followed by EOF may be returned as an empty command, and
   * not NULL (subsequent calls to readAndExecCommands() should return nullptr).
   * E.g., "(check-sat)\n" may return two commands: the check-sat, followed by
   * an empty command, followed by nullptr.
   */
  void countCommands(InteractiveShell& shell,
                     uint32_t minIterations,
                     uint32_t maxIterations)
  {
    uint32_t n = 0;
    while (n <= maxIterations)
    {
      if (!shell.readAndExecCommands())
      {
        break;
      }
      n++;
    }
    ASSERT_LE(n, maxIterations);
    ASSERT_GE(n, minIterations);
  }

  std::unique_ptr<std::stringstream> d_sin;
  std::unique_ptr<std::stringstream> d_sout;
  std::unique_ptr<main::CommandExecutor> d_cexec;
  cvc5::TermManager d_tm;
  std::unique_ptr<cvc5::Solver> d_solver;
};

TEST_F(TestMainBlackInteractiveShell, assert_true)
{
  *d_sin << "(assert true)\n" << std::flush;
  InteractiveShell shell(d_cexec.get(), *d_sin, *d_sout);
  countCommands(shell, 1, 1);
}

TEST_F(TestMainBlackInteractiveShell, query_false)
{
  *d_sin << "(check-sat)\n" << std::flush;
  InteractiveShell shell(d_cexec.get(), *d_sin, *d_sout);
  countCommands(shell, 1, 1);
}

TEST_F(TestMainBlackInteractiveShell, def_use1)
{
  InteractiveShell shell(d_cexec.get(), *d_sin, *d_sout);
  *d_sin << "(declare-const x Real) (assert (> x 0))\n" << std::flush;
  // may read two commands in a single line
  countCommands(shell, 1, 2);
}

TEST_F(TestMainBlackInteractiveShell, def_use2)
{
  InteractiveShell shell(d_cexec.get(), *d_sin, *d_sout);
  /* readCommand may return a sequence, see above. */
  *d_sin << "(declare-const x Real)\n" << std::flush;
  shell.readAndExecCommands();
  *d_sin << "(assert (> x 0))\n" << std::flush;
  countCommands(shell, 1, 1);
}

TEST_F(TestMainBlackInteractiveShell, empty_line)
{
  InteractiveShell shell(d_cexec.get(), *d_sin, *d_sout);
  *d_sin << std::flush;
  countCommands(shell, 0, 0);
}

TEST_F(TestMainBlackInteractiveShell, repeated_empty_lines)
{
  *d_sin << "\n\n\n";
  InteractiveShell shell(d_cexec.get(), *d_sin, *d_sout);
  /* Might return up to four empties, might return nothing */
  countCommands(shell, 0, 3);
}
}  // namespace test
}  // namespace cvc5::internal
