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

#include <WSignal>
#include <WEvent>
#include <WJavaScriptSlot>
#include <boost/lexical_cast.hpp>

namespace Wt {

/*! \class JSignal WJavaScript 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 WtEmitSignal() function.
 *
 * The signal is identified by a unique name, within the scope of a
 * WObject, which are specified in its constructor.
 * 
 * The signal supports up to 6 arguments. Values for these arguments
 * may be specified in the JavaScript WtEmitSignal().
 *
 * For example, consider the following signal, and constructor:
 *
 * <pre>
 * class MyWidget : public WCompositeWidget
 * {
 * public:
 *   // ...
 *   JSignal<std::string, int> doSome;
 *   // ...
 * };
 *
 * MyWidget::MyWidget()
 *   : doSome(this, "dosome")
 * { 
 *   //...
 * }
 * </pre>
 *
 * The following JavaScript code will then emit the signal for a DOM
 * element, which corresponds to a MyWidget widget:
 *
 * <pre>
 * WtSignalEmit(element, 'dosome', 'foo', 42);
 * </pre>
 *
 * The conversion between the JavaScript argument (ax) and the C++ type Ax
 * uses boost::lexical_cast<Ax>(ax).
 *
 * \sa WWidget::jsRef(), WObject::id()
 */
template <typename A1 = None, typename A2 = None,
	  typename A3 = None, typename A4 = None,
	  typename A5 = None, typename A6 = None>
class JSignal : public EventSignalBase
{
public:
  /*! \brief Construct a signal for the given object, and name.
   *
   * The name must be unique for all user signals specified for a single
   * class.
   */
  JSignal(WObject *object, const std::string name);
  ~JSignal();

  /*! \brief Get the name of this signal
   */
  std::string name() const { return name_; }

  virtual const std::string encodeCmd() const;

  /*! \brief Is this signal connected to at least one slot ?
   */
  virtual bool isConnected() const;

  /*! \brief Connect a slot that takes no arguments.
   *
   * This is always possible (even when the signal specifies a number
   * of arguments).
   *
   * The slot is specified as a method of class V, which is equal to
   * class V, or a base class of class V. Thus, the following statement
   * must return a non-null pointer:
   *
   * \code
   * V *v = dynamic_cast<V *>(target);
   * \endcode
   *
   * In practice, to facilitate automatic disconnects on deletion of the
   * target, class T must be also be a descendant of WObject, but this
   * is not enforced by the interface.
   */
  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));

  /*! \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 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 = None::none, A2 a2 = None::none,
	    A3 a3 = None::none, A4 a4 = None::none,
	    A5 a5 = None::none, A6 a6 = None::none);

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

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

private:
  std::string name_;
  void processDynamic(const JavaScriptEvent& e);

  boost::signal6<void, A1, A2, A3, A4, A5, A6> *impl_;
};


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

template <typename A1, typename A2, typename A3,
	  typename A4, typename A5, typename A6>
JSignal<A1, A2, A3, A4, A5, A6>::JSignal(WObject *object, std::string name)
  : EventSignalBase(object),
    name_(name),
    impl_(0)
{ }

template <typename A1, typename A2, typename A3,
	  typename A4, typename A5, typename A6>
JSignal<A1, A2, A3, A4, A5, A6>::~JSignal()
{
  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
{
  return sender()->id() + "." + name_;
}

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))
{
  exposeSignal();
  assert(dynamic_cast<V *>(target));
  if (!impl_)
    impl_ = new boost::signal6<void, A1, A2, A3, A4, A5, A6>;
  return impl_->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))
{
  exposeSignal();
  assert(dynamic_cast<V *>(target));
  if (!impl_)
    impl_ = new boost::signal6<void, A1, A2, A3, A4, A5, A6>;
  return impl_->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));
  if (!impl_)
    impl_ = new boost::signal6<void, A1, A2, A3, A4, A5, A6>;
  return impl_->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));
  if (!impl_)
    impl_ = new boost::signal6<void, A1, A2, A3, A4, A5, A6>;
  return impl_->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))
{
  exposeSignal();
  assert(dynamic_cast<V *>(target));
  if (!impl_)
    impl_ = new boost::signal6<void, A1, A2, A3, A4, A5, A6>;
  return impl_->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))
{
  exposeSignal();
  assert(dynamic_cast<V *>(target));
  if (!impl_)
    impl_ = new boost::signal6<void, A1, A2, A3, A4, A5, A6>;
  return impl_->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();
  assert(dynamic_cast<V *>(target));
  if (!impl_)
    impl_ = new boost::signal6<void, A1, A2, A3, A4, A5, A6>;
  return impl_->connect(boost::bind(method, target));
}

template <typename A1, typename A2, typename A3,
	  typename A4, typename A5, typename A6>
boost::signals::connection JSignal<A1, A2, A3, A4, A5, A6>
::connectBase(WObject *target, void (WObject::*method)())
{
  return connect(target, method);
}

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());
    (*impl_)(a1, a2, a3, a4, a5, a6);
    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 : false;
}

extern void WT_API throwWtException(const std::string msg);

template <typename T>
struct WT_API SignalArgTraits
{
  static T unMarshal(const JavaScriptEvent& jse, int argi) {
    if ((unsigned)argi >= jse.userEventArgs.size())
      throwWtException("Missing JavaScript argument: "
		       + boost::lexical_cast<std::string>(argi));

    try {
      return boost::lexical_cast<T>(jse.userEventArgs[argi]);
    } catch (boost::bad_lexical_cast) {
      throwWtException("Bad argument format: '"
		       + jse.userEventArgs[argi] + "'");
      return T();
    }
  }
};

template<>
struct WT_API SignalArgTraits<None>
{
  static None unMarshal(const JavaScriptEvent& jse, int argi) {
    if ((unsigned)argi < jse.userEventArgs.size())
      throwWtException("Redundant JavaScript argument: '"
		       + jse.userEventArgs[argi] + "'");
    return None::none;
  }
};

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 // WUSER_SIGNAL_H_
