// This may look like C code, but it's really -*- C++ -*-
/*
 * Copyright (C) 2008 Emweb bvba, Kessel-Lo, Belgium.
 *
 * See the LICENSE file for terms of use.
 */
#ifndef WJAVASCRIPT_H_
#define WJAVASCRIPT_H_

#include <Wt/WSignal>
#include <Wt/WEvent>
#include <Wt/WException>
#include <Wt/WJavaScriptSlot>
#include <Wt/WLogger>
#include <Wt/WString>
#include <boost/lexical_cast.hpp>

namespace Wt {
/*! \class JSignal Wt/WJavaScript Wt/WJavaScript
 *  \brief A signal to relay JavaScript to C++ calls.
 *
 * A JSignal, like an EventSignal, provides communicates events from
 * JavaScript to C++ code. However, it not tied to a built-in
 * event. Instead, it can be emitted from within custom JavaScript
 * code using the JavaScript Wt.emit() function.
 *
 * The signal is identified by a unique name within the scope of a
 * WObject, or a unique global name (when declaring the signal in your
 * WApplication).
 * 
 * The signal supports up to 6 arguments. Values for these arguments
 * may be specified in the JavaScript Wt.emit() (or the deprecated
 * global function WtSignalEmit()).
 *
 * Example code:
 * \code
 * class MyWidget : public WCompositeWidget
 * {
 * public:
 *   MyWidget()
 *     : doSome_(this, "doSome")
 *   {
 *     ...
 *   }
 *
 *   JSignal<std::string, int>& doSome() { return doSome_; }
 *
 * private:
 *   JSignal<std::string, int> doSome_;
 *
 *   ...
 * };
 *
 * \endcode
 *
 * The following JavaScript statement will emit the signal for a DOM
 * element <i>element</i> that corresponds to a widget of class
 * <tt>MyWidget</tt>:
 *
 * \code
 * Wt.emit(element, 'dosome', 'foo', 42);
 * \endcode
 *
 * The <i>element</i> can be a \link Wt::WWidget::jsRef() DOM
 * element\endlink, or the \link Wt::WObject::id() object ID\endlink of a
 * WObject, or the constant <tt>"Wt"</tt> which is an alias for
 * Wt::WApplication::instance()->id(). The conversion between the
 * JavaScript arguments (ax) and the C++ type Ax uses
 * <tt>boost::lexical_cast<Ax>(ax)</tt>.
 *
 * You can use the methods createCall() to let the signal itself
 * generate this JavaScript call for you:
 * \code
 * doSome_.createCall("'foo'", "42");
 * \endcode
 *
 * The JavaScript generated by createCall() is possibly affected by every
 * connect or disconnect to the signal. In practice, you will use JSignal
 * internally within a widget and call createCall() only after you connected
 * internal slots to signal.
 *
 * It is also possible to propagate an original JavaScript event as a
 * last argument, of type WMouseEvent or WKeyEvent. In that case, the
 * second argument in <tt>Wt.emit()</tt> must be an object which
 * indicates also the JavaScript event and event target.
 *
 * Consider a signal declaration:
 * \code
 * JSignal<std::string, int, WMouseEvent> doSome();
 * \endcode
 *
 * Then, the following would be a suitable JavaScript call:
 *
 * \code
 * Wt.emit(Wt, {name: 'dosome', event: event, eventObject: object}, 'foo', 42);
 * \endcode
 *
 * The method createEventCall() may be used this variation for the JavaScript
 * method call.
 *
 * Since the conversion from JavaScript to C++ uses
 * <tt>boost::lexical_cast<T>(arg)</tt>, you may provide support for
 * custom types by implementing the c++ input stream operator
 * <tt>operator>> (std::istream&, T& t)</tt> for your type.
 *
 * \sa WWidget::jsRef(), WObject::id()
 *
 * \ingroup signalslot
 */
template <typename A1 = NoClass, typename A2 = NoClass,
	  typename A3 = NoClass, typename A4 = NoClass,
	  typename A5 = NoClass, typename A6 = NoClass>
class JSignal : public EventSignalBase
{
public:
  /*! \brief Construct a signal for the given object, and name.
   *
   * The given \p name must be unique for all user signals
   * specified for the object \p object. Ownership of the signal
   * is not transferred to the object.
   *
   * If \p collectSlotJavaScript is \c true, then javascript specified
   * for connected slots (using JSlot) or learned by stateless slot
   * learning, is collected to client-side JavaScript.
   *
   * Use the utility methods createCall() or createEventCall() to
   * create the appropriate JavaScript statements that invoke the
   * signal, which take into account possible other client-side
   * JavaScript handling associated with the signal.
   *
   * \sa \link WObject::implementStateless() stateless slot learning\endlink
   */
  JSignal(WObject *object, const std::string& name,
	  bool collectSlotJavaScript = false);

  /*! \brief Destructor.
   */
  ~JSignal();

  /*! \brief Returns the signal name.
   */
  const std::string& name() const { return name_; }

  virtual const std::string encodeCmd() const;

  /*! \brief Returns a JavaScript call that triggers the signal.
   *
   * This is:
   * \code
     Wt.emit([element], [name], arg1, ...);
   * \endcode
   *
   * When the signal was constructed with \p collectSlotJavaScript ==
   * \c true, the inline JavaScript from slots defined as JavaScript
   * or from learned stateless slots, or directly connected to the
   * signal, is included as well.
   *
   * \note The method only takes into account JavaScript from slot
   *       connections that have been connected so far, and any
   *       subsequent connections are ignored.
   *
   * \sa createEventCall()
   */
  const std::string createCall(const std::string& arg1 = std::string(),
			       const std::string& arg2 = std::string(),
			       const std::string& arg3 = std::string(),
			       const std::string& arg4 = std::string(),
			       const std::string& arg5 = std::string(),
			       const std::string& arg6 = std::string()) const;

  /*! \brief Returns a JavaScript call that triggers the signal, passing
   *         the original event too.
   *
   * Similar to createCall(), the following JavaScript is returned:
   * \code
     Wt.emit([element], { name: [name], eventObject: [jsObject], event: [jsEvent]},
             arg1, ...);
   * \endcode
   *
   * In addition to information identifying the signal (\p element
   * and \p name) and the arguments, also information on the original
   * JavaScript event is transferred. In this way, you can propagate the
   * corresponding event class (WMouseEvent or WKeyEvent) as an additional
   * last argument in the slot.
   *
   * \note The method only takes into account JavaScript from slot
   *       connections that have been connected so far, and any
   *       subsequent connections are ignored.
   *
   */
  const std::string createEventCall(const std::string& jsObject,
				    const std::string& jsEvent,
				    const std::string& arg1 = std::string(),
				    const std::string& arg2 = std::string(),
				    const std::string& arg3 = std::string(),
				    const std::string& arg4 = std::string(),
				    const std::string& arg5 = std::string())
    const;

  /*! \brief Returns whether the signal is connected to at least one slot.
   */
  virtual bool isConnected() const;

  /*! \brief Connect to a function.
   *
   * This variant of the overloaded connect() method supports a
   * template function object (which supports operator ()).
   *
   * When the receiver function is an object method, the signal will
   * automatically be disconnected when the object is deleted, as long as the
   * object inherits from WObject or boost::signals::trackable.
   *
   * The function may leave 1 parameters unbound (e.g. using
   * boost::bind placeholders _1) that may be bound to the event
   * detail object passed by the signal.
   */
  template<class F>
    boost::signals::connection connect(const F& function);

  /*! \brief Connect a slot that takes no arguments.
   *
   * The slot is specified as a method of an object \p target of class
   * \p T, which equals class \p V, or is a base class of class \p
   * V. In addition, to check for stateless implementations, class \p
   * T must be also be a descendant of WObject. Thus, the following
   * statement must return a non-null pointer:
   *
   * \code
   * WObject *o = dynamic_cast<WObject *>(dynamic_cast<V *>(target));
   * \endcode
   *
   * If a stateless implementation is specified for the slot, then
   * the visual behaviour will be learned in terms of JavaScript, and
   * will be cached on the client side for instant feed-back, in
   * addition running the slot on the server.
   */
  template<class T, class V>
    boost::signals::connection connect(T *target, void (V::*method)());

  /*! \brief Connect a slot that takes one argument.
   *
   * This is only possible for signals that take at least one argument.
   *
   * \sa connect(T *target, void (V::*method)())
   */
  template<class T, class V>
    boost::signals::connection connect(T *target, void (V::*method)(A1));

  template<class T, class V>
    boost::signals::connection connect(T *target, void (V::*method)(const A1&));

  /*! \brief Connect a slot that takes two arguments.
   *
   * This is only possible for signals that take at least two arguments.
   *
   * \sa connect(T *target, void (V::*method)())
   */
  template<class T, class V>
    boost::signals::connection connect(T *target, void (V::*method)(A1, A2));

  /*! \brief Connect a slot that takes three arguments.
   *
   * This is only possible for signals that take at least three arguments.
   *
   * \sa connect(T *target, void (V::*method)())
   */
  template<class T, class V>
    boost::signals::connection connect(T *target,
				       void (V::*method)(A1,A2,A3));

  /*! \brief Connect a slot that takes four arguments.
   *
   * This is only possible for signals that take at least four arguments.
   *
   * \sa connect(T *target, void (V::*method)())
   */
  template<class T, class V>
    boost::signals::connection connect(T *target,
				       void (V::*method)(A1,A2,A3,A4));

  /*! \brief Connect a slot that takes five arguments.
   *
   * This is only possible for signals that take at least five arguments.
   *
   * \sa connect(T *target, void (V::*method)())
   */
  template<class T, class V>
    boost::signals::connection connect(T *target,
				       void (V::*method)(A1,A2,A3,A4,A5));

  /*! \brief Connect a slot that takes six arguments.
   *
   * This is only possible for signals that take at least six arguments.
   *
   * \sa connect(T *target, void (V::*method)())
   */
  template<class T, class V>
    boost::signals::connection connect(T *target,
				       void (V::*method)(A1,A2,A3,A4,A5,A6));

  /*! \brief Connects a JavaScript function.
   *
   * This will provide a client-side connection between the event and
   * a JavaScript function. The argument must be a JavaScript function
   * which optionally accepts up to two arguments (object and event):
   *
   * \code
   * function(object, event) {
   *   ...
   * }
   * \endcode
   *
   * Unlike a JSlot, there is no automatic connection management: the
   * connection cannot be removed. If you need automatic connection
   * management, you should use connect(JSlot&) instead.
   */
  void connect(const std::string& function);
  void connect(const char * function);

  /*! \brief Connect a slot that is specified as JavaScript only.
   *
   * This will provide a client-side connection between the event and
   * some JavaScript code as implemented by the slot. Unlike other
   * connects, this does not cause the event to propagated to the
   * application, and thus the state changes induced by the
   * \p slot are invisible to the server-side.
   */
  void connect(JSlot& slot);

  /*! \brief Emit the signal.
   *
   * The arguments must exactly match the arguments of the target
   * function.
   *
   * This will cause all connected slots to be triggered, with the given
   * arguments.
   */
  void emit(A1 a1 = NoClass::none, A2 a2 = NoClass::none,
	    A3 a3 = NoClass::none, A4 a4 = NoClass::none,
	    A5 a5 = NoClass::none, A6 a6 = NoClass::none);

  /*! \brief Emit the signal.
   *
   * This is equivalent to emit().
   *
   * \sa emit
   */
  void operator()(A1 a1 = NoClass::none, A2 a2 = NoClass::none,
		  A3 a3 = NoClass::none, A4 a4 = NoClass::none,
		  A5 a5 = NoClass::none, A6 a6 = NoClass::none);

  virtual boost::signals::connection connect(WObject *target,
					     void (WObject::*method)());

private:
  JSignal(const JSignal<A1, A2, A3, A4, A5, A6>&);

  std::string name_;
  mutable std::string senderId_;
  void processDynamic(const JavaScriptEvent& e);
#ifndef _MSC_VER
  // Up to 6 arguments allowed
  typedef boost::signal6<void, A1, A2, A3, A4, A5, A6> BoostSignalType;
#else
#  if _MSC_VER >= 1700
  // MSVS 2012's std::bind implementation may support only 5 arguments
  // http://blogs.msdn.com/b/vcblog/archive/2011/09/12/10209291.aspx
#    if defined(_VARIADIC_MAX) && (_VARIADIC_MAX < 6)
       // default MSVS 2012: defaults to 5. We assume no-one will lower this.
       typedef boost::signal5<void, A1, A2, A3, A4, A5> BoostSignalType;
#      define A6_MUST_BE_NOCLASS
#    else
       // You can reconfigure this, and MS promised to raise _VARIADIC_MAX
       // in future compilers
       typedef boost::signal6<void, A1, A2, A3, A4, A5, A6> BoostSignalType;
#    endif
#  else
    typedef boost::signal6<void, A1, A2, A3, A4, A5, A6> BoostSignalType;
#  endif
#endif

  BoostSignalType *impl_;
};

#ifndef WT_CNOR

/*
 * JSignal -- specialization for void
 */
template<>
class WT_API JSignal<void> : public JSignal<>
{ 
public:
  JSignal(WObject *object, const std::string& name,
	  bool collectSlotJavaScript = false);
};

#else // WT_CNOR

class WT_API JSignal0 : public JSignal<NoClass>
{
public:
  JSignal0(WObject *object, const std::string& name,
	   bool collectSlotJavaScript = false);

  template<class T, class V>
    boost::signals::connection connect(T *target, void (V::*method)());
  boost::signals::connection connect(const boost::bound& f);
  
  void connect(const std::string& function);
  void connect(const char * function);

  void connect(JSlot& slot);

  void emit();
};

#endif // WT_CNOR

#ifndef WT_CNOR

template <typename A1, typename A2, typename A3,
	  typename A4, typename A5, typename A6>
JSignal<A1, A2, A3, A4, A5, A6>::JSignal(WObject *object,
					 const std::string& name,
					 bool collectSlotJavaScript)
  : EventSignalBase(0, object, collectSlotJavaScript),
    name_(name),
    impl_(0)
{
#ifdef A6_MUST_BE_NOCLASS
  A6 YourMsvsOnlySupports5BindArguments = NoClass::none;
#endif
}

template <typename A1, typename A2, typename A3,
	  typename A4, typename A5, typename A6>
JSignal<A1, A2, A3, A4, A5, A6>::~JSignal()
{
  prepareDestruct();
  delete impl_;
}

template <typename A1, typename A2, typename A3,
	  typename A4, typename A5, typename A6>
const std::string JSignal<A1, A2, A3, A4, A5, A6>::encodeCmd() const
{
  if (senderId_.empty())
    senderId_ = sender()->id();

  return senderId_ + "." + name_;
}

template <typename A1, typename A2, typename A3,
	  typename A4, typename A5, typename A6>
const std::string JSignal<A1, A2, A3, A4, A5, A6>
::createCall(const std::string& arg1, const std::string& arg2,
	     const std::string& arg3, const std::string& arg4,
	     const std::string& arg5, const std::string& arg6) const
{
  return EventSignalBase::createUserEventCall(std::string(), std::string(),
					      name_, arg1, arg2, arg3,
					      arg4, arg5, arg6);
}

template <typename A1, typename A2, typename A3,
	  typename A4, typename A5, typename A6>
const std::string JSignal<A1, A2, A3, A4, A5, A6>
::createEventCall(const std::string& jsObject, const std::string& jsEvent,
		  const std::string& arg1, const std::string& arg2,
		  const std::string& arg3, const std::string& arg4,
		  const std::string& arg5) const
{
  return EventSignalBase::createUserEventCall(jsObject, jsEvent,
					      name_, arg1, arg2, arg3,
					      arg4, arg5, "");
}

template <typename A1, typename A2, typename A3,
	  typename A4, typename A5, typename A6>
template <class F>
boost::signals::connection JSignal<A1, A2, A3, A4, A5, A6>
::connect(const F& function)
{
  exposeSignal();
  if (!impl_)
    impl_ = new BoostSignalType;
  return impl_->connect(function);
}

template <typename A1, typename A2, typename A3,
	  typename A4, typename A5, typename A6>
template <class T, class V>
boost::signals::connection JSignal<A1, A2, A3, A4, A5, A6>
::connect(T *target, void (V::*method)(A1, A2, A3, A4, A5, A6))
{
  assert(dynamic_cast<V *>(target));
  return
    connect(boost::bind(method, target, ::_1, ::_2, ::_3, ::_4, ::_5, ::_6));
}

template <typename A1, typename A2, typename A3,
	  typename A4, typename A5, typename A6>
template <class T, class V>
boost::signals::connection JSignal<A1, A2, A3, A4, A5, A6>
::connect(T *target, void (V::*method)(A1, A2, A3, A4, A5))
{
  assert(dynamic_cast<V *>(target));
  return connect(boost::bind(method, target, ::_1, ::_2, ::_3, ::_4, ::_5));
}

template <typename A1, typename A2, typename A3,
	  typename A4, typename A5, typename A6>
template <class T, class V>
boost::signals::connection JSignal<A1, A2, A3, A4, A5, A6>
::connect(T *target, void (V::*method)(A1, A2, A3, A4))
{
  assert(dynamic_cast<V *>(target));
  return connect(boost::bind(method, target, ::_1, ::_2, ::_3, ::_4));
}

template <typename A1, typename A2, typename A3,
	  typename A4, typename A5, typename A6>
template <class T, class V>
boost::signals::connection JSignal<A1, A2, A3, A4, A5, A6>
::connect(T *target, void (V::*method)(A1, A2, A3))
{
  assert(dynamic_cast<V *>(target));
  return connect(boost::bind(method, target, ::_1, ::_2, ::_3));
}

template <typename A1, typename A2, typename A3,
	  typename A4, typename A5, typename A6>
template <class T, class V>
boost::signals::connection JSignal<A1, A2, A3, A4, A5, A6>
::connect(T *target, void (V::*method)(A1, A2))
{
  assert(dynamic_cast<V *>(target));
  return connect(boost::bind(method, target, ::_1, ::_2));
}

template <typename A1, typename A2, typename A3,
	  typename A4, typename A5, typename A6>
template <class T, class V>
boost::signals::connection JSignal<A1, A2, A3, A4, A5, A6>
::connect(T *target, void (V::*method)(A1))
{
  assert(dynamic_cast<V *>(target));
  return connect(boost::bind(method, target, ::_1));
}

template <typename A1, typename A2, typename A3,
	  typename A4, typename A5, typename A6>
template <class T, class V>
boost::signals::connection JSignal<A1, A2, A3, A4, A5, A6>
::connect(T *target, void (V::*method)(const A1&))
{
  assert(dynamic_cast<V *>(target));
  return connect(boost::bind(method, target, ::_1));
}

template <typename A1, typename A2, typename A3,
	  typename A4, typename A5, typename A6>
template <class T, class V>
boost::signals::connection JSignal<A1, A2, A3, A4, A5, A6>
::connect(T *target, void (V::*method)())
{
  exposeSignal();
  WObject *o = dynamic_cast<WObject *>(dynamic_cast<V *>(target));
  assert(o);

  WStatelessSlot *s = o->isStateless(static_cast<WObject::Method>(method));

  if (canAutoLearn() && s)
    return EventSignalBase::connectStateless
      (static_cast<WObject::Method>(method), o, s);
  else
    return connect(boost::bind(method, target));
}

template <typename A1, typename A2, typename A3,
	  typename A4, typename A5, typename A6>
void JSignal<A1, A2, A3, A4, A5, A6>::connect(const std::string& function)
{
  if (!canAutoLearn()) {
    Wt::log("error") << "JSignal: connect(const std::string&): signal does "
      "not collect JavaScript from slots";
    return;
  }

  EventSignalBase::connect(function);
}

template <typename A1, typename A2, typename A3,
	  typename A4, typename A5, typename A6>
void JSignal<A1, A2, A3, A4, A5, A6>::connect(const char *function)
{
  if (!canAutoLearn()) {
    Wt::log("error") << "JSignal: connect(const std::string&): signal does "
      "not collect JavaScript from slots";
    return;
  }

  EventSignalBase::connect(function);
}

template <typename A1, typename A2, typename A3,
	  typename A4, typename A5, typename A6>
void JSignal<A1, A2, A3, A4, A5, A6>::connect(JSlot& slot)
{
  if (!canAutoLearn()) {
    Wt::log("error") << "JSignal: connect(JSlot): signal does not collect "
      "JavaScript from slots";
    return;
  }

  EventSignalBase::connect(slot);
}

template <typename A1, typename A2, typename A3,
	  typename A4, typename A5, typename A6>
boost::signals::connection JSignal<A1, A2, A3, A4, A5, A6>
::connect(WObject *target, void (WObject::*method)())
{
  exposeSignal();
  WStatelessSlot *s = target->isStateless(method);
  if (canAutoLearn() && s)
    return EventSignalBase::connectStateless(method, target, s);
  else
    return connect(boost::bind(method, target));
}

template <typename A1, typename A2, typename A3,
	  typename A4, typename A5, typename A6>
void JSignal<A1, A2, A3, A4, A5, A6>::emit(A1 a1, A2 a2, A3 a3,
					   A4 a4, A5 a5, A6 a6)
{
  if (impl_) {
    pushSender(sender());
#ifndef A6_MUST_BE_NOCLASS
    (*impl_)(a1, a2, a3, a4, a5, a6);
#else
    (*impl_)(a1, a2, a3, a4, a5);
#endif
    popSender();
  }
}

template <typename A1, typename A2, typename A3,
	  typename A4, typename A5, typename A6>
void JSignal<A1, A2, A3, A4, A5, A6>::operator()(A1 a1, A2 a2, A3 a3,
						 A4 a4, A5 a5, A6 a6)
{
  emit(a1, a2, a3, a4, a5, a6);
}

template <typename A1, typename A2, typename A3,
	  typename A4, typename A5, typename A6>
bool JSignal<A1, A2, A3, A4, A5, A6>::isConnected() const
{
  return impl_ ? impl_->num_slots() > 0 : EventSignalBase::isConnected();
}

template <typename T>
struct SignalArgTraits
{
  static T unMarshal(const JavaScriptEvent& jse, int argi) {
    if ((unsigned)argi >= jse.userEventArgs.size()) {
      Wt::log("error") << "JSignal: missing JavaScript argument:" << argi;
      return T();
    }

    try {
      std::string v = jse.userEventArgs[argi];
      WString::checkUTF8Encoding(v);
      return boost::lexical_cast<T>(v);
    } catch (boost::bad_lexical_cast) {
      Wt::log("error") << "JSignal: bad argument format: '"
		<< jse.userEventArgs[argi] << "' for C++ type '"
		<< typeid(T).name() << "'";
      return T();
    }
  }
};

template<>
struct SignalArgTraits<WString>
{
  static WString unMarshal(const JavaScriptEvent& jse, int argi) {
    if ((unsigned)argi >= jse.userEventArgs.size()) {
      Wt::log("error") << "JSignal: missing JavaScript argument:" << argi;
      return WString::Empty;
    }

    std::string v = jse.userEventArgs[argi];
    return WString::fromUTF8(v);
  }
};

template<>
struct SignalArgTraits<NoClass>
{
  static NoClass unMarshal(const JavaScriptEvent& jse, int argi) {
    if ((unsigned)argi < jse.userEventArgs.size()) {
      Wt::log("error") << "JSignal: redundant JavaScript argument: '"
		       << jse.userEventArgs[argi] << "'";
    }

    return NoClass::none;
  }
};

template<>
struct SignalArgTraits<WMouseEvent>
{
  static WMouseEvent unMarshal(const JavaScriptEvent& jse, int) {
    return WMouseEvent(jse);
  }
};

template<>
struct SignalArgTraits<WKeyEvent>
{
  static WKeyEvent unMarshal(const JavaScriptEvent& jse, int) {
    return WKeyEvent(jse);
  }
};

template <typename A1, typename A2, typename A3,
	  typename A4, typename A5, typename A6>
void JSignal<A1, A2, A3, A4, A5, A6>::processDynamic(const JavaScriptEvent& jse)
{
  emit(SignalArgTraits<A1>::unMarshal(jse, 0),
       SignalArgTraits<A2>::unMarshal(jse, 1),
       SignalArgTraits<A3>::unMarshal(jse, 2),
       SignalArgTraits<A4>::unMarshal(jse, 3),
       SignalArgTraits<A5>::unMarshal(jse, 4),
       SignalArgTraits<A6>::unMarshal(jse, 5));
}

#endif // WT_CNOR

}

#endif // WUSER_SIGNAL_H_
