/* libpqxx test runner.
 */
#include <cassert>
#include <iostream>
#include <list>
#include <map>
#include <new>
#include <stdexcept>
#include <string>

#include <pqxx/transaction>

#include "test_helpers.hxx"

namespace
{
inline std::string deref_field(pqxx::field const &f)
{
  return f.c_str();
}
} // namespace


namespace pqxx::test
{
#if defined(PQXX_HAVE_SOURCE_LOCATION)
test_failure::test_failure(std::string const &desc, std::source_location loc) :
        std::logic_error{desc}, m_loc{loc}
{}
#else
test_failure::test_failure(
  std::string const &ffile, int fline, std::string const &desc) :
        std::logic_error(desc), m_file(ffile), m_line(fline)
{}
#endif

test_failure::~test_failure() noexcept = default;


/// Drop table, if it exists.
inline void drop_table(transaction_base &t, std::string const &table)
{
  t.exec("DROP TABLE IF EXISTS " + table);
}


[[noreturn]] void check_notreached(
#if !defined(PQXX_HAVE_SOURCE_LOCATION)
  char const file[], int line,
#endif
  std::string desc
#if defined(PQXX_HAVE_SOURCE_LOCATION)
  ,
  std::source_location loc
#endif
)
{
  throw test_failure{
#if !defined(PQXX_HAVE_SOURCE_LOCATION)
    file, line,
#endif
    desc
#if defined(PQXX_HAVE_SOURCE_LOCATION)
    ,
    loc
#endif
  };
}


void check(
#if !defined(PQXX_HAVE_SOURCE_LOCATION)
  char const file[], int line,
#endif
  bool condition, char const text[], std::string const &desc
#if defined(PQXX_HAVE_SOURCE_LOCATION)
  ,
  std::source_location loc
#endif
)
{
  if (not condition)
    throw test_failure{
#if !defined(PQXX_HAVE_SOURCE_LOCATION)
      file, line,
#endif
      desc + " (failed expression: " + text + ")"
#if defined(PQXX_HAVE_SOURCE_LOCATION)
      ,
      loc
#endif
    };
}


void expected_exception(std::string const &message)
{
  std::cout << "(Expected) " << message << std::endl;
}


std::string list_row(row Obj)
{
  return separated_list(", ", std::begin(Obj), std::end(Obj), deref_field);
}


std::string list_result(result Obj)
{
  if (std::empty(Obj))
    return "<empty>";
  return "{" +
         separated_list(
           "}\n{", std::begin(Obj), std::end(Obj),
           [](row r) { return list_row(r); }) +
         "}";
}


std::string list_result_iterator(result::const_iterator Obj)
{
  return "<iterator at " + to_string(Obj.rownumber()) + ">";
}


void create_pqxxevents(transaction_base &t)
{
  t.exec(
    "CREATE TEMP TABLE pqxxevents(year integer, event varchar) "
    "ON COMMIT PRESERVE ROWS");
  t.exec("INSERT INTO pqxxevents(year, event) VALUES (71, 'jtv')");
  t.exec("INSERT INTO pqxxevents(year, event) VALUES (38, 'time_t overflow')");
  t.exec(
    "INSERT INTO pqxxevents(year, event) VALUES (1, '''911'' WTC attack')");
  t.exec("INSERT INTO pqxxevents(year, event) VALUES (81, 'C:\\>')");
  t.exec(
    "INSERT INTO pqxxevents(year, event) VALUES (1978, 'bloody\t\tcold')");
  t.exec("INSERT INTO pqxxevents(year, event) VALUES (99, '')");
  t.exec("INSERT INTO pqxxevents(year, event) VALUES (2002, 'libpqxx')");
  t.exec(
    "INSERT INTO pqxxevents(year, event) "
    "VALUES (1989, 'Ode an die Freiheit')");
  t.exec(
    "INSERT INTO pqxxevents(year, event) VALUES (2001, 'New millennium')");
  t.exec("INSERT INTO pqxxevents(year, event) VALUES (1974, '')");
  t.exec("INSERT INTO pqxxevents(year, event) VALUES (97, 'Asian crisis')");
  t.exec(
    "INSERT INTO pqxxevents(year, event) VALUES (2001, 'A Space Odyssey')");
}
} // namespace pqxx::test


namespace
{
std::map<std::string const, pqxx::test::testfunc> *all_tests{nullptr};
} // namespace


namespace pqxx::test
{
void register_test(char const name[], pqxx::test::testfunc func)
{
  if (all_tests == nullptr)
  {
    all_tests = new std::map<std::string const, pqxx::test::testfunc>();
  }
  else
  {
    assert(all_tests->find(name) == all_tests->end());
  }
  (*all_tests)[name] = func;
}
} // namespace pqxx::test


int main(int argc, char const *argv[])
{
  char const *const test_name{(argc > 1) ? argv[1] : nullptr};

  int test_count = 0;
  std::list<std::string> failed;
  for (auto const &i : *all_tests)
    if (test_name == nullptr or std::string{test_name} == std::string{i.first})
    {
      std::cout << std::endl << "Running: " << i.first << std::endl;

      bool success = false;
      try
      {
        i.second();
        success = true;
      }
      catch (pqxx::test::test_failure const &e)
      {
        std::cerr << "Test failure in " << e.file() << " line "
                  << pqxx::to_string(e.line()) << ": " << e.what()
                  << std::endl;
      }
      catch (std::bad_alloc const &)
      {
        std::cerr << "Out of memory!" << std::endl;
      }
      catch (pqxx::feature_not_supported const &e)
      {
        std::cerr << "Not testing unsupported feature: " << e.what() << '\n';
#if defined(PQXX_HAVE_SOURCE_LOCATION)
        std::string func{e.location.function_name()};
        std::cerr << "(";
        std::cerr << e.location.file_name() << ':' << e.location.line();
        if (not func.empty())
          std::cerr << " in " << e.location.function_name();
        std::cerr << ")\n";
#endif
        success = true;
        --test_count;
      }
      catch (pqxx::sql_error const &e)
      {
        std::cerr << "SQL error: " << e.what() << '\n';
#if defined(PQXX_HAVE_SOURCE_LOCATION)
        std::string func{e.location.function_name()};
        std::cerr << "(";
        std::cerr << e.location.file_name() << ':' << e.location.line();
        if (not func.empty())
          std::cerr << " in " << e.location.function_name();
        std::cerr << ")\n";
#endif
        std::cerr << "Query was: " << e.query() << std::endl;
      }
      catch (std::exception const &e)
      {
        std::cerr << "Exception: " << e.what() << std::endl;
      }
      catch (...)
      {
        std::cerr << "Unknown exception" << std::endl;
      }

      if (not success)
      {
        std::cerr << "FAILED: " << i.first << std::endl;
        failed.emplace_back(i.first);
      }
      ++test_count;
    }

  std::cout << "Ran " << test_count << " test(s).\n";

  if (not std::empty(failed))
  {
    std::cerr << "*** " << std::size(failed) << " test(s) failed: ***\n";
    for (auto const &i : failed) std::cerr << "\t" << i << '\n';
  }

  return int(std::size(failed));
}
