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

#include <coral/core/Collection.h>

#include <coral/core/Element.h>

#include <algorithm>

#include <coral/lib/unicodestring.h>

#define UNREF_ALL() do {			\
		for (iterator i = begin();	\
		     i != end();		\
		     ++i) {			\
			coral_assert(*i);	\
			(*i)->unref();		\
		}				\
		this->n_elements = 0;		\
		this->values.clear();		\
	} while (0)

NAMESPACE_BEGIN(coral);

NAMESPACE_BEGIN();
inline int get_index(int index, int elements)
	throw (std::out_of_range) {
	/* count from the back? */
	if (index < 0) {
		index = elements + index;
	}
	
	/* Needed for Python to stop iteration. */
	if ((index >= elements) || (index < 0)) {
		char buf[128];
		snprintf(buf,
			 128,
			 "out_of_range for element, index %d of %d",
			 index,
			 elements);
		MAY_FAIL(dlog << DEBUGLEVEL_DEBUG << buf << std::endl);
		throw std::out_of_range(std::string(buf));
	}

	return index;
}
std::list<const ref_counted*>::iterator
getSeqIterator(std::list<const ref_counted*>::iterator begin,
	       std::list<const ref_counted*>::iterator end,
	       int index, int elements) {
	index = get_index(index, elements);

	coral_assert(index >= 0);

	std::list<const ref_counted*>::iterator i;

	if (index > elements/2) {
		i = end;
		/* NOTE the difference here to the
		 * const version of getSeqIterator */
		++index;
		for (; index < elements; ++index, --i)
			;
		coral_assert(index == elements);
	} else {
		i = begin;
		for (; index > 0; --index, ++i)
			;
	}
	return i;
}

/* duh, have to copy the above func here because of constness. */
std::list<const ref_counted*>::const_iterator
getSeqIterator(std::list<const ref_counted*>::const_iterator begin,
	       std::list<const ref_counted*>::const_iterator end,
	       int index, int elements) {
	index = get_index(index, elements);

	coral_assert(index >= 0);

	std::list<const ref_counted*>::const_iterator i;

	if (index > elements/2) {
		i = end;
		/* NOTE the difference here to the
		 * non-const version of getSeqIterator */
		for (; index < elements; ++index, --i)
			;
		coral_assert(index == elements);
	} else {
		i = begin;
		for (; index > 0; --index, ++i)
			;
	}
	return i;
}

int
get_list_index(std::list<const ref_counted*>::const_iterator begin,
	       std::list<const ref_counted*>::const_iterator iter) throw () {
	int index = 0;
	while (begin != iter) { ++begin; ++index; }
	return index;
}

void throw_if_outside(int index, int size) {
	if ((index < 0) || (index >= size)) {
		char buf[128];
		snprintf(buf,
			 128,
			 "out_of_range for element, index %d of %d",
			 index,
			 size);
		MAY_FAIL(dlog << DEBUGLEVEL_DEBUG << buf << std::endl);
		throw std::out_of_range(std::string(buf));
	}
}


NAMESPACE_END();

/*
 * Collection
 */

Collection::Collection() : ref_counted(), n_elements(0) {
	;
}

Collection::~Collection() throw () {
	if (this->get_ref_count() > 1) print_backtrace();
}

coral::unicodestring Collection::__str__() const {
	std::string result;
	result += "[ ";
	bool first = true;
	for (Iterator i(this); const ref_counted* r = i.next(); ) {
		if (!first)
			result += ", ";
		first = false;
		if (const Atom* a = dynamic_cast<const Atom*>(r)) {
			result += a->__str__().toUTF8();
		} else {
			char buf[1024];
			snprintf(buf, 1024,
				 "%p",
				 static_cast<const void*>(r));
			result += buf;
		}
	}
	if (this->n_elements)
		result += " ";

	result += "]";

	if (isOrdered()) {
		result += " {ordered}";
	}
	if (isBag()) {
		result += " {bag}";
	}
	return unicodestring(result, true);
}

Iterator* Collection::__iter__() const throw (std::bad_alloc) {
	return new Iterator(this);
}

void Collection::drop_index(int index) {
	const ref_counted* m = this->remove_keepref(index);
	if (m) m->unref();
}

bool Collection::includesAll(const Collection* c)
	const throw () {
	if (this == c) return true;
	Iterator m(c);
	ref_counted* a;
	while ((a = m.next()) != NULL) {
		if (!this->includes(a)) return false;
	}
	return true;
}

bool Collection::excludesAll(const Collection* c)
	const throw () {
	Iterator m(c);
	ref_counted* a;
	while ((a = m.next()) != NULL) {
		if (this->includes(a)) return false;
	}
	return true;
}

void Collection::unionWith(const Collection* c) throw (std::runtime_error, std::exception) {
  if (!c) {
    throw std::runtime_error("This is not a valid collection!");
  }
	Iterator m(c);
	ref_counted* a;
	while ((a = m.next()) != NULL) {
		add(a);
	}
}

void Collection::intersectWith(const Collection* c) throw (std::runtime_error, std::exception) {
  if (!c) {
    throw std::runtime_error("This is not a valid collection!");
  }
	std::map<const ref_counted*, int> in_c;
	std::map<const ref_counted*, int> in_this;

	for (Iterator m(c); ref_counted* a = m.next(); ) {
		++in_c[a];
	}

	for (Iterator m(this); ref_counted* a = m.next(); ) {
		++in_this[a];
	}

	for (std::map<const ref_counted*, int>::const_iterator i
		     = in_this.begin();
	     i != in_this.end();
	     ++i) {
		const ref_counted* r = (*i).first;
		int ihave = (*i).second;
		int j = std::min(ihave, in_c[r]);
		for (; j < ihave; ++j) {
			drop(r);
		}
	}


}


coral::autounref<coral::Set>
Collection::setDifference(const Collection* c) const throw (std::runtime_error, std::exception) {
  if (!c) {
    throw std::runtime_error("This is not a valid collection!");
  }
	Set* result = new Set();
	autounref<Set> t(result);

	const Set* c2 = dynamic_cast<const Set*>(c);
	if (!c2) {
		autounref<Set> ss(new Set());
		for (Iterator i(c); ref_counted* r = i.next(); ) {
			ss->add(r);
		}
		c2 = ss.get();
		ss.release();
	} else {
		c2->ref();
	}
	autounref<const Set> s(c2);

	Iterator m(this);
	ref_counted* a;
	while ((a = m.next()) != NULL) {
		if (!c2->includes(a)) {
			result->add(a);
		}
	}

	return t;
}


coral::autounref<coral::Collection>
Collection::newSimilarCollection() const {
	return newCollection(this->isBag(), this->isOrdered());
}


coral::autounref<Collection>
Collection::clone() const {
	coral::autounref<Collection> c(this->newSimilarCollection());
	Iterator m(this);
	const ref_counted* a;
	while ((a = m.next()) != NULL) {
		c.get()->add(a);
	}
	return c;
}

coral::autounref<Collection>
Collection::newCollection(bool is_bag,
			  bool is_ordered) {
	coral::autounref<Collection> c(NULL);
	if (!is_bag && !is_ordered) c = new Set();
	if (!is_bag &&  is_ordered) c = new OrderedSet();
	if ( is_bag && !is_ordered) c = new Bag();
	if ( is_bag &&  is_ordered) c = new Sequence();
	coral_assert(c.get() != NULL);
	return c;
}

/*
 * Bag
 */

Bag::Bag() : Collection() {
	;
}

Bag::~Bag() throw () {
	this->reset();
}

void Bag::reset() throw () {
	UNREF_ALL();
}

bool Bag::insert(const ref_counted* e, int index) {
	coral_assert(index == -1);
	coral_assert(e);
	this->values.insert(e);
	e->ref();
	++(this->n_elements);
	return true;
}

std::size_t Bag::count(const ref_counted* e) const throw () {
	return this->values.count(e);
}


bool Bag::__setitem__(int index, const ref_counted* e) {
	throw_if_outside(index, size());
	values_type::const_iterator i = values.begin();
	std::advance(i, index); // a bit slow but they deserve it...
	const ref_counted* old = *i;
	coral_assert(old);
	if (old == e) return true;
	this->values.insert(e);
	e->ref();

	this->values.erase(i);
	old->unref();
	return true;
}
ref_counted* Bag::__getitem__(int index) const throw (std::out_of_range) {
	throw_if_outside(index, size());
	values_type::const_iterator i = values.begin();
	std::advance(i, index); // a bit slow but they deserve it...
	ref_counted* e = const_cast<ref_counted*>(*i);
	coral_assert(e);
	return e;
}
	
ref_counted* Bag::remove_keepref(int index) throw (std::out_of_range) {
	coral_assert(0);
	values_type::const_iterator i = values.begin();
	std::advance(i, index); // a bit slow but they deserve it...
	ref_counted* e = const_cast<ref_counted*>(*i);
	coral_assert(e);
	this->values.erase(i);
	--(this->n_elements);
	return e;
}
	
int Bag::drop(const ref_counted* e) throw () {
	coral_assert(e);
	values_type::iterator i = values.find(e);
	if (i == values.end()) return -1;
	values.erase(i);
	--(this->n_elements);
	e->unref();
	return 0;
}
	
bool Bag::includes(const ref_counted* e) const throw () {
	return (this->values.find(e) != this->values.end());
}


/*
 * OrderedSet
 */
	
OrderedSet::OrderedSet() : Collection() {
	;
}
	
OrderedSet::~OrderedSet() throw () {
	this->reset();
}
	
void OrderedSet::reset() throw () {
	UNREF_ALL();
}


std::size_t OrderedSet::count(const ref_counted* e) const throw () {
	std::size_t res = std::count(values.begin(), values.end(), e);
	return res;
}

ref_counted* OrderedSet::__getitem__(int index)
	const throw (std::out_of_range) {
	values_type::const_iterator i =
		getSeqIterator(this->values.begin(),
			       this->values.end(),
			       index,
			       this->n_elements);
	ref_counted* e = const_cast<ref_counted*>(*i);
	coral_assert(e);
	return e;
}

bool OrderedSet::insert(const ref_counted* e, int index) {
	coral_assert(e);
	if (std::find(this->values.begin(),
		      this->values.end(), e) != this->values.end())
		return false;
	values_type::iterator i =
		getSeqIterator(this->values.begin(),
			       this->values.end(),
			       index,
			       this->n_elements+1);
	this->values.insert(i, e);
	e->ref();
	++(this->n_elements);
	return true;
}

bool OrderedSet::__setitem__(int index, const ref_counted* e) {
	coral_assert(e);
	values_type::iterator i =
		getSeqIterator(this->values.begin(),
			       this->values.end(),
			       index,
			       this->n_elements+1);
	const ref_counted* old = *i;
	coral_assert(old);
	if (old == e) return false;
	*i = e;
	e->ref();
	old->unref();
	return true;
}

ref_counted* OrderedSet::remove_keepref(int index) throw (std::out_of_range) {
	values_type::iterator i =
		getSeqIterator(this->values.begin(),
			       this->values.end(),
			       index,
			       this->n_elements+1);
	ref_counted* e = const_cast<ref_counted*>(*i);
	coral_assert(e);
	this->values.erase(i);
	--(this->n_elements);
	return e;
}
int OrderedSet::drop(const ref_counted* e) throw () {
	coral_assert(e);
	/* BUG faster */
	values_type::iterator i =
		find(this->values.begin(), this->values.end(), e);

	if (i == this->values.end()) return -1;

	int index = get_list_index(this->values.begin(), i);

	this->values.erase(i);
	--(this->n_elements);
	e->unref();

	return index;
}

bool OrderedSet::includes(const ref_counted* e) const throw () {
	return (std::find(this->values.begin(),
			  this->values.end(), e)
		!= this->values.end());
}


/*
 * OrderedSet
 */
	
Sequence::Sequence() : Collection() {
	;
}
	
Sequence::~Sequence() throw () {
	this->reset();
}
	
void Sequence::reset() throw () {
	UNREF_ALL();
}


std::size_t Sequence::count(const ref_counted* e) const throw () {
	std::size_t res = std::count(values.begin(), values.end(), e);
	return res;
}

ref_counted* Sequence::__getitem__(int index)
	const throw (std::out_of_range) {
	values_type::const_iterator i =
		getSeqIterator(this->values.begin(),
			       this->values.end(),
			       index,
			       this->n_elements);
	ref_counted* e = const_cast<ref_counted*>(*i);
	coral_assert(e);
	return e;
}

bool Sequence::insert(const ref_counted* e, int index) {
	coral_assert(e);
	values_type::iterator i =
		getSeqIterator(this->values.begin(),
			       this->values.end(),
			       index,
			       this->n_elements+1);
	this->values.insert(i, e);
	e->ref();
	++(this->n_elements);
	return true;
}

bool Sequence::__setitem__(int index, const ref_counted* e) {
	coral_assert(e);
	values_type::iterator i =
		getSeqIterator(this->values.begin(),
			       this->values.end(),
			       index,
			       this->n_elements+1);
	const ref_counted* old = *i;
	coral_assert(old);
	if (old == e) return false;
	*i = e;
	e->ref();
	old->unref();
	return true;
}

ref_counted* Sequence::remove_keepref(int index)
	throw (std::out_of_range) {
	values_type::iterator i =
		getSeqIterator(this->values.begin(),
			       this->values.end(),
			       index,
			       this->n_elements+1);
	ref_counted* e = const_cast<ref_counted*>(*i);
	coral_assert(e);
	this->values.erase(i);
	--(this->n_elements);
	return e;
}
int Sequence::drop(const ref_counted* e) throw () {
	coral_assert(e);
	/* BUG faster */
	values_type::iterator i =
		find(this->values.begin(), this->values.end(), e);

	if (i == this->values.end()) return -1;

	int index = get_list_index(this->values.begin(), i);

	this->values.erase(i);
	--(this->n_elements);
	e->unref();

	return index;
}

bool Sequence::includes(const ref_counted* e) const throw () {
	return (std::find(this->values.begin(),
			  this->values.end(), e)
		!= this->values.end());
}


/*
 * Set
 */

Set::Set() : Collection() {
	;
}
	
Set::~Set() throw () {
	this->reset();
}
	
void Set::reset() throw () {
	UNREF_ALL();
}


std::size_t Set::count(const ref_counted* e) const throw () {
	return this->values.count(e);
}

ref_counted* Set::__getitem__(int index) const throw (std::out_of_range) {
	throw_if_outside(index, size());
	values_type::const_iterator i = values.begin();
	std::advance(i, index); // a bit slow but they deserve it...
	ref_counted* e = const_cast<ref_counted*>(*i);
	coral_assert(e);
	return e;
}

bool Set::insert(const ref_counted* e, int index) {
	coral_assert(index == -1);
	coral_assert(e);
	/* Returns if we didn't add anything. */
	if (!this->values.insert(e).second) return false;
	e->ref();
	++(this->n_elements);
	return true;
}

bool Set::__setitem__(int index, const ref_counted* e) {
	coral_assert(e);
	throw_if_outside(index, size());
	/* Damn, what to do here??? */
	coral_assert(values.find(e) == values.end());
	values_type::const_iterator i = values.begin();
	std::advance(i, index); // a bit slow but they deserve it...
	const ref_counted* old = *i;
	coral_assert(old);
	coral_assert(old != e);
	try {
		std::pair<values_type::iterator, bool> p
			= this->values.insert(e);
		coral_assert(p.second == true);
	} catch (...) {
		return false;
	}
	e->ref();
	this->values.erase(i);
	old->unref();
	return true;
}

ref_counted* Set::remove_keepref(int index) throw (std::out_of_range) {
	throw_if_outside(index, size());
	values_type::const_iterator i = values.begin();
	std::advance(i, index); // a bit slow but they deserve it...
	ref_counted* e = const_cast<ref_counted*>(*i);
	coral_assert(e);
	this->values.erase(i);
	--(this->n_elements);
	return e;
}

int Set::drop(const ref_counted* e) throw () {
	coral_assert(e);
	if (this->values.erase(e) == 0) {
		return -1;
	}
	--(this->n_elements);
	e->unref();
	return 0;
}

bool Set::includes(const ref_counted* e) const throw () {
	return (this->values.find(e) != this->values.end());
}


/*
 * Iterator
 */

Iterator::~Iterator() throw () {
#if 0
	std::cout << "Deleted Iterator " << this
		  << " of collection " << this->collection
		  << std::endl;
#endif
	if (this->iter_type != ITER_ONE_ELEMENT)
		this->collection->unref();
}

void Iterator::__collection(const Collection* c) {
#if 0
	std::cout << "Created Iterator " << this
		  << " of collection " << c << std::endl;
#endif
	coral_assert(c);
	this->collection = c;
	this->collection->ref();
	if (c->isBag()) {
		if (c->isOrdered()) {
			this->iter_type = ITER_MMSEQ;
			this->seqi =
				static_cast<const Sequence*>(c)->begin();
		} else {
			this->iter_type = ITER_MMBAG;
			this->bagi =
				static_cast<const Bag*>(c)->begin();
		}
	} else if (c->isOrdered()) {
		this->iter_type = ITER_MMORDSET;
		this->oseti =
			static_cast<const OrderedSet*>(c)->begin();
	} else {
		this->iter_type = ITER_MMSET;
		this->seti = static_cast<const Set*>(c)->begin();
	}
}

Iterator::Iterator(const Collection* c) {
	__collection(c);
}

Iterator::Iterator(const Collection& c) {
	__collection(&c);
}

Iterator::Iterator(const Slot* s) {
	coral_assert(s);
	Property* p = s->getProperty();
	if (p->isAtMostOne()) {
		/* Note that we don't ref it, it's the
		 * callers responsibility.. I think.
		 */
		this->element = (*s)[0];
		this->iter_type = ITER_ONE_ELEMENT;
	} else {
		__collection(s->getCollection());
	}
}

Iterator::Iterator(const Slot& s) {
	Property* p = s.getProperty();
	if (p->isAtMostOne()) {
		/* Note that we don't ref it, it's the
		 * callers responsibility.. I think.
		 */
		this->element = s[0];
		this->iter_type = ITER_ONE_ELEMENT;
	} else {
		__collection(s.getCollection());
	}
}

ref_counted* Iterator::next() throw () {
	switch (this->iter_type) {
	case ITER_MMBAG: {
		const Bag* m =
			static_cast<const Bag*>(this->collection);
		if (this->bagi == m->end()) break;
		return const_cast<ref_counted*>(*(this->bagi++));
		break;
	}
	case ITER_MMORDSET: {
		const OrderedSet* m =
			static_cast<const OrderedSet*>(this->collection);
		if (this->oseti == m->end()) break;
		return const_cast<ref_counted*>(*(this->oseti++));
		break;
	}
	case ITER_MMSEQ: {
		const Sequence* m =
			static_cast<const Sequence*>(this->collection);
		if (this->seqi == m->end()) break;
		return const_cast<ref_counted*>(*(this->seqi++));
		break;
	}
	case ITER_MMSET: {
		const Set* m =
			static_cast<const Set*>(this->collection);
		if (this->seti == m->end()) break;
		return const_cast<ref_counted*>(*(this->seti++));
		break;
	}
	case ITER_ONE_ELEMENT: {
		ref_counted* e = const_cast<ref_counted*>(this->element);
		this->element = NULL;
		return e;
		break;
	}
	default: {
		coral_assert(0);
		break;
	}
	}
	return NULL;
}

NAMESPACE_END();

