/*
 *  music_event_out_proxy.cpp
 *
 *  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 "config.h"

#ifdef HAVE_MUSIC

#include "music_event_out_proxy.h"
#include "network.h"
#include "dict.h"
#include "integerdatum.h"
#include "doubledatum.h"
#include "dictutils.h"
#include "arraydatum.h"

#include <numeric>

/* ---------------------------------------------------------------- 
 * Default constructors defining default parameters and state
 * ---------------------------------------------------------------- */

nest::music_event_out_proxy::Parameters_::Parameters_()
   : port_name_("event_out")
{}

nest::music_event_out_proxy::Parameters_::Parameters_(const Parameters_& op)
   : port_name_(op.port_name_)
{}

nest::music_event_out_proxy::State_::State_()
   : published_(false),
     port_width_(-1)
{}

/* ---------------------------------------------------------------- 
 * Parameter extraction and manipulation functions
 * ---------------------------------------------------------------- */

void nest::music_event_out_proxy::Parameters_::get(DictionaryDatum &d) const
{
  (*d)[names::port_name] = port_name_; 
}
 
void nest::music_event_out_proxy::Parameters_::set(const DictionaryDatum& d, State_& s)
{
// TODO: This is not possible, as P_ does not know about get_name()
//  if(d->known(names::port_name) && s.published_)
//    throw MUSICPortAlreadyPublished(get_name(), P_.port_name_);

  if (!s.published_)
    updateValue<string>(d, names::port_name, port_name_);
}

void nest::music_event_out_proxy::State_::get(DictionaryDatum &d) const
{
  (*d)[names::published] = published_; 
  (*d)[names::port_width] = port_width_; 
} 

void nest::music_event_out_proxy::State_::set(const DictionaryDatum&, const Parameters_&)
{}


/* ---------------------------------------------------------------- 
 * Default and copy constructor for node
 * ---------------------------------------------------------------- */

nest::music_event_out_proxy::music_event_out_proxy()
        : Node(),
          P_(),
          S_()
{}

nest::music_event_out_proxy::music_event_out_proxy(const music_event_out_proxy &n)
        : Node(n),
          P_(n.P_),
          S_(n.S_)
{}

nest::music_event_out_proxy::~music_event_out_proxy()
{
  if (S_.published_)
  {
    delete V_.MP_;
    delete V_.music_perm_ind_;
  } 
}

void nest::music_event_out_proxy::init_state_(const Node& /* np */)
{
  // const music_event_out_proxy& sd = dynamic_cast<const music_event_out_proxy&>(np);
}

void nest::music_event_out_proxy::init_buffers_()
{}

void nest::music_event_out_proxy::calibrate()
{
  // only publish the output port once,
  if (!S_.published_)
  {
    MUSIC::Setup* s = nest::Communicator::get_music_setup();
    if (s == 0)
      throw MUSICSimulationHasRun(get_name());

    V_.MP_ = s->publishEventOutput(P_.port_name_);
    
    if (!V_.MP_->isConnected())
      throw MUSICPortUnconnected(get_name(), P_.port_name_);
  
    if (!V_.MP_->hasWidth())
      throw MUSICPortHasNoWidth(get_name(), P_.port_name_);
    
    S_.port_width_ = V_.MP_->width();

    // check, if there are connections to receiver ports, which are
    // beyond the width of the port
    std::vector<MUSIC::GlobalIndex>::const_iterator it;
    for (it = V_.index_map_.begin(); it != V_.index_map_.end(); ++it)
      if (*it > S_.port_width_)
	throw UnknownReceptorType(*it, get_name());

    // The permutation index map, contains global_index[local_index]
    V_.music_perm_ind_ = new MUSIC::PermutationIndex(&V_.index_map_.front(), V_.index_map_.size());

    // we identify channels by global indices within NEST
    V_.MP_->map(V_.music_perm_ind_, MUSIC::Index::GLOBAL);
    
    S_.published_ = true;

    std::string msg = String::compose("Mapping MUSIC output port '%1' with width=%2.",
                                      P_.port_name_, S_.port_width_);
    net_->message(SLIInterpreter::M_INFO, "MusicEventHandler::publish_port()", msg.c_str());
  }
}

void nest::music_event_out_proxy::get_status(DictionaryDatum &d) const
{
  P_.get(d);
  S_.get(d);

  (*d)[names::connection_count] = V_.index_map_.size();

  // make a copy, since MUSIC uses int instead of long int
  std::vector<long_t>* pInd_map_long = new std::vector<long_t>(V_.index_map_.size());
  std::copy<std::vector<MUSIC::GlobalIndex>::const_iterator,
            std::vector<long_t>::iterator>(V_.index_map_.begin(),
                                           V_.index_map_.end(),
                                           pInd_map_long->begin());

  (*d)[names::index_map] = IntVectorDatum(pInd_map_long);
}

void nest::music_event_out_proxy::set_status(const DictionaryDatum & d)
{
  Parameters_ ptmp = P_;  // temporary copy in case of errors
  ptmp.set(d, S_);        // throws if BadProperty

  State_ stmp = S_;
  stmp.set(d, P_);        // throws if BadProperty

  // if we get here, temporaries contain consistent set of properties
  P_ = ptmp;
  S_ = stmp;
}

void nest::music_event_out_proxy::handle(SpikeEvent & e)
{
  assert(e.get_multiplicity() > 0);

  // propagate the spikes to MUSIC port
  double_t time = e.get_stamp().get_ms()*1e-3; // event time in seconds
  long_t receiver_port = e.get_rport();

  for (int_t i = 0; i < e.get_multiplicity(); ++i)
    V_.MP_->insertEvent(time, MUSIC::GlobalIndex(receiver_port));
}

#endif