// This file is part of the Coral project.
// Please read the copyright notice in the LICENSE file.

#include <coral/core/garbage_collector.h>
#include <coral/core/Element.h>
#include <coral/lib/multithread.h>

/* I wonder why it's ext/... A GCC extension? Argh. */
#include <ext/hash_set>

#include <iostream>
#include <iomanip>

#include <unistd.h>

#include <sys/time.h>

#include <coral/lib/hashed_ptr.h>

namespace coral {
	/*
	 * There is a really annoying part with the garbage collector
	 * and attribute Elements. If e1 has an attribute e2,
	 * the reference count of e2 will be "one too big",
	 * since there is no connection back from e2 to e1.
	 * That means e2 (and elements under it) will be seen as used,
	 * even though they perhaps aren't.
	 * But, it doesn't matter: e1 will be considered unused at
	 * the appropriate time, and it will be deleted. The refcount
	 * of e2 will be dropped, and next GC round it will also be
	 * collected.
	 */

	typedef __gnu_cxx::hash_set<Element*, ptr_hash<Element> > tree_t;

	/*
	 * Counts the reference count of e,
	 * given its connections. Thus, the difference
	 * between e.refcount and the calculated value
	 * is nonzero (actually, > 0) if the element has pointers to it.
	 */
	static int cnt_refs(Element* e) {
		int refcount_should_be = 0;
		for (Element::slots_type::iterator i = e->begin();
		     i != e->end();
		     ++i) {
			Slot* mf = (*i).second;
			Property* mm = mf->getProperty();
			if (!mm->isBidirectional())
				/* Note this takes care of datatypes as well */
				continue;
			refcount_should_be += mf->__len__();
		}
		return refcount_should_be;
	}

	void unmark(tree_t& element_blacks, Element* white) {
		/* Already unmarked? */
		if (element_blacks.erase(white) == 0) return;

		for (Element::slots_type::iterator i = white->begin();
		     i != white->end();
		     ++i) {
			Property* mm;
			Slot* mf = (*i).second;
			mm = mf->getProperty();
			if (mm->getTarget()->isDatatypeDefinition())
				continue;
			Iterator m(mf);
			ref_counted* _child;
			while ((_child = m.next()) != NULL) {
				Element* child
					= dynamic_cast<Element*>(_child);
				if (!child) continue;
				unmark(element_blacks, child);
			}
		}
		
	}
	
	void x__garbage_collect() {
		/*
		 * We work in several passes
		 * 1. Mark all elements black.
		 * 2. Filter out whites
		 * 3. ref() all blacks, so we can...
		 * 4. __resetattr__() them
		 * 5. unref(), causing them to delete themselves _without_
		 *    a "cascade" of deletions.
		 *
		 * The reason this is done instead of marking
		 * whites, and then matching that against all_modelelements
		 * is that in practice, we will have lot's more whites
		 * than blacks. Now we go through all_modelelements only
		 * twice. The hash_set is likely to be small.
		 *
		 * I'm sure there are several better ways than this.
		 * 
		 */
		struct timeval start_time;
		struct timeval pass1_time;
		struct timeval pass2_time;
		struct timeval pass3_time;
		struct timeval pass4_time;
		struct timeval pass5_time;
		struct timeval end_time;
		gettimeofday(&start_time, NULL);

		tree_t element_blacks;
		{
			LockMutex l(mutex_modelelements);
#ifndef NDEBUG
			dlog << DEBUGLEVEL_DEBUG << "1. Mark all elements black" << std::endl;
#endif
			/* 1. Mark all elements black. We can skip primitive
			 * types, since they trivially are destructed when
			 * refcount goes to zero (no cyclic refs)
			 */
			for (all_modelelements_type::iterator h
				     = all_modelelements.begin();
			     h != all_modelelements.end();
			     ++h) {
				Element* e = static_cast<Element*>(*h);
				element_blacks.insert(e);
			}
			gettimeofday(&pass1_time, NULL);
#ifndef NDEBUG
			dlog << DEBUGLEVEL_DEBUG << "2. Filter out whites" << std::endl;
#endif
			/* 2. Filter out whites. Hmm, could we go
			 * through element_blacks instead? */
			for (all_modelelements_type::iterator h
				     = all_modelelements.begin();
			     h != all_modelelements.end();
			     ++h) {
				Element* e = static_cast<Element*>(*h);

				/* Already unmarked?*/
				if (element_blacks.find(e)
				    == element_blacks.end()) continue;

				int refs = cnt_refs(e);
				/* Is it black? (or possibly white, via
				 * some transitive white mark,
				 * so we can't ref() it yet. )
				 */
				if (e->get_ref_count() <= refs) continue;
			
				/* It is white, unmark it, and all
				 * elements transitively connected. */
				unmark(element_blacks, e);
			}
			gettimeofday(&pass2_time, NULL);

#ifndef NDEBUG
			dlog << DEBUGLEVEL_DEBUG << "3. ref() all blacks" << std::endl;
#endif
			/* 3. ref() all blacks. */
			for (tree_t::iterator h = element_blacks.begin();
			     h != element_blacks.end();
			     ++h) {
				(*h)->ref();
			}
			gettimeofday(&pass3_time, NULL);

#ifndef NDEBUG
			dlog << DEBUGLEVEL_DEBUG << "4. resetattr blacks" << std::endl;
#endif
			/* 4. resetattr them */
			for (tree_t::iterator h = element_blacks.begin();
			     h != element_blacks.end();
			     ++h) {
				(*h)->__resetAttrs__();
			}
			gettimeofday(&pass4_time, NULL);

			/*
			 * Block ends - mutex is released. The next stage
			 * can do its job and unref() => delete
			 */
		}
#ifndef NDEBUG
		dlog << DEBUGLEVEL_DEBUG << "5. unref() all blacks" << std::endl;
#endif
		/* 5. and unref(). */
		for (tree_t::iterator h = element_blacks.begin();
		     h != element_blacks.end();
		     ++h) {
			(*h)->unref();
		}
		gettimeofday(&pass5_time, NULL);
#ifndef NDEBUG
		dlog << DEBUGLEVEL_DEBUG << "6. Garbage collection done." << std::endl;
#endif
		gettimeofday(&end_time, NULL);
		try {
			dlog << DEBUGLEVEL_DEBUG << std::setfill('0');
			dlog << DEBUGLEVEL_DEBUG << "Garbage collector duration: "
				  << end_time.tv_sec - start_time.tv_sec << "." << std::setw(6) << end_time.tv_usec - start_time.tv_usec << std::endl
				  << "Pass 1: " << pass1_time.tv_sec - start_time.tv_sec << "." << std::setw(6) << pass1_time.tv_usec - start_time.tv_usec << std::endl
				  << "Pass 2: " << pass2_time.tv_sec - pass1_time.tv_sec << "." << std::setw(6) << pass2_time.tv_usec - pass1_time.tv_usec << std::endl
				  << "Pass 3: " << pass3_time.tv_sec - pass2_time.tv_sec << "." << std::setw(6) << pass3_time.tv_usec - pass2_time.tv_usec << std::endl
				  << "Pass 4: " << pass4_time.tv_sec - pass3_time.tv_sec << "." << std::setw(6) << pass4_time.tv_usec - pass3_time.tv_usec << std::endl
				  << "Pass 5: " << pass5_time.tv_sec - pass4_time.tv_sec << "." << std::setw(6) << pass5_time.tv_usec - pass4_time.tv_usec << std::endl;
		} catch (...) {
			;
		}
	}

	void garbage_collect() throw () {
		try {
			x__garbage_collect();
		} catch (...) {
			;
		}
	}


}
