/* -*- c++ -*- (enables emacs c++ mode) */
/*===========================================================================
 
 Copyright (C) 2002-2012 Yves Renard
 
 This file is a part of GETFEM++
 
 Getfem++  is  free software;  you  can  redistribute  it  and/or modify it
 under  the  terms  of the  GNU  Lesser General Public License as published
 by  the  Free Software Foundation;  either version 3 of the License,  or
 (at your option) any later version along with the GCC Runtime Library
 Exception either version 3.1 or (at your option) any later version.
 This program  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 Lesser General Public
 License and GCC Runtime Library Exception for more details.
 You  should  have received a copy of the GNU Lesser General Public License
 along  with  this program;  if not, write to the Free Software Foundation,
 Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA.
 
 As a special exception, you  may use  this file  as it is a part of a free
 software  library  without  restriction.  Specifically,  if   other  files
 instantiate  templates  or  use macros or inline functions from this file,
 or  you compile this  file  and  link  it  with other files  to produce an
 executable, this file  does  not  by itself cause the resulting executable
 to be covered  by the GNU Lesser General Public License.  This   exception
 does not  however  invalidate  any  other  reasons why the executable file
 might be covered by the GNU Lesser General Public License.
 
===========================================================================*/

/** @file dal_static_stored_objects.h 
    @author  Yves Renard <Yves.Renard@insa-lyon.fr>
    @date February 19, 2005
    @brief Stores interdependent getfem objects.

    Stored object :  

    A type of object to be stored should derive from
    dal::static_stored_object and a key should inherit from
    static_stored_object_key with an overloaded "compare" method.

    To store a new object, you have to test if the object is not
    already stored and then call dal::add_stored_object:
    @code
    if (!search_stored_object(your_object_key(parameters))) {
      add_stored_object(new your_object_key(parameters),
                        new your_object(parameters));
    }
    @endcode
    You can add a dependency of your new object with
    @code
    add_dependency(pointer_on_your_object,
                   pointer_on_the_object_object_from_which_it_depends);
    @endcode
    and then your object will be automatically deleted if the second object is
    deleted.
    The dependency can be added within the add_stored_object call:
    @code
    add_stored_object(new your_object_key(parameters), 
                      new your_object(parameters),
                      dependency);
    @endcode

    Boost intrusive_ptr are used.
*/
#ifndef DAL_STATIC_STORED_OBJECTS_H__
#define DAL_STATIC_STORED_OBJECTS_H__

#include "dal_config.h"

#include "getfem/getfem_arch_config.h"
#ifdef GETFEM_HAVE_BOOST
# include <boost/intrusive_ptr.hpp>
#else
# include <getfem_boost/intrusive_ptr.hpp>
#endif

namespace dal {

  enum permanence { PERMANENT_STATIC_OBJECT = 0, // not deletable object
		    STRONG_STATIC_OBJECT = 1,    // preferable not to delete it
		    STANDARD_STATIC_OBJECT = 2,  // standard
		    WEAK_STATIC_OBJECT = 3,      // delete it if necessary
		    AUTODELETE_STATIC_OBJECT = 4 // automatically deleted 
		             // when the last dependent object is deleted
  };


  class static_stored_object_key {
  protected :
    virtual bool compare(const static_stored_object_key &) const {
      GMM_ASSERT1(false, "This method should not be called");
    }

  public :
    bool operator < (const static_stored_object_key &o) const {
      // comparaison des noms d'objet
      if (typeid(*this).before(typeid(o))) return true;
      if (typeid(o).before(typeid(*this))) return false;
      return compare(o);
    }
    
    virtual ~static_stored_object_key() {}
    
  };


  template <typename var_type>
  class simple_key : virtual public static_stored_object_key { 
    var_type a;                                                     
  public :                                                           
    virtual bool compare(const static_stored_object_key &oo) const {
      const simple_key &o = dynamic_cast<const simple_key &>(oo);
      if (a < o.a) return true; return false; 
    }
    simple_key(var_type aa) : a(aa) {}
  };

#define DAL_SIMPLE_KEY(class_name, var_type)                         \
  struct class_name : public dal::simple_key<var_type> {	     \
    class_name(var_type aa) : dal::simple_key<var_type>(aa) {}	     \
  }
  
#define DAL_DOUBLE_KEY(class_name, var_type1, var_type2)	     \
  struct class_name :						     \
    public dal::simple_key<std::pair<var_type1,var_type2> > {	     \
    class_name(var_type1 aa, var_type2 bb) :			     \
      dal::simple_key<std::pair<var_type1,var_type2> >		     \
    (std::make_pair(aa,bb)) {}					     \
  }
  
#define DAL_TRIPLE_KEY(class_name, var_type1, var_type2, var_type3)	\
  struct class_name :							\
    public dal::simple_key<std::pair<var_type1,				\
				   std::pair<var_type2,var_type3> > > { \
  class_name(var_type1 aa, var_type2 bb, var_type3 cc) :		\
    dal::simple_key<std::pair<var_type1,				\
    std::pair<var_type2, var_type3> > >					\
    (std::make_pair(aa,std::make_pair(bb,cc))) {}			\
  }

#define DAL_FOUR_KEY(class_name,var_type1,var_type2,var_type3,var_type4)\
  struct class_name : public						\
  dal::simple_key<std::pair						\
		  <var_type1, std::pair<var_type2, std::pair		\
					<var_type3,var_type4> > > > {	\
    class_name(var_type1 aa, var_type2 bb, var_type3 cc,var_type4 dd) : \
      dal::simple_key<std::pair						\
		      <var_type1, std::pair<var_type2,			\
					    std::pair<var_type3,	\
						      var_type4> > > >	\
      (std::make_pair(aa,std::make_pair(bb,std::make_pair(cc, dd)))) {} \
  }



typedef const static_stored_object_key *pstatic_stored_object_key;
  
  /**
     base class for reference-counted getfem objects (via
     boost::intrusive_ptr).
     
     @see dal_static_stored_objects.h
  */
  class static_stored_object {
    mutable long pointer_ref_count_;
    
    
  public :
    static_stored_object(void) : pointer_ref_count_(0) {}
    virtual ~static_stored_object() { assert(pointer_ref_count_ == 0); }
    friend void intrusive_ptr_add_ref(const static_stored_object *o);
    friend void intrusive_ptr_release(const static_stored_object *o);
  };

  typedef boost::intrusive_ptr<const static_stored_object>
  pstatic_stored_object;

  template<class T> boost::intrusive_ptr<const T>
  stored_cast(pstatic_stored_object o) {
    return boost::intrusive_ptr<const T>(dynamic_cast<const T *>(o.get()));
  }

  inline void intrusive_ptr_add_ref(const static_stored_object *o)
  { o->pointer_ref_count_++; }

  inline void intrusive_ptr_release(const static_stored_object *o)
  { 
    //cout << "intrusive_ptr_release(" << typeid(*o).name() << ")@" << o << " refcnt=" << o->pointer_ref_count_ << "\n";
    assert(o->pointer_ref_count_ > 0);
    if (--(o->pointer_ref_count_) == 0) delete o; 
  }


  /** Gives a pointer to an object from a key pointer. */
  pstatic_stored_object search_stored_object(pstatic_stored_object_key k);

  /** Gives a pointer to an object from a key reference. */
  inline pstatic_stored_object
  search_stored_object(const static_stored_object_key &k)
  { return search_stored_object(&k); }

  /** Test if an object is stored. */
  bool exists_stored_object(pstatic_stored_object o);

  /** Add a dependency, object o1 will depend on object o2. */
  void add_dependency(pstatic_stored_object o1, pstatic_stored_object o2);

  /** remove a dependency. Return true if o2 has no more dependent object. */
  bool del_dependency(pstatic_stored_object o1, pstatic_stored_object o2);

  /** Add an object with two optional dependencies. */
  void add_stored_object(pstatic_stored_object_key k, pstatic_stored_object o,
			 permanence perm = STANDARD_STATIC_OBJECT);

  inline void
  add_stored_object(pstatic_stored_object_key k, pstatic_stored_object o,
		    pstatic_stored_object dep1,
		    permanence perm = STANDARD_STATIC_OBJECT) {
    add_stored_object(k, o, perm);
    add_dependency(o, dep1);
  }
			 
  inline void
  add_stored_object(pstatic_stored_object_key k, pstatic_stored_object o,
		    pstatic_stored_object dep1, pstatic_stored_object dep2, 
		    permanence perm = STANDARD_STATIC_OBJECT) {
    add_stored_object(k, o, perm);
    add_dependency(o, dep1);
    add_dependency(o, dep2);
  }
  
  inline void
  add_stored_object(pstatic_stored_object_key k, pstatic_stored_object o,
		    pstatic_stored_object dep1, pstatic_stored_object dep2,
		    pstatic_stored_object dep3,
		    permanence perm = STANDARD_STATIC_OBJECT) {
    add_stored_object(k, o, perm);
    add_dependency(o, dep1);
    add_dependency(o, dep2);
    add_dependency(o, dep3);
  }
  
  inline void
  add_stored_object(pstatic_stored_object_key k, pstatic_stored_object o,
		    pstatic_stored_object dep1, pstatic_stored_object dep2,
		    pstatic_stored_object dep3, pstatic_stored_object dep4,
		    permanence perm = STANDARD_STATIC_OBJECT) {
    add_stored_object(k, o, perm);
    add_dependency(o, dep1);
    add_dependency(o, dep2);
    add_dependency(o, dep3);
    add_dependency(o, dep4);
  }

  /** Delete an object and the object which depend on it. */
  void del_stored_object(pstatic_stored_object o, bool ignore_unstored=false);
  
  /** Delete all the object whose permanence is greater or equal to perm. */
  void del_stored_objects(int perm);

  /** Gives a pointer to a key of an object from its pointer. */
  pstatic_stored_object_key key_of_stored_object(pstatic_stored_object o);

  /** Show a list of stored objects (for debugging purpose). */
  void list_stored_objects(std::ostream &ost);

  /** Return the number of stored objects (for debugging purpose). */
  size_t nb_stored_objects(void);
}

#endif /* DAL_STATIC_STORED_OBJECTS_H__ */
