#include <Rcpp.h>
// Copyright (c) 2016  GeometryFactory Sarl (France)
// All rights reserved.
//
// This file is part of CGAL (www.cgal.org).
//
// $URL: https://github.com/CGAL/cgal/blob/v5.6.1/Three/include/CGAL/Three/exceptions.h $
// $Id: exceptions.h 2478b85 2021-11-04T09:14:02+01:00 Andreas Fabri
// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial
//
//
// Author(s)     : Laurent RINEAU

#ifndef CGAL_THREE_EXCEPTIONS_H
#define CGAL_THREE_EXCEPTIONS_H

#include <CGAL/license/Three.h>


#include <exception>
#include <QString>
#include <QApplication>
#include <QScriptable>
#include <QScriptContext>
#include <QScriptEngine>
#include <boost/optional.hpp>
#include <QStringList>

namespace CGAL{
namespace Three{

class Script_exception : public std::runtime_error {
  QStringList bt;
public:
  Script_exception(QString what_arg,
                   QStringList backtrace)
    : std::runtime_error(what_arg.toStdString())
    , bt(backtrace)
  {}

  QStringList backtrace() const {
    return bt;
  }
};

template <typename T>
struct Optional_or_bool {
  typedef boost::optional<T> type;

  template <typename Callable>
  static type invoke(Callable f) { return type(f()); }
};

template <>
struct Optional_or_bool<void> {
  typedef bool type;

  template <typename Callable>
  static type invoke(Callable f) { f(); return true; }
};

enum Context { CURRENT_CONTEXT, PARENT_CONTEXT };

/// This function template wraps a `Callable` that can be called without
/// any argument (such as a lambda expression without arguments), and in
/// case the function call is in a Qt Script context, wraps the call in a
/// try/catch that converts the possible exception throw into a Qt Script
/// exception. That allows a Qt Script to catch the exception and deal
/// with it.
template <typename Callable>
typename Optional_or_bool<typename cpp11::result_of<Callable()>::type>::type
wrap_a_call_to_cpp(Callable f,
                   QScriptable* qs = 0,
                   const char* file = 0,
                   int line = -1,
                   Context c = CURRENT_CONTEXT) {
  typedef typename cpp11::result_of<Callable()>::type Callable_RT;
  typedef Optional_or_bool<Callable_RT> O_r_b;
  typedef typename O_r_b::type Return_type;

  const bool no_try_catch = qApp->property("no-try-catch").toBool();
  if(no_try_catch || qs == 0 || !qs->context()) return O_r_b::invoke(f);
  else
    try {
      return O_r_b::invoke(f);
    }
    catch(const std::exception& e) {
      const Script_exception* se = dynamic_cast<const Script_exception*>(&e);
      QScriptContext* context = qs->context();
      QStringList qt_bt = context->backtrace();
      if(se) qt_bt = se->backtrace();
      Rcpp::Rcerr << "Backtrace:\n";
      Q_FOREACH(QString s, qt_bt)
      {
        Rcpp::Rcerr << "  " << qPrintable(s) << std::endl;
      }
      context = context->parentContext();
      if(c == PARENT_CONTEXT) {
        Rcpp::Rcerr << "> parent";
        context = context->parentContext();
      } else {
        Rcpp::Rcerr << "> current";
      }
      Rcpp::Rcerr << " context: "
                << qPrintable(context->toString()) << std::endl;
      QString error;
      if(se) {
        error = se->what();
      } else {
        error = QObject::tr("Error");
        QString context;
        if(file != 0) context += QObject::tr(" at file %1").arg(file);
        if(line != -1) context += QString(":%1").arg(line);
        if(!context.isNull()) {
          error += context;
          qt_bt.push_front(QObject::tr("<cpp>") + context);
        }
        error += QString(": %1").arg(e.what());
      }
      QScriptValue v = context->throwError(error);
      v.setProperty("backtrace",
                    qScriptValueFromSequence(context->engine(), qt_bt));
      Rcpp::Rcerr << "result after throwError: "
                << qPrintable(v.toString()) << std::endl;
      return Return_type();
    }
}

} // end namespace Three
} // end namespace CGAL

#endif // CGAL_THREE_EXCEPTIONS_H

