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


#include "getfem/dal_static_stored_objects.h"
#include "getfem/dal_singleton.h"
#include <map>
#include <list>
#include <set>

namespace dal {

  // Pointer to an object with the dependencies
  struct enr_static_stored_object {
    pstatic_stored_object p;
    bool valid;
    permanence perm;
    std::set<pstatic_stored_object> dependent_object;
    std::set<pstatic_stored_object> dependencies;
    enr_static_stored_object(pstatic_stored_object o, permanence perma)
      : p(o), valid(true), perm(perma) {}
    enr_static_stored_object(void)
      : p(0), valid(true), perm(STANDARD_STATIC_OBJECT) {}
  };
  
  // Pointer to a key with a coherent order
  struct enr_static_stored_object_key {
    pstatic_stored_object_key p;
    bool operator < (const enr_static_stored_object_key &o) const
    { return (*p) < (*(o.p)); }
    enr_static_stored_object_key(pstatic_stored_object_key o) : p(o) {}
  };

  // Storing array types
  typedef std::map<enr_static_stored_object_key, enr_static_stored_object>
  stored_object_tab;
  struct stored_key_tab : public std::map<pstatic_stored_object,
					  pstatic_stored_object_key> {
    ~stored_key_tab() {
      for (iterator it = begin(); it != end(); ++it) {
	/*cerr << "~stored_key_tab: it->first = " << it->first << " of type "
	  << typeid(*(it->first)).name() << " . Delete key@" << it->second << endl;*/
	delete it->second;
      }
    }
  };
  
  // Gives a pointer to a key of an object from its pointer
  pstatic_stored_object_key key_of_stored_object(pstatic_stored_object o) {
    stored_key_tab& stored_keys = dal::singleton<stored_key_tab>::instance();
    stored_key_tab::iterator it = stored_keys.find(o);
    if (it != stored_keys.end()) return it->second;
    return 0;
  }

  // Test if an object is stored.
  bool exists_stored_object(pstatic_stored_object o) {
    stored_key_tab& stored_keys = dal::singleton<stored_key_tab>::instance();
    return (stored_keys.find(o) != stored_keys.end());
  }

  // Gives a pointer to an object from a key pointer
  pstatic_stored_object search_stored_object(pstatic_stored_object_key k) {
    stored_object_tab& stored_objects
      = dal::singleton<stored_object_tab>::instance();
    stored_object_tab::iterator it
      = stored_objects.find(enr_static_stored_object_key(k));
    if (it != stored_objects.end()) return it->second.p;
    return 0;
  }

  // Gives an iterator on stored object from a pointer object
  static inline stored_object_tab::iterator 
  iterator_of_object(pstatic_stored_object o) {
    stored_object_tab& stored_objects
      = dal::singleton<stored_object_tab>::instance();
    pstatic_stored_object_key k = key_of_stored_object(o);
    if (k) {
      stored_object_tab::iterator it
	= stored_objects.find(enr_static_stored_object_key(k));
      GMM_ASSERT1(it != stored_objects.end(),
		  "Object has key but cannot be found");
      return it;
    }
    return stored_objects.end();
  }

  // Test the validity of arrays
  void test_stored_objects(void) {
    stored_key_tab& stored_keys = dal::singleton<stored_key_tab>::instance();
    for (stored_key_tab::iterator it = stored_keys.begin();
	 it != stored_keys.end(); ++it)
      iterator_of_object(it->first);
    stored_object_tab& stored_objects
      = dal::singleton<stored_object_tab>::instance();
    for (stored_object_tab::iterator it = stored_objects.begin();
	 it != stored_objects.end(); ++it)
      GMM_ASSERT1(iterator_of_object(it->second.p) != stored_objects.end(),
		  "Object has key but cannot be found");
  }

  // Add a dependency, object o1 will depend on object o2
  void add_dependency(pstatic_stored_object o1, pstatic_stored_object o2) {
    stored_object_tab& stored_objects
      = dal::singleton<stored_object_tab>::instance();
    stored_object_tab::iterator it1 = iterator_of_object(o1);
    stored_object_tab::iterator it2 = iterator_of_object(o2);
    if (it1 != stored_objects.end() && it2 != stored_objects.end()) {
      it2->second.dependent_object.insert(o1);
      it1->second.dependencies.insert(o2);
    }
    else {
      cerr << "Problem adding dependency between " << o1 << " of type "
	   << typeid(*o1).name() << " and " << o2 << " of type "
	   << typeid(*o2).name() << ". ";
      if (it1 == stored_objects.end()) cerr << "First object does not exist.";
      if (it2 == stored_objects.end()) cerr << "Second object does not exist.";
      cerr << endl;
      assert(false);
      GMM_ASSERT1(false, "Add_dependency : Inexistent object");
    }
  }

  // remove a dependency. Return true if o2 has no more dependent object.
  bool del_dependency(pstatic_stored_object o1, pstatic_stored_object o2) {
    stored_object_tab& stored_objects
      = dal::singleton<stored_object_tab>::instance();
    stored_object_tab::iterator it1 = iterator_of_object(o1);
    stored_object_tab::iterator it2 = iterator_of_object(o2);
    if (it1 != stored_objects.end() && it2 != stored_objects.end()) {
      it2->second.dependent_object.erase(o1);
      it1->second.dependencies.erase(o2);
      return it2->second.dependent_object.empty();
    }
    return true;
  }

  // Add an object with two optional dependencies
  void add_stored_object(pstatic_stored_object_key k, pstatic_stored_object o,
			 permanence perm) {
    stored_object_tab& stored_objects
      = dal::singleton<stored_object_tab>::instance();
    stored_key_tab& stored_keys = dal::singleton<stored_key_tab>::instance();
    GMM_ASSERT1(stored_keys.find(o) == stored_keys.end(),
		"This object has already been stored, "
		"possibly with another key");
    stored_keys[o] = k;
    stored_objects[enr_static_stored_object_key(k)]
      = enr_static_stored_object(o, perm);
    /*cerr << "add_stored_object " << o.get() << " of type "
      << typeid(*o).name() << endl;*/
  }

  // Only delete the object but not the dependencies
  static void basic_delete(std::list<pstatic_stored_object> &to_delete){
    stored_object_tab& stored_objects
      = dal::singleton<stored_object_tab>::instance();
    stored_key_tab& stored_keys = dal::singleton<stored_key_tab>::instance();
    std::list<pstatic_stored_object>::iterator it;
    for (it = to_delete.begin(); it != to_delete.end(); ++it) {
      // cout << "delete object " << (*it).get() << " of type "
      //      << typeid(*(*it)).name() << endl;
      pstatic_stored_object_key k = key_of_stored_object(*it);
      stored_object_tab::iterator ito = stored_objects.find(k);
      if (k) stored_keys.erase(*it);
      if (ito != stored_objects.end()) {
	delete ito->first.p;
	stored_objects.erase(ito);
      }
    }
  }
  
  // Delete a list of objects and their dependencies
  void del_stored_objects(std::list<pstatic_stored_object> &to_delete,
			  bool ignore_unstored) {
    stored_object_tab& stored_objects
      = dal::singleton<stored_object_tab>::instance();
    std::list<pstatic_stored_object>::iterator it, itnext;
    for (it = to_delete.begin(); it != to_delete.end(); it = itnext) {
      itnext = it; itnext++;
      stored_object_tab::iterator ito = iterator_of_object(*it);
      if (ito == stored_objects.end()) {
	if (ignore_unstored)
	  to_delete.erase(it);
	else
	  GMM_ASSERT1(false, "This object is not stored : " << it->get()
		      << " typename: " << typeid(*it->get()).name());
      }
      else
	iterator_of_object(*it)->second.valid = false;
    }
    std::set<pstatic_stored_object>::iterator itd;
    for (it = to_delete.begin(); it != to_delete.end(); ++it) {
      if (*it) {
	stored_object_tab::iterator ito = iterator_of_object(*it);
	GMM_ASSERT1(ito != stored_objects.end(), "An object disapeared !");
	ito->second.valid = false;
	std::set<pstatic_stored_object> dep = ito->second.dependencies;
	for (itd = dep.begin(); itd != dep.end(); ++itd) {
	  if (del_dependency(*it, *itd)) {
	    stored_object_tab::iterator itod=iterator_of_object(*itd);
	    if (itod->second.perm == AUTODELETE_STATIC_OBJECT
		&& itod->second.valid) {
	      itod->second.valid = false;
	      to_delete.push_back(*itd);
	    }
	  }
	}
	for (itd = ito->second.dependent_object.begin();
	     itd != ito->second.dependent_object.end(); ++itd) {
	  stored_object_tab::iterator itod=iterator_of_object(*itd);
	  if (itod != stored_objects.end()) {
	    GMM_ASSERT1(itod->second.perm != PERMANENT_STATIC_OBJECT,
			"Trying to delete a permanent object " << *itd);
	    if (itod->second.valid) {
	      itod->second.valid = false;
	      to_delete.push_back(itod->second.p);
	    }
	  }
	}
      }
    }
    basic_delete(to_delete);
  }

  // Delete an object and its dependencies
  void del_stored_object(pstatic_stored_object o, bool ignore_unstored) {
    std::list<pstatic_stored_object> to_delete;
    to_delete.push_back(o);
    del_stored_objects(to_delete, ignore_unstored);
  }
  
  // Delete all the object whose perm is greater or equal to perm
  void del_stored_objects(permanence perm) {
    stored_object_tab& stored_objects
      = dal::singleton<stored_object_tab>::instance();
    if (perm == PERMANENT_STATIC_OBJECT) perm = STRONG_STATIC_OBJECT;
    std::list<pstatic_stored_object> to_delete;
    stored_object_tab::iterator it;
    for (it = stored_objects.begin(); it != stored_objects.end(); ++it)
      if (it->second.perm >= perm)
	to_delete.push_back(it->second.p);
    del_stored_objects(to_delete, false);
  }

  // List the stored objects for debugging purpose
  void list_stored_objects(std::ostream &ost) {
    stored_key_tab& stored_keys = dal::singleton<stored_key_tab>::instance();
    if (stored_keys.begin() == stored_keys.end())
      ost << "No static stored objects" << endl;
    else
      ost << "Static stored objects" << endl;
    for (stored_key_tab::iterator it = stored_keys.begin();
	 it != stored_keys.end(); ++it) {
      ost << "Object: " << it->first << " typename: "
	  << typeid(*it->first).name() << endl;
    }
  }

  // Number of stored objects
  size_t nb_stored_objects(void) {
    stored_key_tab& stored_keys = dal::singleton<stored_key_tab>::instance();
    return stored_keys.size();
  }

}
