/*
 *  dictutils.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 DICTUTILS_H
#define DICTUTILS_H

#include "dictdatum.h"
#include "namedatum.h"
#include "tokenutils.h"
#include "arraydatum.h"
#include <string>
#include <algorithm>
#include <functional>

/**
 * @defgroup DictUtils How to access the value contained in a Token contained in a dictionary.
 * @ingroup TokenHandling
 *
 * Class Dictionary defines the standard user interface for accessing tokens
 * from dictionaries (see there). However, this user interface returns
 * tokens, from which the actual value would still need to be
 * extracted. The utilitiy functions described in this group shortcut
 * this step and provide direct access to the underlying fundamental
 * values associated to a dictionary entry.
 */

/** Get the value of an existing dictionary entry.
 * @ingroup DictUtils
 * @throws UnknownName An entry of the given name is not known in the dictionary.
 */
template<typename FT>
FT getValue(const DictionaryDatum &d, Name const n)
{
  // We must take a reference, so that access information can be stored in the
  // token.
  const Token& t = d->lookup2(n);
  
  /* if (!t.empty()) */
  /*   throw UndefinedName(n.toString()); */
 
  return getValue<FT>(t);
}  
   

/** Define a new dictionary entry from a fundamental type.
 * @ingroup DictUtils
 * @throws TypeMismatch Fundamental type and requested SLI type are incompatible.
 */
template<typename FT, class D>
void def2(DictionaryDatum &d, Name const n, FT const &value)
{
  Token t = newToken2<FT,D>(value);
  d->insert_move(n, t);
}

/** Define a new dictionary entry from a fundamental type.
 * @ingroup DictUtils
 * @throws TypeMismatch Creating a Token from the fundamental type failed, 
 *         probably due to a missing template specialization.
 */
template<typename FT>
void def(DictionaryDatum &d, Name const n, FT const &value)
{
  Token t(value); // we hope that we have a constructor for this.
  d->insert_move(n, t);
}

/** Update a variable from a dictionary entry if it exists, skip call if it doesn't.
 * @ingroup DictUtils
 * @throws see getValue(DictionaryDatum, Name)
 */
template<typename FT, typename VT>
bool updateValue(DictionaryDatum const &d, Name const n, VT &value)
{
  // We will test for the name, and do nothing if it does not exist,
  // instead of simply trying to getValue() it and catching a possible
  // exception. The latter works, however, but non-existing names are
  // the rule with updateValue(), not the exception, hence using the
  // exception mechanism would be inappropriate. (Markus pointed this
  // out, 05.02.2001, Ruediger.)

  // We must take a reference, so that access information can be stored in the
  // token.
  const Token& t = d->lookup(n);

  if (t.empty())
    return false;
  
  value = getValue<FT>(t);
  return true;
}

/** Call a member function of an object, passing the value of an dictionary entry if it exists, 
 *  skip call if it doesn't.
 * @ingroup DictUtils
 * @throws see getValue(DictionaryDatum, Name)
 */
template<typename FT, typename VT, class C>
void updateValue2(DictionaryDatum const &d, Name const n, C &obj, void (C::* setfunc)(VT))
{
  if (d->known(n)) // Does name exist in the dictionary?
  {
    // yes, call the function for update.
    (obj.*setfunc)(getValue<FT>(d, n));
  }
}


/** Create a property of type ArrayDatum in the dictionary, if it does not already exist.
 * @ingroup DictUtils
 */
void initialize_property_array(DictionaryDatum &d, Name propname);


/** Create a property of type DoubleVectorDatum in the dictionary, if it does not already exist.
 * @ingroup DictUtils
 */
void initialize_property_doublevector(DictionaryDatum &d, Name propname);


/** Create a property of type IntVectorDatum in the dictionary, if it does not already exist.
 * @ingroup DictUtils
 */
void initialize_property_intvector(DictionaryDatum &d, Name propname);


/** Append a value to a property ArrayDatum in the dictionary.
 * This is the version for scalar values
 * @ingroup DictUtils
 */
template<typename PropT>
inline
void append_property(DictionaryDatum &d, Name propname, const PropT &prop)
{
  Token t = d->lookup(propname);
  assert (!t.empty());

  ArrayDatum* arrd = dynamic_cast<ArrayDatum*>(t.datum());
  assert(arrd != 0);

  Token prop_token(prop);
  arrd->push_back_dont_clone(prop_token);
}

/** Append a value to a property DoubleVectorDatum in the dictionary.
 * This is a specialization for appending vector<double>s to vector<double>s
 * @ingroup DictUtils
 */
template<>
inline
void append_property<std::vector<double> >(DictionaryDatum &d, Name propname, const std::vector<double> &prop)
{
  Token t = d->lookup(propname);
  assert (!t.empty());

  DoubleVectorDatum* arrd = dynamic_cast<DoubleVectorDatum*>(t.datum());
  assert(arrd != 0);

  (*arrd)->insert((*arrd)->end(), prop.begin(), prop.end());
}


/** Append a value to a property IntVectorDatum in the dictionary.
 * This is a specialization for appending vector<long>s to vector<long>s
 * @ingroup DictUtils
 */
template<>
inline
void append_property<std::vector<long> >(DictionaryDatum &d, Name propname, const std::vector<long> &prop)
{
  Token t = d->lookup(propname);
  assert (!t.empty());

  IntVectorDatum* arrd = dynamic_cast<IntVectorDatum*>(t.datum());
  assert(arrd != 0);

  (*arrd)->insert((*arrd)->end(), prop.begin(), prop.end());
}


/** Provide a value to a property DoubleVectorDatum in the dictionary.
 * In contrast to append_property, this function adds the value only once
 * to the property. On all subsequent events, it ensures that the value
 * passed in is identical to the value present. This is needed by recording_decive.
 * @ingroup DictUtils
 */
void provide_property(DictionaryDatum&, Name, const std::vector<double>&);

/** Provide a value to a property IntVectorDatum in the dictionary.
 * In contrast to append_property, this function adds the value only once
 * to the property. On all subsequent events, it ensures that the value
 * passed in is identical to the value present. This is needed by recording_decive.
 * @ingroup DictUtils
 */
void provide_property(DictionaryDatum&, Name, const std::vector<long>&);


/** Add values of a vector<double> to a property DoubleVectorDatum in the dictionary.
 * This variant of append_property is for adding vector<double>s to vector<double>s of the
 * same size. It is required for collecting data across threads when multimeter
 * is running in accumulation mode.
 * @ingroup DictUtils
 */
void accumulate_property(DictionaryDatum&, Name, const std::vector<double>&);

#endif