#ifndef UNIVERSAL_DATA_LOGGER_H
#define UNIVERSAL_DATA_LOGGER_H

/*
 *  universal_data_logger.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/>.
 *
 */

#include "nest.h"
#include "event.h"
#include "recordables_map.h"
#include "nest_time.h"

#include <vector>

namespace nest {

  class Network;
  
  /**
   * Universal data-logging plug-in for neuron models.
   *
   * This class provides logging of universal data such as 
   * membrane potentials or conductances in a way that is
   * compatible with the DataLoggingRequest/DataLoggingReply
   * read-out mechanism.
   *
   * The logger must be informed about any incoming DataLoggingRequest
   * connections by calling connect_logging_device(). All incoming
   * DataLoggingRequests should then be forwarded to the logger using
   * handle().
   *
   * @note A reference to the host node is stored in the logger, for
   *       access to the state and sending events. This requires a constructor
   *       and a copy constructor for the HostNode::Buffers_, creating new
   *       logger members initialized with the new node, e.g.
   * @code
   * struct Buffers_ {
   *   Buffers_(iaf_cond_alpha&);
   *   Buffers_(const Buffers_&, iaf_cond_alpha&);
   *
   *   UniversalDataLogger<iaf_cond_alpha> logger_;
   *
   * ...
   *
   * nest::iaf_cond_alpha::Buffers_::Buffers_(iaf_cond_alpha& n)
   * : logger_(n), ... {}
   *
   * nest::iaf_cond_alpha::Buffers_::Buffers_(const Buffers_&, iaf_cond_alpha& n)
   * : logger_(n), ... {}
   *
   * nest::iaf_cond_alpha::iaf_cond_alpha()
   * : ..., B_(*this) {}
   *
   * nest::iaf_cond_alpha::iaf_cond_alpha(const iaf_cond_alpha& n)
   * : ..., B_(n.B_, *this) {}
   * @code
   *
   * @todo Could HostNode be passed as const& to handle() and record_data()?
   *
   * @note To avoid inclusion problems and code-bloat, the class 
   *       interface is defined in this file, while most of the
   *       implementation is in the companion universal_data_logger_impl.h.
   *       As a consequence, calls to UniversalDataLogger members should
   *       only come from cpp files---do not inline them.
   *
   * @addtogroup Devices
   */
   template <typename HostNode>
   class UniversalDataLogger {

   public:
     
     /**
      * Create logger.
      */
     UniversalDataLogger(HostNode&);
        
     /**
      * Notify data logger that the node is recorded from.
      *
      * This method must be called when a universal recording device
      * is connected to the node. It informs the data logger that
      * data actually needs to be logged. Otherwise, data is simply
      * discarded.
      *
      * @param provides information about requested data and interval
      * @param map of access functions
      * @return rport for future logging requests
      */
     port connect_logging_device(const DataLoggingRequest&,
				 const RecordablesMap<HostNode>&);

     /**
      * Answer DataLoggingRequest.
      *
      * The data logger creates an event of type DataLoggingRequest
      * with a reference to the data and returns it to the requester.
      *
      * @note The request should not contain any data; it is ignored.
      *
      * @param DataLoggingRequest Request to be handled.
      */
     void handle(const DataLoggingRequest&);
     
     /** 
      * Record data using predefined access functions.
      * This function should be called once per time step at the end of the
      * time step to record data from the node to the logger.
      *
      * @param long_t    Time in steps at the BEGINNING of the update step, i.e.,
      *                  origin+lag, but the data is logged with time stamp
      *                  origin+lag+1, since this is the proper time of the data.
      */
     void record_data(long_t);

     //! Erase all existing data
     void reset();

     /**
      * Initialize logger, i.e., set up data buffers.
      * Has no effect if buffer is initialized already.
      */
     void init();

   private:

     /**
      * Single data logger, serving one Multimeter.
      * For each Multimeter connected to a node, one DataLogger_ instance is created.
      * The UniversalDataLogger forwards all requests to the correct DataLogger_ based
      * on the rport of the request.
      */
     class DataLogger_ {
     public:
       DataLogger_(const DataLoggingRequest&,
                   const RecordablesMap<HostNode>&);
       index get_mm_gid() const { return multimeter_; }
       void handle(HostNode&, const DataLoggingRequest&);
       void record_data(const HostNode&, long_t);
       void reset();
       void init();

     private:
       index  multimeter_;         //!< GID of multimeter for which the logger works
       size_t num_vars_;           //!< number of variables recorded

       Time   recording_interval_; //!< interval between two recordings
       long_t rec_int_steps_;      //!< interval in steps
       long_t next_rec_step_;      //!< next time step at which to record

       /** Vector of pointers to member functions for data access. */
       std::vector<typename RecordablesMap<HostNode>::DataAccessFct> node_access_;

       /**
        * Buffer for data.
        * The first dimension has size two, to provide for alternate
        * writing/reading using a toggle. The second dimension has
        * one entry per recording time in each time slice. Each entry
        * consists of a time stamp and one data point per recordable.
        */
       std::vector<DataLoggingReply::Container> data_;

       //! Next buffer entry to write to, with read/write toggle
       std::vector<size_t> next_rec_;
     };

     HostNode& host_;            //!< node to which logger belongs

     /**
      * Data loggers, one per connected multimeter.
      * Indices are rport-1.
      */
     std::vector<DataLogger_> data_loggers_;
     typedef typename std::vector<DataLogger_>::iterator DLiter_;

     //! Should not be copied. 
     UniversalDataLogger(const UniversalDataLogger&);

     //! Should not be assigned
     UniversalDataLogger const& operator=(const UniversalDataLogger&);
   };

   // must be defined in this file, since it is required by check_connection(),
   // which typically is in h-files.   
   template <typename HostNode>
     port nest::UniversalDataLogger<HostNode>::connect_logging_device(const DataLoggingRequest& req,
								      const RecordablesMap<HostNode>& rmap)
   {
     // rports are assigned consecutively, the caller may not request specific rports.
     if ( req.get_rport() != 0 )
       throw IllegalConnection("UniversalDataLogger::connect_logging_device(): "
			       "Connections from multimeter to node must request rport 0.");

     // ensure that we have not connected this multimeter before
     const index  mm_gid = req.get_sender().get_gid();
     const size_t n_loggers = data_loggers_.size();
     size_t j = 0;
     while ( j < n_loggers && data_loggers_[j].get_mm_gid() != mm_gid )
       ++j;
     if ( j < n_loggers )
       throw IllegalConnection("UniversalDataLogger::connect_logging_device(): "
                               "Each multimeter can only be connected once to a given node.");

     // we now know that we have no DataLogger_ for the given multimeter, so we create one and push it
     data_loggers_.push_back(DataLogger_(req, rmap));

     // rport is index plus one, i.e., size
     return data_loggers_.size();
   }

   template <typename HostNode>
   nest::UniversalDataLogger<HostNode>::DataLogger_::DataLogger_(const DataLoggingRequest& req,
                                                                 const RecordablesMap<HostNode>& rmap):
      multimeter_(req.get_sender().get_gid()),
      num_vars_(0),
      recording_interval_(Time::neg_inf()),
      rec_int_steps_(0),
      next_rec_step_(-1),  // flag as uninitialized
      node_access_(),
      data_(),
      next_rec_(2,0)
   {
     const std::vector<Name>& recvars = req.record_from();
     for ( size_t j = 0 ; j < recvars.size() ; ++j )
       {
	 typename RecordablesMap<HostNode>::const_iterator rec
	   = rmap.find(recvars[j].toString());  // .toString() required as work-around for #339, 
	                                        // remove when #348 is solved.

	 if ( rec == rmap.end() )
	 {
	   // delete all access information again: the connect either succeeds
	   // for all entries in recvars, or it fails, leaving the logger untouched
	   node_access_.clear();
	   throw IllegalConnection("UniversalDataLogger::connect_logging_device(): "
				   "Unknown recordable " + recvars[j].toString());
	 }

	 node_access_.push_back(rec->second);
       }

     num_vars_ = node_access_.size();

     if ( num_vars_ > 0 && req.get_recording_interval() < Time::step(1) )
       throw IllegalConnection("UniversalDataLogger::connect_logging_device(): "
			       "recording interval must be >= resolution.");

     recording_interval_ = req.get_recording_interval();
   }
   
}

#endif // UNIVERSAL_DATA_LOGGER_H