// 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 have to respond to the same signal of
 * many objects or widgets, but need to identify the sender through some
 * property.
 *
 * Consider the following example, where you can check for the sender, by
 * adding it as a mapped argument:
 *
 * \code
void Test::createWidgets()
{
  WSignalMapper<WText*> *MyMap = new WSignalMapper<WText*>(this);

  MyMap->mapped.connect(SLOT(this, Test::onClick));
  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 \link
 * WSignalMapper::mapped mapped\endlink signal. In that case, you must
 * connect the original signal to the map1() slot, or use mapConnect1().
 *
 * \ingroup signalslot
 */
template <typename T, typename A1 = NoClass>
class WSignalMapper : public WObject
{
public:
  /*! \brief Create a new %WSignalMapper.
   */
  WSignalMapper(WObject *parent = 0);

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

  /*! \brief Map a signal without arguments.
   *
   * Connect the given signal with the slot, and associate the data
   * when it is triggered.
   */
  void mapConnect(SignalBase& signal, const T& data);

  /*! \brief Map 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
   * \link WSignalMapper::mapped mapped\endlink signal.
   */
  template<typename S>
    void 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;

public slots:
  /*! \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 \link WSignalMapper::mapped
   * mapped\endlink 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 \link WSignalMapper::mapped
   * mapped\endlink signal. The additional argument <i>a</i> is passed
   * as the second argument to the \link WSignalMapper::mapped
   * mapped\endlink signal.
   */
  void map1(A1 a);

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

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

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

template <typename T, typename A1>
void WSignalMapper<T, A1>::mapConnect(SignalBase& signal, const T& data)
{
  mappings_[signal.sender()] = data;
  signal.connectBase
    (this, static_cast<void (WObject::*)()>(&WSignalMapper<T, A1>::map));
}

template <typename T, typename A1>
template <typename S>
void WSignalMapper<T, A1>::mapConnect1(S& signal, const T& data)
{
  mappings_[signal.sender()] = data;
  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);
  }
}

}

#endif // WSIGNALMAPPER_H_
