/*
 *  connection.h
 *
 *  This file is part of NEST.
 *
 *  Copyright (C) 2004 The NEST Initiative
 *
 *  NEST is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  NEST is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with NEST.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#ifndef CONNECTION_H
#define CONNECTION_H

#include "nest_time.h"
#include "nest_timeconverter.h"
#include "nest.h"
#include "node.h"
#include "event.h"
#include "dict.h"
#include "dictutils.h"
#include "arraydatum.h"
#include "doubledatum.h"
#include "common_synapse_properties.h"
#include "nest_names.h"
#include "generic_connector_model.h"
#include "spikecounter.h"

namespace nest
{

class ConnectorModel;

/**
 * Base class for representing connections.
 * It provides the mandatory properties receiver port and target,
 * as well as the functions get_status() and set_status()
 * to read and write them. A suitable Connector containing these
 * connections can be obtained from the template GenericConnector.
 */
class Connection
{

 public:

  /**
   * Default Constructor. Sets default values for all parameters.
   * Needed by GenericConnectorModel.
   */
  Connection();

  /**
   * Copy Constructor.
   */
  Connection(const Connection& c);

  /**
   * Default Destructor.
   */
  virtual ~Connection() {}

  /**
   * Get all properties of this connection and put them into a dictionary.
   */
  virtual
  void get_status(DictionaryDatum & d) const;

  /**
   * Create new empty arrays for the properties of this connection in the given
   * dictionary. It is assumed that they are not existing before.
   */
  void initialize_property_arrays(DictionaryDatum & d) const;

  /**
   * Append properties of this connection to the given dictionary. It is
   * assumed that the arrays were created by initialize_property_arrays()
   */
  void append_properties(DictionaryDatum & d) const;

  /**
   * Calibrate the delay of this connection to the desired resolution.
   */
  //virtual
  //void calibrate();

  /**
   * This function calls check_connection() on the sender to check if the receiver
   * accepts the event type and receptor type requested by the sender.
   * \param s The source node
   * \param r The target node
   * \param receptor The ID of the requested receptor type
   * \param the last spike produced by the presynaptic neuron (for STDP and maturing connections) 
   */
  virtual void check_connection(Node & s, Node & r, rport receptor, double_t t_lastspike);

  /**
   * This function checks if the event type is supported by the concrete
   * event type. In the base class it just throws UnsupportedEvent. In
   * the derived Connection classes it can be implemented as an empty
   * function to indicate that the event type is supported.
   */
  virtual void check_event(SpikeEvent&);

  virtual void check_event(RateEvent&);
  virtual void check_event(DataLoggingRequest&);
  virtual void check_event(CurrentEvent&);
  virtual void check_event(ConductanceEvent&);
  virtual void check_event(DoubleDataEvent&);

  // We must handle DSSpikeEvent and DSCurrentEvent explicitly instead
  // of subsuming them under SpikeEvent and CurrentEvent via inheritance,
  // as they must only be transmitted via static_synapse.
  virtual void check_event(DSSpikeEvent&);
  virtual void check_event(DSCurrentEvent&);

  /**
   * Return the rport of the connection
   */
  rport get_rport() const;

  /**
   * Return the target of the connection
   */
  Node *get_target() const;

  /**
   * triggers an update of a synaptic weight
   * this function is needed for neuromodulated synaptic plasticity 
   */
  void trigger_update_weight(const std::vector<spikecounter>&, double_t, const CommonSynapseProperties&);

 protected:

  Node *target_;       //!< Target node
  rport rport_;         //!< Receiver port at the target node
};

inline
void Connection::check_connection(Node & s, Node & r, rport receptor_type, double_t)
{
  target_ = &r;
  rport_ = s.check_connection(*this, receptor_type);
}

inline
rport Connection::get_rport() const
{
  return rport_;
}

inline
Node *Connection::get_target() const
{
  return target_;
}

inline
void Connection::check_event(SpikeEvent&)
{
  throw UnsupportedEvent();
}

inline
void Connection::check_event(DSSpikeEvent&)
{
  throw UnsupportedEvent();
}

inline
void Connection::check_event(RateEvent&)
{
  throw UnsupportedEvent();
}

inline
void Connection::check_event(DataLoggingRequest&)
{
  throw UnsupportedEvent();
}

inline
void Connection::check_event(CurrentEvent&)
{
  throw UnsupportedEvent();
}

inline
void Connection::check_event(DSCurrentEvent&)
{
  throw UnsupportedEvent();
}

inline
void Connection::check_event(ConductanceEvent&)
{
  throw UnsupportedEvent();
}

inline
void Connection::check_event(DoubleDataEvent&)
{
  throw UnsupportedEvent();
}

inline
void Connection::trigger_update_weight(const std::vector<spikecounter>&, double_t, const CommonSynapseProperties&)
{
  throw IllegalConnection("Connection::trigger_update_weight: "
			  "Connection does not support time-driven update.");
}

template<typename PropT>
bool set_property(const DictionaryDatum & d, Name propname, index p, PropT &prop)
{
  if (d->known(propname))
  {
    ArrayDatum* arrd = 0;
    arrd = dynamic_cast<ArrayDatum*>((*d)[propname].datum());
    if (! arrd)
    {
      ArrayDatum const arrd;
      throw TypeMismatch(arrd.gettypename().toString(), 
                         (*d)[propname].datum()->gettypename().toString());
    }
    
    prop = (*arrd)[p];
    return true;
  }

  return false;
}

} // namespace nest

#endif // CONNECTION_H