#include <iostream>

#include "smtk/common/TypeName.h"

#include <QApplication>
#include <QLabel>
#include <QLayout>
#include <QLineEdit>
#include <QMainWindow>
#include <QKeySequence>
#include <QShortcut>
#include <QWidget>

#include "tao/pegtl.hpp"
#include "tao/pegtl/contrib/icu/utf8.hpp"

extern "C"
{
#include "udunits2.h"

static bool inInit = true;

ut_status basic_visitor(const ut_unit* unit, void* arg)
{
  (void)arg;
  if (!unit) { std::cerr << "Bad arg\n"; return UT_BAD_ARG; }
  const auto* uname = ut_get_name(unit, UT_UTF8);
  const auto* usymb = ut_get_symbol(unit, UT_UTF8);

  std::cout << "  Related to basic " << (uname ? uname : "null") << " (symbol " << (usymb ? usymb : "null") << ")\n";
  return UT_SUCCESS;
}

ut_status product_visitor(const ut_unit* unit, int count, const ut_unit* const* basicUnits, const int* powers, void* arg)
{
  (void)arg;
  if (!unit) { std::cerr << "Bad arg\n"; return UT_BAD_ARG; }
  const auto* uname = ut_get_name(unit, UT_UTF8);
  const auto* usymb = ut_get_symbol(unit, UT_UTF8);

  std::cout << "  Related to product " << (uname ? uname : "null") << " (symbol " << (usymb ? usymb : "null") << ") with " << count << " basics:\n";
  for (int ii = 0; ii < count; ++ii)
  {
    std::cout << "    " << ut_get_name(basicUnits[ii], UT_UTF8) << "^" << powers[ii] << "\n";
  }
  return UT_SUCCESS;
}

ut_status galilean_visitor(const ut_unit* unit, double scale, const ut_unit* underlyingUnit, double origin, void* arg)
{
  (void)arg;
  try
  {
  // std::cout << "  Galilean " << unit << " underly " << underlyingUnit << "\n";
  if (!unit) { std::cerr << "Bad arg\n"; return UT_BAD_ARG; }
  const auto* uname = unit ? ut_get_name(unit, UT_UTF8) : nullptr;
  const auto* usymb = unit ? ut_get_symbol(unit, UT_UTF8) : nullptr;

  std::cerr
    << "  Related to galilean " << (uname ? uname : "null")
    << " (symbol " << (usymb ? usymb : "null") << ") with scale "
    << scale << " origin " << origin << " relative to "
    << (underlyingUnit ? ut_get_name(underlyingUnit, UT_UTF8) : "null")
    << "\n";
  std::cerr.flush();
  }
  catch (std::exception& e)
  {
    std::cerr << "ERROR: " << e.what() << "\n";
  }
  return UT_SUCCESS;
}

ut_status timestamp_visitor(const ut_unit* unit, const ut_unit* timeUnit, double origin, void* arg)
{
  (void)arg;
  if (!unit || !timeUnit) { std::cerr << "Bad arg\n"; return UT_BAD_ARG; }
  const auto* uname = ut_get_name(unit, UT_UTF8);
  const auto* usymb = ut_get_symbol(unit, UT_UTF8);

  std::cout << "  Related to timestamp " << (uname ? uname : "null") << " (symbol " << (usymb ? usymb : "null") << ") with origin " << origin << " relative to " << ut_get_name(timeUnit, UT_UTF8) << "\n";
  return UT_SUCCESS;
}

ut_status logarithmic_visitor(const ut_unit* unit, double base, const ut_unit* reference, void* arg)
{
  (void)arg;
  if (!unit || !reference) { std::cerr << "Bad arg\n"; return UT_BAD_ARG; }
  const auto* uname = ut_get_name(unit, UT_UTF8);
  const auto* usymb = ut_get_symbol(unit, UT_UTF8);

  std::cout << "  Related to log " << (uname ? uname : "null") << " (symbol " << (usymb ? usymb : "null") << ") with base " << base << " relative to " << ut_get_name(reference, UT_UTF8) << "\n";
  return UT_SUCCESS;
}

ut_visitor visitor = {
  .visit_basic = &basic_visitor,
  .visit_product = &product_visitor,
  .visit_galilean = &galilean_visitor,
  .visit_timestamp = &timestamp_visitor,
  .visit_logarithmic = &logarithmic_visitor
};

int unit_error_handler(const char* fmt, va_list args)
{
  if (inInit)
  {
    // Ignore warnings when reading the system XML file provided by udunits2.
    return 1;
  }
  (void)args;
  std::cout << fmt << "\n";
  return 1;
}

} // extern "C"

namespace
{

} // anonymous namespace

namespace grammar
{
using namespace tao::pegtl;

struct measurement_state
{
  bool have_value{ false };
  double value{ 1.0 };
  std::string units;
};

struct white_space :
  sor<
    one<' ', '\t', '\n', '\v', '\f', '\r'>,
    TAO_PEGTL_ISTRING(" "), // non-breaking space, U+00a0
    TAO_PEGTL_ISTRING(" "), // en space, U+2002
    TAO_PEGTL_ISTRING(" "), // em space, U+2003
    TAO_PEGTL_ISTRING(" "), // three-per-em space, U+2004
    TAO_PEGTL_ISTRING(" "), // four-per-em space, U+2005
    TAO_PEGTL_ISTRING(" "), // six-per-em space, U+2006
    TAO_PEGTL_ISTRING(" "), // figure space, U+2007
    TAO_PEGTL_ISTRING(" "), // punctuation space, U+2008
    TAO_PEGTL_ISTRING(" "), // thin space, U+2009
    TAO_PEGTL_ISTRING(" "), // hair space, U+200a
    TAO_PEGTL_ISTRING(" "), // narrow non-breaking space, U+202f
    TAO_PEGTL_ISTRING(" "), // medium mathematical space, U+205f
    TAO_PEGTL_ISTRING("␠"), // symbol for space, U+2420
    TAO_PEGTL_ISTRING("　"), // ideographic space, U+3000
    TAO_PEGTL_ISTRING("𑽈") // Kawi punctation space, U+11f48
    // TAO_PEGTL_ISTRING(" "), // Ogham space mark, U+1680
  > {};

// -----
// This fragment (floating-point parse rules) has been
// adapted from PEGTL's example double:
// Copyright (c) 2014-2018 Dr. Colin Hirsch and Daniel Frey
// Visit https://github.com/taocpp/PEGTL/ for license information.
struct plus_minus : opt< one< '+', '-' > > {};
struct dot : one< '.' > {};
struct inf : seq< TAO_PEGTL_ISTRING("inf"),
                  opt< TAO_PEGTL_ISTRING("inity") > > {};
struct nan : seq< TAO_PEGTL_ISTRING("nan"),
                  opt< one< '(' >,
                       plus< alnum >,
                       one< ')' > > > {};
template< typename D >
struct number :
  if_then_else< dot,
                plus< D >,
                seq< plus< D >, opt< dot, star< D > > >
                > {};
struct e : one< 'e', 'E' > {};
struct p : one< 'p', 'P' > {};
struct exponent : seq< plus_minus, plus< tao::pegtl::digit > > {};
struct decimal : seq< number< tao::pegtl::digit >, opt< e, exponent > > {};
struct hexadecimal : seq< one< '0' >, one< 'x', 'X' >, number< xdigit >,
                          opt< p, exponent > > {};
struct value : seq< plus_minus, sor< hexadecimal, decimal, inf, nan > > {};
// -----


// A keyword indicating translation from some baseline.
struct shift_kw : seq<
  star<white_space>,
  sor<
    one<'@'>,
    TAO_PEGTL_ISTRING("after"),
    TAO_PEGTL_ISTRING("from"),
    TAO_PEGTL_ISTRING("ref"),
    TAO_PEGTL_ISTRING("since")
  >,
  star<white_space>> {};

struct divide_kw : seq<
  star<white_space>,
  sor<
    one<'/'>,
    TAO_PEGTL_ISTRING("per")
  >,
  star<white_space>> {};

struct multiply_kw :
  seq<
    sor<
      one<' '>,
      seq<
        star<white_space>,
        sor<
          one<'*', '-'>,
          TAO_PEGTL_ISTRING("·")
          // TAO_PEGTL_ISTRING("×"), // udunits does not support this yet
        >
      >
    >,
    star<white_space>
  > {};

// Accept either a^b or a**b.
struct exponent_kw : sor<one<'^'>, TAO_PEGTL_ISTRING("**")> {};

struct basic_first :
  sor<
    ranges<'a', 'z', 'A', 'Z'>,
    TAO_PEGTL_ISTRING("‘"),
    TAO_PEGTL_ISTRING("’"),
    TAO_PEGTL_ISTRING("“"),
    TAO_PEGTL_ISTRING("”"),
    TAO_PEGTL_ISTRING("°"),
    TAO_PEGTL_ISTRING("Ω"),
    TAO_PEGTL_ISTRING("㏀"),
    TAO_PEGTL_ISTRING("㏁"),
    TAO_PEGTL_ISTRING("℉"),
    TAO_PEGTL_ISTRING("℃"),
    TAO_PEGTL_ISTRING("K"),
    TAO_PEGTL_ISTRING("Å"),
    TAO_PEGTL_ISTRING("µ")
  > {};

struct basic_expression :
  seq<
    basic_first,
    star<
      sor<
        basic_first,
        one<'_'>
      >
    >
  > {};

struct power_expression :
  sor<
    seq<basic_expression, exponent_kw, exponent>,
    // seq<basic_expression, exponent>,
    basic_expression
  > {};

struct product_expression :
  sor<
    seq<power_expression, multiply_kw, product_expression>,
    seq<power_expression,   divide_kw, product_expression>,
    // seq<product_expression, multiply_kw, power_expression>,
    // seq<product_expression, divide_kw, power_expression>,
    // seq<product_expression, power_expression>,
    power_expression
  > {};

struct shifted_expression :
  sor<
    seq<product_expression, shift_kw, value>,
    //, seq<product_expression, shift_kw, timestamp>
    product_expression
  > {};

struct units : must<shifted_expression, eof> {};
struct measurement : must<sor<seq<value,star<white_space>,units>, seq<value, eof>>> {};

template<typename Rule>
struct Action : nothing<Rule>
{
};

struct capture_value
{
  template<typename Input>
  static void apply(const Input& input, measurement_state& s)
  {
    std::cout << "value «" << input.string() << "»\n";
    s.have_value = true;
    s.value = std::strtod(input.string().c_str(), nullptr);
  }
};

struct capture_units
{
  template<typename Input>
  static void apply(const Input& input, measurement_state& s)
  {
    s.units = input.string();
    std::cout << "basic expression «" << input.string() << "»\n";
  }
};

template <> struct Action<units> : capture_units {};
template <> struct Action<value> : capture_value {};

bool parseUnits(const std::string& input, measurement_state& state)
{
  bool ok = true;
  tao::pegtl::string_input<> in(input, "validateUnits");
  try
  {
    ok = tao::pegtl::parse<units, Action>(in, state);
    std::cout << "--- parsed input <<" << input << ">>? " << (ok ? "Y":"N") << "\n";
  }
  catch (tao::pegtl::parse_error& err)
  {
    const auto p = err.positions.front();
    std::cerr <<
      "parseUnits: " << err.what() << "\n"
      << in.line_as_string(p) << "\n"
      << std::string(p.byte_in_line, ' ') << "^\n";
    ok = false;
    return ok;
  }

  return ok;
}

bool parseMeasurement(const std::string& input, measurement_state& state)
{
  tao::pegtl::string_input<> in(input, "validateUnits");
  try
  {
    tao::pegtl::parse<measurement, Action>(in, state);
  }
  catch (tao::pegtl::parse_error& err)
  {
    const auto p = err.positions.front();
    std::cerr <<
      "parseMeasurement: " << err.what() << "\n"
      << in.line_as_string(p) << "\n"
      << std::string(p.byte_in_line, ' ') << "^\n";
    return false;
  }

  return true;
}

} // namespace grammar


int main(int argc, char* argv[])
{
  auto* initialErrorHandler = ut_set_error_message_handler(&::unit_error_handler);
  (void)initialErrorHandler;
  auto* unitSystem = ut_read_xml(nullptr);
  inInit = false;
  if (!unitSystem)
  {
    std::cerr << "Could not initialize udunits.\n";
    return 1;
  }

  auto* destinationUnit = ut_get_unit_by_name(unitSystem, "m");
  if (!destinationUnit)
  {
    destinationUnit = ut_get_unit_by_symbol(unitSystem, "m");
  }
  if (!destinationUnit)
  {
    std::cerr << "Oops, no meter unit.\n";
    return 1;
  }
  ut_accept_visitor(destinationUnit, &visitor, nullptr);

  QApplication app(argc, argv);
  auto* ww = new QWidget;
  auto* ly = new QHBoxLayout(ww);
  auto* ll = new QLabel("Wheelbase");
  auto* le = new QLineEdit;
  ww->setLayout(ly);
  ly->addWidget(ll);
  ly->addWidget(le);
  le->setPlaceholderText("[m]");
  QPalette pal = le->palette();
  auto* mw = new QMainWindow;
  auto* sc = new QShortcut(QKeySequence("Ctrl+Q"), mw);
  QObject::connect(sc, &QShortcut::activated, [&]() { app.closeAllWindows(); });
  mw->setCentralWidget(ww);
  mw->show();
  QObject::connect(le, &QLineEdit::textChanged, [&](const QString& text)
    {
      grammar::measurement_state state;
      auto utext = text.toStdString();
      if (utext.empty())
      {
        pal.setColor(QPalette::Base, QColor("#ffffff"));
        le->setPalette(pal);
        return;
      }

      if (!grammar::parseMeasurement(utext, state))
      {
        pal.setColor(QPalette::Base, QColor("#ff9999"));
        le->setPalette(pal);
        return;
      }
      // pal.setColor(QPalette::Base, QColor("#ffffff"));
      // le->setPalette(pal);

      auto* sourceUnit = ut_parse(unitSystem, state.units.c_str(), UT_UTF8);
      if (!sourceUnit)
      {
        std::cerr << "  Cannot ut_parse.\n";
        pal.setColor(QPalette::Base, QColor("#ffbbbb"));
        le->setPalette(pal);
        return;
      }

      const auto* uname = ut_get_name(sourceUnit, UT_UTF8);
      const auto* usymb = ut_get_symbol(sourceUnit, UT_UTF8);
      std::cout << "  Parsed to " << sourceUnit << " " << (uname && uname[0] ? uname : "nullity") << " " << (usymb && usymb[0] ? usymb : "nullity") << "\n";
      // ut_accept_visitor(sourceUnit, &visitor, sourceUnit);
      if (auto* converter = ut_get_converter(sourceUnit, destinationUnit))
      {
        pal.setColor(QPalette::Base, QColor("#ffffff"));
        le->setPalette(pal);
        std::cout << "  1 " << utext << " = " << cv_convert_double(converter, 1.0) << " m\n";
      }
      else
      {
        std::cerr << "  Cannot convert.\n";
        pal.setColor(QPalette::Base, QColor("#ff9999"));
        le->setPalette(pal);
      }
      ut_free(sourceUnit);
    }
  );

  app.exec();

  ut_free(destinationUnit);
  ut_free_system(unitSystem);
  return 0;
}
