#ifndef _PTRLIST_H
#define _PTRLIST_H

#include <bfc/std_math.h>
//#include <bfc/memblock.h>
#include <bfc/bfc_assert.h>

#include <bfc/platform/platform.h>

//#include <wasabicfg.h>

#ifdef _DEBUG
extern int ptrlist_totalnitems;
#endif

// Disable the "identifier was truncated to '255' characters..." warning.
#pragma warning( disable : 4786 )

/*
 
a generic pointer list template. only takes up 12 bytes when empty. auto-grows
the array as necessary, NEVER shrinks it unless you removeAll() or equivalent
 
Use, PtrList<typename>, never PtrListRoot
 
*/

// yes, this really should be an enum or something
#define PTRLIST_POS_LAST -1

// 4k each, leaving 16 bytes for MALLOC overhead
//#define DEF_PTRLIST_INCREMENT (4096/sizeof(void*)-16)

// in average, 4k doubles the VM working set of a skin, 128bytes (32 elements) seems most optimal
#define DEF_PTRLIST_INCREMENT (128)

class __foreach;

// base class, to share as much code as possible
class NOVTABLE PtrListRoot
{
	friend class __foreach;
protected:
	PtrListRoot(int initial_size = 0);
	PtrListRoot(const PtrListRoot *from);
	virtual ~PtrListRoot();

	void copyFrom(const PtrListRoot *from);
	void appendFrom(const PtrListRoot *from);

	void setMinimumSize(int nslots);	// expand to at least this many slots
	int getNumItems() const;
	int size() const                                 { return nitems; }
	bool empty()                                     { return nitems == 0; }

	void *enumItem(int n) const;

	void moveItem(int from, int to);

	int removeItem(void *item);
	void removeEveryItem(void *item);
	void removeByPos(int pos);
	void removeLastItem();
	void removeAll();
	void freeAll();
	void purge();

	// gross-ass linear search to find index of item
	// note that PtrListSorted provides a binary search version
	int searchItem(void *item) const;

#if 0//fuct
	// speedy binary search. although it'll be fuct if it's not sorted right
	int bsearchItem(void *item) const;
#endif

	void *addItem(void *item, int pos, int inc);
	void *setItem(void *item, int pos); // replace what's in the slot with the new value

	void reverse();

	void **getItemList() const                       { return items; } // try to avoid! this is inline to make q() fast

private:
#undef verify // for Mac
	void verify();

	int nitems, nslots;
	void **items;
};

// now we add the methods that refer specifically to the pointer type
template <class T>
class PtrList : public PtrListRoot
{
	friend class __foreach;
public:
	PtrList(int initial_size = 0)                    {}
	PtrList(const PtrList<T> &r)                     { copyFrom(&r); }
	PtrList(const PtrList<T> *r)                     { copyFrom(r); }
	~PtrList() {}

	// copy another PtrList
	// deletes previous contents
	void copyFrom(const PtrList<T> *from)            { PtrListRoot::copyFrom(from); }

	// append contents of another PtrList to the end of this one
	// preserves previous contents
	void appendFrom(const PtrList<T> *from)          { PtrListRoot::appendFrom(from); }

	// adding

	// expand freelist to at least this many slots, even if 0 items in list
	void setMinimumSize(int nslots)                  { PtrListRoot::setMinimumSize(nslots); }

	// provide a public addItem for the pointer type
	T *addItem(const T *item, int pos = PTRLIST_POS_LAST, int inc = DEF_PTRLIST_INCREMENT)
	{
		return static_cast<T *>(PtrListRoot::addItem(const_cast<T*>(item), pos, inc));
	}

	void push_back(const T* item)                    { return addItem(item); }
	
	// replace what's in the slot with the new value
	T *setItem(const T *item, int pos)               { return static_cast<T *>(PtrListRoot::setItem(const_cast<T*>(item), pos)); }

	// reverse the order of the list in place
	void reverse()                                   { PtrListRoot::reverse(); }

	// enumerating

	// returns # of items in list
	int getNumItems() const                          { return PtrListRoot::getNumItems(); }

	// basic list enumerator. returns NULL for out of bounds
	T *enumItem(int n) const                         { return static_cast<T *>(PtrListRoot::enumItem(n)); }
	T *operator[](int n) const                       { return enumItem(n); }

	// this will safely return NULL if 0 items due to enumItems's boundscheck
	T *getFirst() const                              { return enumItem(0); }
	T *getLast() const                               { return enumItem(getNumItems() - 1); }

	// this is a NON-BOUNDS-CHECKING lookup
	T *q(int n)                                      { return static_cast<T*>(getItemList()[n]); }

	// gross-ass linear search to find index of item
	// note that PtrListSorted provides a binary search version
	int searchItem(T *item) const                    { return PtrListRoot::searchItem(item); }
	int haveItem(T *item) const                      { return searchItem(item) >= 0; }

	// deleteing

	// removes first instance of a pointer in list, returns how many are left
	int removeItem(T *item)                          { return PtrListRoot::removeItem(item); }
	
	//DEPRECATED
	int delItem(T *item)                             { return removeItem(item); }
	
	// removes all instances of this pointer
	void removeEveryItem(const T *item)              { PtrListRoot::removeEveryItem(const_cast<T*>(item)); }	
	// removes pointer at specified position regardless of value
	void removeByPos(int pos)                        { PtrListRoot::removeByPos(pos); }	
	// DEPRECATED
	void delByPos(int pos)                           { removeByPos(pos); }
	// removes last item
	void removeLastItem()                            { PtrListRoot::removeLastItem(); }
	// removes all entries, also deletes memory space
	void removeAll()                                 { PtrListRoot::removeAll(); }
	// removes all entries, calling FREE on the pointers
	void freeAll()                                   { PtrListRoot::freeAll(); }
	// removes all entries, calling delete on the pointers
	void deleteAll()
	{
		int i, nitems = getNumItems();
		for (i = 0; i < nitems; i++)
			delete enumItem(i);
		removeAll();
	}
	// removes all entries, calling delete on the pointers
	// FG>this version removes each entry as it deletes it so if
	// one of the object uses this list in its destructor, it will
	// still work. It is MUCH slower than deleteAll tho.
	void deleteAllSafe()
	{
		//CUT    ASSERT(!(nitems != 0 && items == NULL));
		while (getNumItems())
		{
			T *i = enumItem(0);
			delete i;
			removeItem(i);
		}
	}
	void deleteItem(int item)
	{
		if (item < getNumItems())
		{
			deleteItem(enumItem(item));
		}
	}
	void deleteItem(T *item)
	{
		delete item;
		removeItem(item);
	}

	void moveItem(int from, int to) { PtrListRoot::moveItem(from, to); }

	static T *castFor(void *ptr) { return static_cast<T*>(ptr); }

	using PtrListRoot::purge;

protected:
	T **getItemList()
	{
		return reinterpret_cast<T **>(PtrListRoot::getItemList());
	}
};

class NotSorted
{
public:
	// comparator for searching -- override
	static int compareAttrib(const wchar_t *attrib, void *item) { return 0; }
	// comparator for sorting -- override	, -1 p1 < p2, 0 eq, 1 p1 > p2
	static int compareItem(void *p1, void* p2) { return CMP3(p1, p2); }
};

//template <class T, class C> class NoSort {
//  static void _sort(T **, int) {}
//};

// a base class to sort the pointers
// you must implement the comparisons (C) and the sort algorithm (S)
template < class T, class C, class S >
class PtrListSorted : public PtrList<T>
{
public:
	PtrListSorted(int initial_size = 0) : PtrList<T>(initial_size)
	{
		need_sorting = 0;
		auto_sort = true;
		dups_low = dups_hi = dups_pos = 0;
	}

	void copyFrom(const PtrList<T> *from)
	{
		PtrList<T>::copyFrom(from);
		need_sorting = 1;
		if (auto_sort) sort();
	}

	T *addItem(T *item, int pos = PTRLIST_POS_LAST, int inc = DEF_PTRLIST_INCREMENT)
	{
#if 1
		// check for appending in sorted order
		if (pos == PTRLIST_POS_LAST && !need_sorting && auto_sort)
		{
			int n = PtrList<T>::getNumItems();
			if (n > 0 && C::compareItem(item, q(n - 1)) < 0) need_sorting = 1;
		}
		else
#endif
			need_sorting = 1;
		return PtrList<T>::addItem(item, pos, inc);
	}

	void sort(bool force_sort = false)
	{
		if (need_sorting || force_sort)
			S::_sort(PtrList<T>::getItemList(), PtrList<T>::getNumItems());
		need_sorting = 0;
	}

	T *enumItem(int n)
	{	// NOT const since we might call sort()
		if (auto_sort) sort();
		return PtrList<T>::enumItem(n);
	}
T *operator[](int n) { return PtrListSorted<T, C, S>::enumItem(n); }

	T *q(int n)
	{
		if (auto_sort) sort();
		return static_cast<T*>(PtrList<T>::getItemList()[n]);
	}

	T *findItem(const wchar_t *attrib, int *pos = NULL)
	{
		ASSERTPR(!(!auto_sort && need_sorting), "must call sort() first if auto-sorting is disabled");
		sort();
#if 1	// do binary search
		if (PtrList<T>::getNumItems() == 0) return NULL;

		int bot = 0, top = PtrList<T>::getNumItems() - 1, mid;

		for (int c = 0; c < PtrList<T>::getNumItems() + 1; c++)
		{
			if (bot > top) return NULL;
			mid = (bot + top) / 2;
			int r = C::compareAttrib(attrib, PtrList<T>::getItemList()[mid]);
			if (r == 0)
			{
				if (pos != NULL) *pos = mid;
				return PtrList<T>::getItemList()[mid];
			}
			if (r < 0)
			{
				top = mid - 1;
			}
			else
			{
				bot = mid + 1;
			}
		}
		ASSERTPR(0, "binary search fucked up");
#else
		// re-enable this in case of fuckup
		for (int i = 0; i < nitems; i++)
		{
			if (C::compareAttrib(attrib, static_cast<T *>(items[i])) == 0)
				return static_cast<T *>items[i];
		}
#endif
		return NULL;
	}

	T *findItem(T *attrib, int *pos = NULL)
	{
		return findItem((const wchar_t *)attrib, pos);
	}

	int beginEnumDups(const char *attrib)
	{
		int pos;
		findItem(attrib, &pos);
		
		if (pos < 0)
			return -1;
		
		dups_hi = pos;
		dups_low = pos;

		int i;
		for (i = pos - 1;i >= 0;i--)
		{
			if (C::compareAttrib(attrib, static_cast<T *>(enumItem(i))) == 0)
				break;
			
			dups_low = i;
		}
		
		for (i = pos + 1;i < PtrList<T>::getNumItems();i++)
		{
			if (C::compareAttrib(attrib, static_cast<T *>(enumItem(i))) == 0)
				break;
			
			dups_hi = i;
		}
		
		dups_pos = dups_low;
		
		return dups_pos;
	}
	
	int getNextDup()
	{ // returns -1 when done
		if (dups_pos >= dups_hi)
			return -1;
		
		return ++dups_pos;
	}

#if 0
	// replace search with binary search
	int searchItem(T *item) const
	{
		ASSERTPR(!(!auto_sort && need_sorting), "must call sort() first if auto-sorting is disabled");
		sort();
		return bsearchItem(item);
	}
#endif

	void setAutoSort(bool as)                        { auto_sort = as; }
	bool getAutoSort() const                         { return auto_sort; }

	void removeDups()
	{
		ASSERTPR(!(!auto_sort && need_sorting), "must call sort() first if auto-sorting is disabled");
		sort();
		for (int i = 1; i < PtrList<T>::getNumItems(); i++)
		{
			if (C::compareItem(enumItem(i - 1), enumItem(i)) == 0)
			{
				PtrList<T>::delByPos(i);
				i--;
			}
		}
	}

private:
	int need_sorting;
	bool auto_sort;
	int dups_low, dups_hi, dups_pos;
};

// quicksort -- you still need to override the compare fns
template <class T, class C>
class QuickSorted
{
public:
	static void _sort(T **items, int nitems)
	{
		if (items == NULL || nitems <= 1)
			return ;
		
		Qsort(items, 0, nitems - 1);
	}

private:
	static void swapItem(T **items, int a, int b)
	{ // no bounds checking!
		T *tmp = items[a];
		items[a] = items[b];
		items[b] = tmp;
	}

	static void Qsort(T **items, int lo0, int hi0)
	{
		int lo = lo0, hi = hi0;
		if (hi0 > lo0)
		{
			T *mid = items[(lo0 + hi0) / 2];
			while (lo <= hi)
			{
				while ((lo < hi0) && (C::compareItem(items[lo], mid) < 0))
					lo++;
				
				while ((hi > lo0) && (C::compareItem(items[hi], mid) > 0))
					hi--;

				if (lo <= hi)
				{
					swapItem(items, lo, hi);
					lo++;
					hi--;
				}
			}

			if (lo0 < hi)
				Qsort(items, lo0, hi);
			
			if (lo < hi0)
				Qsort(items, lo, hi0);
		}
	}
};

// easy way to specify quicksorting, just data type and comparison class
template <class T, class C> class PtrListQuickSorted : public PtrListSorted<T, C, QuickSorted<T, C> >
{
public:
	PtrListQuickSorted(int initial_size = 0) : PtrListSorted<T, C, QuickSorted<T, C> >(initial_size) {}
};


// easy way to get a list sorted by pointer val
class SortByPtrVal
{
public:
	static int compareItem(void *p1, void *p2)                     { return CMP3(p1, p2); }
	static int compareAttrib(const wchar_t *attrib, void *item)    { return CMP3((void *)attrib, item); }
};

template <class T> class PtrListQuickSortedByPtrVal : public PtrListQuickSorted<T, SortByPtrVal > {};


// this class automatically inserts at the correct position, so
// the binary searches are very fast if you need to insert and search often (no need to sort)
template < class T, class C >
class PtrListInsertSorted : public PtrList<T>
{
public:
	PtrListInsertSorted() : last_insert_pos(0) { disable_sort = 0; }
	
	T *addItem(T *item)
	{
		int numItems = PtrList<T>::getNumItems();
		if (numItems == 0)
		{
			last_insert_pos = 0;
			
			return PtrList<T>::addItem(item);
		}

		int insertpoint = -1;
		if (!disable_sort)
		{
			int bot = 0, top = numItems - 1, mid;
			// benski>
			// optimization based on profiler info.  Too many string compares!
			// Most of the use of this comes from GuiObjectWnd's constructor (and derived classes)
			// so I've changed GuiObjectWnd to add things in alphabetical order.
			// Before we start the binary search, we'll check the new item against the LAST item inserted
			// Most of the time, we'll finish the insert in O(1)
			// Even if we fail, we mitigate the loss somewhat by limiting the binary search

			if (last_insert_pos >= numItems) // the list may have shrunk since last time
				last_insert_pos = numItems - 1;
				
			int quickTest = C::compareItem(item, PtrList<T>::getItemList()[last_insert_pos]);

			if (quickTest == 0) // right on the money.. we'll go ahead and insert ourselves next
				return PtrList<T>::addItem(item, last_insert_pos);

			if (quickTest > 0) // ok we go after the last inserted item (good), but we need to make sure we go before the next one
			{
				last_insert_pos++;
				
				if (last_insert_pos == numItems) // we're at the end? cool...
					return PtrList<T>::addItem(item, PTRLIST_POS_LAST);

				quickTest = C::compareItem(item, PtrList<T>::getItemList()[last_insert_pos]); // test against the next item
				
				if (quickTest <= 0) // and we're not bigger than the next one... perfect!
					return PtrList<T>::addItem(item, last_insert_pos);
				else // too bad
					bot = last_insert_pos; // help out the binary search ... We're at least bigger than everything before last_insert_pos
			}
			else // ok our optimization failed, but we can still help out the binary search
				top = last_insert_pos - 1; // we're at least smaller than everything before last_insert_pos

			// end optimization code


			for (int c = 0; c < numItems + 1; c++)
			{
				if (bot > top)
				{
					// insert here
					insertpoint = bot;
					break;
				}
				mid = (bot + top) / 2;
				int r = C::compareItem(item, PtrList<T>::getItemList()[mid]);
				
				if (r == 0)
				{
					// insert here
					insertpoint = mid;
					break;
				}
				
				if (r < 0)
				{
					top = mid - 1;
				}
				else
				{
					bot = mid + 1;
				}
			}
			last_insert_pos = insertpoint;
			ASSERTPR(insertpoint != -1, "insertsort/binary search fucked up");
		}
		else // no sorting
		{
			last_insert_pos = numItems;
			insertpoint = PTRLIST_POS_LAST;
		}

		return PtrList<T>::addItem(item, insertpoint);
	}
	T *getInsertionPoint(T *item, int *pos)
	{
		if (PtrList<T>::getNumItems() == 0)
		{ 
			if (pos)
				*pos = 0;

			return NULL; 
		}

		int bot = 0, top = PtrList<T>::getNumItems() - 1, mid;

		int insertpoint = -1;
		if (!disable_sort )
		{
			for (int c = 0; c < PtrList<T>::getNumItems() + 1; c++)
			{
				if (bot > top)
				{
					// insert here
					insertpoint = bot;
					break;
				}
				mid = (bot + top) / 2;
				int r = C::compareItem(item, PtrList<T>::getItemList()[mid]);
				
				if (r == 0)
				{
					// insert here
					insertpoint = mid;
					break;
				}
				
				if (r < 0)
				{
					top = mid - 1;
				}
				else
				{
					bot = mid + 1;
				}
			}
			
			ASSERTPR(insertpoint != -1, "insertsort/binary search fucked up");
		}
		else
			insertpoint = PTRLIST_POS_LAST;
		
		if (pos)
			*pos = insertpoint;
		
		return PtrList<T>::enumItem(insertpoint);
	}
	T *findItem(const wchar_t *attrib, int *pos = NULL)
	{
		if (isSorted())
		{
			// binary search
			if (PtrList<T>::getNumItems() == 0)
				return NULL;

			int bot = 0, top = PtrList<T>::getNumItems() - 1, mid;

			for (int c = 0; c < PtrList<T>::getNumItems() + 1; c++)
			{
				if (bot > top)
					return NULL;
				
				mid = (bot + top) / 2;
				int r = C::compareAttrib(attrib, PtrList<T>::getItemList()[mid]);
				
				if (r == 0)
				{
					if (pos != NULL)
						*pos = mid;
					
					return PtrList<T>::getItemList()[mid];
				}
				
				if (r < 0)
				{
					top = mid - 1;
				}
				else
				{
					bot = mid + 1;
				}
			}
			ASSERTPR(0, "binary search fucked up");
		}
		else
		{
			// linear search
			for (int i = 0; i < PtrList<T>::getNumItems(); i++)
			{
				int r = C::compareAttrib(attrib, PtrList<T>::getItemList()[i]);
				if (r == 0)
				{
					if (pos != NULL)
						*pos = i;
					
					return PtrList<T>::getItemList()[i];
				}
			}
		}
		
		return NULL;
	}
	T *findItem(T *attrib, int *pos = NULL)          { return findItem((const wchar_t *)attrib, pos); }
	void setSorted(int dosort)                       { disable_sort = !dosort; }
	int isSorted()                                   { return !disable_sort; }
	int disable_sort;

	int last_insert_pos;
};

// this list allows you to have multiple items with same attrib and adds findLastItem so you can
// sort on more than just one item. this can be used to make autosorting lists of overriding items
// which you can add and remove at will.
template <class T, class C> class PtrListQuickMultiSorted : public PtrListQuickSorted<T, C>
{
public:
	PtrListQuickMultiSorted(int initial_size = 0) : PtrListQuickSorted<T, C>(initial_size) {}
	T *findLastItem(const wchar_t *attrib, int *pos = NULL)
	{
		PtrListQuickSorted<T, C>::sort();
		int p = 0;
		int fp = 0;
		T *item = PtrListQuickSorted<T, C>::findItem(attrib, &fp);
		
		if (!item)
			return NULL;

		p = fp;

		for(;;)
		{
			p++;
			
			if (p >= PtrListQuickSorted<T, C>::getNumItems())
				break;
			
			T* i = PtrListQuickSorted<T, C>::enumItem(p);
			
			if (!C::compareAttrib(attrib, i))
			{
				fp = p;
				item = i;
			}
			else
				break;
		}

		if (pos != NULL)
			*pos = fp;
		
		return item;
	}
};
//same thing but Insert sorted. use this one if you insert and search items often (no need to sort on findItem)
template <class T, class C> class PtrListInsertMultiSorted : public PtrListInsertSorted<T, C>
{
public:
	PtrListInsertMultiSorted() : PtrListInsertSorted<T, C>() {}
	T *findLastItem(const wchar_t *attrib, int *pos = NULL)
	{
		//sort();
		int p = 0;
		int fp = 0;
		T *item = PtrListInsertSorted<T, C>::findItem(attrib, &fp);
		
		if (!item)
			return NULL;

		p = fp;

		for (;;)
		{
			p++;
			
			if (p >= PtrListInsertSorted<T, C>::getNumItems())
				break;
			
			T* i = PtrListInsertSorted<T, C>::enumItem(p);
			
			if (!C::compareAttrib(attrib, i))
			{
				fp = p;
				item = i;
			}
			else
				break;
		}

		if (pos != NULL)
			*pos = fp;
		
		return item;
	}
};


#include <bfc/foreach.h>

#endif