/*
 *  dictstack.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 DICTIONARYSTACK_H
#define DICTIONARYSTACK_H
/* 
    SLI's dictionary stack
*/
#include <typeinfo>
#include "dictdatum.h"
#include <list>
#include "sliexceptions.h"


/***************************************************************

Problems:
     
- is ist better to uses dictionaries as references to common
  objects like in PS. What is the exact meaning of undef and
  where in our current situation (read RedBook).     
- more efficient implementation exploiting 
  the name ids (documented elsewhere).



    History:
            (1) using list<Dictionary> 
               MD, 23.6.1, Freiburg
            (0) first version (single dictionary)
               MOG, MD, June 1997, Freiburg
***************************************************************
*/

/**
 * The macro DICTSTACK_CACHE switches on two caches:
 * 1. cache_, global cache for the dictionary stack
 * 2. basecache_, a cache for the system dictionary
 * These caches are direct lookup table with one entry per name.
 * They work as follows:
 * If a name is looked up, it is looked up in the cache. 
 * If the cache does not have an entry, the dictionary stack is searched and
 * the name/token combination is added to the cache. 
 */ 
#ifndef DICTSTACK_CACHE
#define DICTSTACK_CACHE 1
#endif

//#undef DICTSTACK_CACHE

class DictionaryStack
{
private:
    const Token VoidToken;
    std::list<DictionaryDatum> d;
    DictionaryDatum base_;
#ifdef DICTSTACK_CACHE
    std::vector<const Token *> cache_;
    std::vector<const Token *> basecache_;
#endif

public:
  DictionaryStack(const Token & = Token());
  DictionaryStack(const DictionaryStack&);
  ~DictionaryStack();

    
#ifdef DICTSTACK_CACHE
  /**
   * Add a token to the cache.
   */
  void cache_token( const Name &n, const Token *result)
  {
    Name::handle_t key=n.toIndex();
    if (key>=cache_.size())
      cache_.resize(Name::num_handles()+100,0);
    cache_[key]= result;
  }

  void basecache_token( const Name &n, const Token *result)
  {
    Name::handle_t key=n.toIndex();
    if (key>=basecache_.size())
      basecache_.resize(Name::num_handles()+100,0);
    basecache_[key]= result;
  }

  /**
   * Clear a name from the cache.
   * This function should be called in each def variant.
   */
  void clear_token_from_cache(const Name&n)
  {
    Name::handle_t key=n.toIndex();
    if(key<cache_.size())
      cache_[key]=0;
  }

  void clear_token_from_basecache(const Name&n)
  {
    Name::handle_t key=n.toIndex();
    if(key<basecache_.size())
      basecache_[key]=0;
  }

  void clear_dict_from_cache(DictionaryDatum d)
  {
    for(TokenMap::iterator i = d->begin(); i != d->end(); ++i)
      clear_token_from_cache(i->first);
  }


  /** Clear the entire cache.
   * This should be  called whenever a dictionary is pushed or poped.
   * Alternative, one could just clear the names from the moved dictionary.
   */
  void clear_cache()
  {
    const size_t cache_size=cache_.size();

    for(size_t i=0; i< cache_size; ++i)
      cache_[i]=0;
  }

#endif

  const Token & lookup(const Name &n)
  {  
    try
      {
	return lookup2(n);
      }
    catch(UndefinedName)
    {
      return VoidToken;
    }
  }

  const Token & lookup2(const Name &n)
  { 
#ifdef DICTSTACK_CACHE
    Name::handle_t key=n.toIndex();
    if (key<cache_.size())
      {
    	const Token *result=cache_[key];
    	if(result)
    	  return *result;
      }
#endif

    std::list<DictionaryDatum>::const_iterator i=d.begin();
    
    while (i!=d.end())
      {
	TokenMap::const_iterator where =(*i)->find(n);
	if(where!=(*i)->end())
	  {
#ifdef DICTSTACK_CACHE
	    cache_token(n,&(where->second)); // Update the cache 
#endif
	    return where->second;
	  }
	++i;
      }
    throw UndefinedName(n.toString());
  }
  
  /** Lookup a name searching only the bottom level dictionary.
   *  If the Name is not found,
   *  @a VoidToken is returned.
   */
  const Token & baselookup(const Name &n) // lookup in a specified
  {                                           // base dictionary
#ifdef DICTSTACK_CACHE
      Name::handle_t key=n.toIndex();
      if (key<basecache_.size())
	{
	  const Token *result=basecache_[key];
	  if(result)
	    return *result;
	}
#endif
      TokenMap::const_iterator where =base_->find(n);

    if ( where != base_->end() )
    {
#ifdef DICTSTACK_CACHE
      cache_token(n, &(where->second)); // Update the cache
      basecache_token(n, &(where->second)); // and the basecache
#endif
      return where->second;
    }
    else
      return VoidToken;
  }

  /** Test for a name searching all dictionaries on the stack.
   */
  bool known(const Name &n)
    { 
#ifdef DICTSTACK_CACHE
      Name::handle_t key=n.toIndex();
      if (key<cache_.size())
	{
	  const Token *result=cache_[key];
	  if(result)
	    return true;
	}
#endif
      std::list<DictionaryDatum>::const_iterator i(d.begin());

      while (i!=d.end())
	{
	  TokenMap::const_iterator where =(*i)->find(n);
	  if(where!=(*i)->end())
	    {
#ifdef DICTSTACK_CACHE
	      cache_token(n,&(where->second)); // Update the cache 
#endif
	      return true;
	  }
	++i;
      }
      return false;
    }
  
  /** Test for a name in the bottom level dictionary.
   */
  bool baseknown(const Name &n) // lookup in a specified
  {                                                  // base dictionary
#ifdef DICTSTACK_CACHE
      Name::handle_t key=n.toIndex();
      if (key<basecache_.size())
	{
	  const Token *result=basecache_[key];
	  if(result)
	    return true;
	}
#endif
      TokenMap::const_iterator where =base_->find(n);
      if(where!=base_->end())
	{
#ifdef DICTSTACK_CACHE
	  basecache_token(n,&(where->second)); // Update the basecache 
	  cache_token(n,&(where->second)); // and the cache 
#endif
	  return true;
	}
      return false;
  }


   //
   // def and def_move operate on the top level dictionary.
   // undef is not defined for the dictionary stack.

  /** Bind a Token to a Name in the top level dictionary.
   *  The Token is copied.
   */
  void def(const Name &, const Token &);

  /** Unbind a previously defined Name from its token. Seach in all dictionaries.
   */
  void undef(const Name &);

  /** Bind a Token to a Name in the bottom level dictionary.
   *  The Token is copied.
   */
  void basedef(const Name &n, const Token &t);

  /** Bind a Token to a Name in the top level dictionary.
   *  The Token is moved.
   */
  void def_move(const Name&, Token &);
  
  /**
   * This function must be called once to initialize the systemdict cache.
   */
  void set_basedict();

  /** Bind a Token to a Name in the bottom level dictionary.
   *  The Token is moved.
   */
  void basedef_move(const Name &n, Token &t);

  bool where(const Name&, Token&);
    
  void pop(void);


   //
   // a Dictionary is always wrapped in a Token
   //
   void top(Token &) const;
   void push(const DictionaryDatum &);
   void push(Token &);
   
   void clear(void);
   void toArray(TokenArray &) const;
   //
   // move is efficient for interaction with operand and execution
   // stack, but can not be implemented in this version
   //
//    void pop_move(Token &) const;
//    void push_move(const Token &);

    // 
    // number of dictionaries currently on the stack
    //
  size_t size(void) const;


    //
    // info for debugging purposes. 
    // calls info(ostream&) for all dictionaries 
    //
  void info(std::ostream&) const;
  void top_info(std::ostream &) const; // calls info of top dictionary
  const DictionaryStack& operator=(const DictionaryStack&); 
};

inline
void DictionaryStack::def(const Name &n, const Token &t)
{
  //
  // insert (n,t) in top level dictionary
  // dictionary stack must contain at least one dictionary
  // VoidToken is an illegal value for t.
  //
#ifdef DICTSTACK_CACHE
  cache_token(n,&((*d.begin())->insert(n,t)));
#endif
#ifndef DICTSTACK_CACHE
        (**d.begin())[n]=t;
#endif
}

inline
void DictionaryStack::def_move(const Name &n, Token &t)
{
  //
  // insert (n,t) in top level dictionary
  // dictionary stack must contain at least one dictionary
  // VoidToken is an illegal value for t.
  // def_move returns t as the VoidToken.  
  //
    /* clear_token_from_cache(n); */

#ifdef DICTSTACK_CACHE
     cache_token(n,&((*d.begin())->insert_move(n,t))); 
#endif
#ifndef DICTSTACK_CACHE
     (*d.begin())->insert_move(n,t);
#endif
}

#endif