// 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 WSIGNALMAPPER_H_
#define WSIGNALMAPPER_H_

#include <map>
#include <Wt/WSignal>
#include <Wt/WJavaScript>

namespace Wt {

/*! \class WSignalMapper Wt/WSignalMapper Wt/WSignalMapper
 *  \brief A utility class to connect multiple senders to a single slot.
 *
 * This class is useful if you would like to respond to signals of
 * many objects or widgets within a single slot, but need to identify
 * the particular sender through some property.
 *
 * Usage example:
 * \code
 * void Test::createWidgets()
 * {
 *   Wt::WSignalMapper<WText *> *MyMap = new Wt::WSignalMapper<Wt::WText *>(this);
 *   // connect mapped() to our target slot
 *   MyMap->mapped().connect(this, &Test::onClick);
 *
 *   // connect and map every source signal to the mapper
 *   MyMap->mapConnect(text1->clicked(), text1);
 *   MyMap->mapConnect(text2->clicked(), text2);
 *   MyMap->mapConnect(text3->clicked(), text3);
 * }
 *
 * void Test::onClick(WText* source)
 * {
 *   // source is where it is coming from
 *   // ...
 * }
 * \endcode
 *
 * The type <i>T</i> may be any type that has proper copy semantics
 * and a default constructor. The mapper may pass one extra argument
 * (type <i>A1</i>) from the original signal to the mapped() signal.
 * In that case, you must connect the original signal to the map1()
 * slot, or use mapConnect1().
 *
 * The mapper uses signal.sender() to attribute ownership of a signal to
 * a sender.
 *
 * You may want to consider to boost::bind() (or std::bind()) instead,
 * which is simpler and achieves the same in a more direct way:
 * \code
 *  text1->clicked().connect(boost::bind(&Test::onClick, this, text1));
 *  text2->clicked().connect(boost::bind(&Test::onClick, this, text2));
 *  text3->clicked().connect(boost::bind(&Test::onClick, this, text3));
 * \endcode
 *
 * \ingroup signalslot
 */
template <typename T, typename A1 = NoClass>
class WSignalMapper : public WObject
{
public:
  /*! \brief Creates a new %WSignalMapper.
   */
  WSignalMapper(WObject *parent = 0);

  /*! \brief Associates data with a sender.
   *
   * Associate data with a sender, which wel emitted by the mapped()
   * signal, when the corresponding sender signal triggers map() or
   * map1().
   */
  void setMapping(WObject *sender, const T& data);

#ifndef WT_CNOR
  /*! \brief Maps a signal without arguments.
   *
   * Connect the given signal with the slot, and associate the data
   * when it is triggered.
   *
   * \code
   * Wt::WSignalMapper<T> *mapper = ...
   *
   * mapper->mapConnect(widget->clicked(), data);
   * \endcode
   * is equivalent to:
   * \code
   * Wt::WSignalMapper<T> *mapper = ...
   *
   * widget->clicked().connect(mapper, &Wt::WSignalMapper<T>::map);
   * mapper->setMapping(widget, data);
   * \endcode
   */
  boost::signals::connection mapConnect(SignalBase& signal, const T& data);
#else
#ifndef WT_TARGET_JAVA
  boost::signals::connection mapConnect(SignalBase& signal, const T& data);
#endif
  template<typename S>
   boost::signals::connection mapConnect(EventSignal<S>& signal, const T& data);
  
  boost::signals::connection mapConnect(EventSignal<>& signal, const T& data);
#endif

  /*! \brief Maps a signal with one argument.
   *
   * Connect the given signal with the slot, and associate the data
   * when it is triggered. The signal argument will be passed to the
   * mapped() signal.
   *
   * \code
   * Wt::WSignalMapper<T, Wt::WMouseEvent> *mapper = ...
   *
   * mapper->mapConnect(widget->clicked(), data);
   * \endcode
   * is equivalent to:
   * \code
   * Wt::WSignalMapper<T, Wt::WMouseEvent> *mapper = ...
   *
   * widget->clicked().connect(mapper, &Wt::WSignalMapper<T, Wt::WMouseEvent>::map1);
   * mapper->setMapping(widget, data);
   * \endcode
   */
  template<typename S>
    boost::signals::connection mapConnect1(S& signal, const T& data);

  /*! \brief %Signal emitted in response to a signal sent to map() or map1().
   *
   * The first argument propagated is the data that is associated with
   * the specific sender, set in setMapping() or mapConnect(). The
   * second argument is an argument passed from the originating signal.
   */
  Signal<T, A1>& mapped() { return mapped_; }

  /*! \brief Slot to which to connect the source signal.
   *
   * When a signal triggers the slot, the sender is identified and
   * used to find corresponding data set with setMapping(), which is
   * then use to propagate further in the mapped() signal.
   */
  void map();

  /*! \brief Slot to which to connect the source signal, passing the argument
   *         to the receiver.
   *
   * When a signal triggers the slot, the sender is identified and
   * used to find corresponding data set with setMapping(), which is
   * then use to propagate further in the mapped() signal. The
   * additional argument \p a is passed as the second argument to
   * the mapped() signal.
   */
  void map1(A1 a);

  /*! \brief Removes the mapping of an object
   *
   * This method does not disconnect any signals; that is the responsability of
   * the user of WSignalMapper. If no mapping of for an object exits,
   * the mapper will ignore the signal, and not emit the mapped signal.
   */
   void removeMapping(WObject *sender);

private:
  Signal<T, A1> mapped_;

  typedef std::map<WObject *, T> DataMap;
  DataMap mappings_;
};

#ifndef WT_TARGET_JAVA

#ifdef _MSC_VER
#pragma warning( push )
#pragma warning( disable : 4355 )
#endif // _MSC_VER

template <typename T, typename A1>
WSignalMapper<T, A1>::WSignalMapper(WObject *parent)
  : WObject(parent),
    mapped_(this)
{ }

#ifdef _MSC_VER
#pragma warning( pop )
#endif // _MSC_VER

template <typename T, typename A1>
void WSignalMapper<T, A1>::setMapping(WObject *sender, const T& data)
{
  mappings_[sender] = data;
}

template <typename T, typename A1>
boost::signals::connection WSignalMapper<T, A1>::mapConnect(SignalBase& signal, const T& data)
{
  if (signal.sender() == 0)
    throw WException("WSignalMapper::mapConnect(): signal does not "
		     "have sender");
  mappings_[signal.sender()] = data;
  return signal.connect
    (this, static_cast<void (WObject::*)()>(&WSignalMapper<T, A1>::map));
}

template <typename T, typename A1>
template <typename S>
boost::signals::connection WSignalMapper<T, A1>::mapConnect1(S& signal, const T& data)
{
  if (signal.sender() == 0)
    throw WException("WSignalMapper::mapConnect1(): signal does not "
		     "have sender");
  mappings_[signal.sender()] = data;
  return signal.connect(this, &WSignalMapper<T, A1>::map1);
}

template <typename T, typename A1>
void WSignalMapper<T, A1>::map()
{
  WObject *theSender = sender();

  typename DataMap::const_iterator i = mappings_.find(theSender);
  if (i != mappings_.end()) {
    mapped_.emit(i->second, A1());
  }
}

template <typename T, typename A1>
void WSignalMapper<T, A1>::map1(A1 a1)
{
  WObject *theSender = sender();

  typename DataMap::const_iterator i = mappings_.find(theSender);
  if (i != mappings_.end()) {
    mapped_.emit(i->second, a1);
  }
}

template <typename T, typename A1>
void WSignalMapper<T, A1>::removeMapping(WObject *sender)
{
  typename DataMap::iterator i = mappings_.find(sender);
  if (i != mappings_.end()) {
    mappings_.erase(i);
  }

}

#endif

}

#endif // WSIGNALMAPPER_H_
