text: // Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
  // under NSF AWARD 1414736 and by the respective contributors.
  // All rights reserved.
  //
  // SPDX-License-Identifier: BSD-3-Clause
  
  #include "app_helper.hpp"
  
  #include <complex>
  #include <cstdint>
  
  using Catch::Matchers::Contains;
  using Catch::Approx;
  
  using cx = std::complex<double>;
  
  CLI::Option *
  add_option(CLI::App &app, std::string name, cx &variable, std::string description = "", bool defaulted = false) {
      CLI::callback_t fun = [&variable](CLI::results_t res) {
          double x, y;
          bool worked = CLI::detail::lexical_cast(res[0], x) && CLI::detail::lexical_cast(res[1], y);
          if(worked)
              variable = cx(x, y);
          return worked;
      };
  
      CLI::Option *opt = app.add_option(name, fun, description, defaulted);
      opt->type_name("COMPLEX")->type_size(2);
      if(defaulted) {
          std::stringstream out;
          out << variable;
          opt->default_str(out.str());
      }
      return opt;
  }
  
  TEST_CASE_METHOD(TApp, "AddingComplexParser", "[complex]") {
  
      cx comp{0, 0};
      add_option(app, "-c,--complex", comp);
      args = {"-c", "1.5", "2.5"};
  
      run();
  
      CHECK(comp.real() == Approx(1.5));
      CHECK(comp.imag() == Approx(2.5));
  }
  
  TEST_CASE_METHOD(TApp, "DefaultedComplex", "[complex]") {
  
      cx comp{1, 2};
      add_option(app, "-c,--complex", comp, "", true);
      args = {"-c", "4", "3"};
  
      std::string help = app.help();
      CHECK_THAT(help, Contains("1"));
      CHECK_THAT(help, Contains("2"));
  
      CHECK(comp.real() == Approx(1));
      CHECK(comp.imag() == Approx(2));
  
      run();
  
      CHECK(comp.real() == Approx(4));
      CHECK(comp.imag() == Approx(3));
  }
  
  // an example of custom complex number converter that can be used to add new parsing options
  #if defined(__has_include)
  #if __has_include(<regex>)
  // an example of custom converter that can be used to add new parsing options
  #define HAS_REGEX_INCLUDE
  #endif
  #endif
  
  #ifdef HAS_REGEX_INCLUDE
  // Gcc 4.8 and older and the corresponding standard libraries have a broken <regex> so this would
  // fail.  And if a clang compiler is using libstd++ then this will generate an error as well so this is just a check to
  // simplify compilation and prevent a much more complicated #if expression
  #include <regex>
  namespace CLI {
  namespace detail {
  
  // On MSVC and possibly some other new compilers this can be a free standing function without the template
  // specialization but this is compiler dependent
  template <> bool lexical_cast<std::complex<double>>(const std::string &input, std::complex<double> &output) {
      // regular expression to handle complex numbers of various formats
      static const std::regex creg(
          R"(([+-]?(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)\s*([+-]\s*(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)[ji]*)");
  
      std::smatch m;
      double x{0.0}, y{0.0};
      bool worked;
      std::regex_search(input, m, creg);
      if(m.size() == 9) {
          worked = CLI::detail::lexical_cast(m[1], x) && CLI::detail::lexical_cast(m[6], y);
          if(worked) {
              if(*m[5].first == '-') {
                  y = -y;
              }
          }
      } else {
          if((input.back() == 'j') || (input.back() == 'i')) {
              auto strval = input.substr(0, input.size() - 1);
              CLI::detail::trim(strval);
              worked = CLI::detail::lexical_cast(strval, y);
          } else {
              std::string ival = input;
              CLI::detail::trim(ival);
              worked = CLI::detail::lexical_cast(ival, x);
          }
      }
      if(worked) {
          output = cx{x, y};
      }
      return worked;
  }
  }  // namespace detail
  }  // namespace CLI
  
  TEST_CASE_METHOD(TApp, "AddingComplexParserDetail", "[complex]") {
  
      bool skip_tests = false;
      try {  // check if the library actually supports regex,  it is possible to link against a non working regex in the
             // standard library
          std::smatch m;
          std::string input = "1.5+2.5j";
          static const std::regex creg(
              R"(([+-]?(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)\s*([+-]\s*(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)[ji]*)");
  
          auto rsearch = std::regex_search(input, m, creg);
          if(!rsearch) {
              skip_tests = true;
          } else {
              CHECK(9u == m.size());
          }
  
      } catch(...) {
          skip_tests = true;
      }
      static_assert(CLI::detail::is_complex<cx>::value, "complex should register as complex in this situation");
      if(!skip_tests) {
          cx comp{0, 0};
  
          app.add_option("-c,--complex", comp, "add a complex number option");
          args = {"-c", "1.5+2.5j"};
  
          run();
  
          CHECK(comp.real() == Approx(1.5));
          CHECK(comp.imag() == Approx(2.5));
          args = {"-c", "1.5-2.5j"};
  
          run();
  
          CHECK(comp.real() == Approx(1.5));
          CHECK(comp.imag() == Approx(-2.5));
      }
  }
  #endif
  // defining a new complex class
  class complex_new {
    public:
      complex_new() = default;
      complex_new(double v1, double v2) : val1_{v1}, val2_{v2} {};
      double real() { return val1_; }
      double imag() { return val2_; }
  
    private:
      double val1_{0.0};
      double val2_{0.0};
  };
  
  TEST_CASE_METHOD(TApp, "newComplex", "[complex]") {
      complex_new cval;
      static_assert(CLI::detail::is_complex<complex_new>::value, "complex new does not register as a complex type");
      static_assert(CLI::detail::classify_object<complex_new>::value == CLI::detail::object_category::complex_number,
                    "complex new does not result in complex number categorization");
      app.add_option("-c,--complex", cval, "add a complex number option");
      args = {"-c", "1.5+2.5j"};
  
      run();
  
      CHECK(cval.real() == Approx(1.5));
      CHECK(cval.imag() == Approx(2.5));
      args = {"-c", "1.5-2.5j"};
  
      run();
  
      CHECK(cval.real() == Approx(1.5));
      CHECK(cval.imag() == Approx(-2.5));
  }
  