/*
	SuperCollider real time audio synthesis system
    Copyright (c) 2002 James McCartney. All rights reserved.
	http://www.audiosynth.com

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
*/
/*

The garbage collector for SuperCollider.
Based on Wilson and Johnstone's real time collector and the Baker treadmill.

*/

#pragma once

#include "PyrObject.h"
#include "VMGlobals.h"
#include "AdvancingAllocPool.h"
#include "function_attributes.h"

void DumpSimpleBackTrace(VMGlobals *g);

const int kMaxPoolSet = 7;
const int kNumGCSizeClasses = 28;
const int kFinalizerSet = kNumGCSizeClasses;
const int kNumGCSets = kNumGCSizeClasses + 1;
const int kScanThreshold =  256;


class GCSet
{
public:
	GCSet() {}
	void Init(int inSizeClass);

	bool HasFree() { return mFree != &mBlack; }

private:
	friend class PyrGC;

	void MajorFlip();
	void MinorFlip();

	PyrObjectHdr mBlack;
	PyrObjectHdr mWhite;
	PyrObjectHdr* mFree;
};

struct SlotRef {
	SlotRef(PyrObject* inObj, int32 inIndex) : obj(inObj), slotIndex(inIndex) {}

	PyrObject *obj;
	int32 slotIndex;
};

class PyrGC
{
	static const int kLazyCollectThreshold = 1024;

public:
	PyrGC(VMGlobals *g, AllocPool *inPool, PyrClass *mainProcessClass, long poolSize);

	MALLOC PyrObject* New(size_t inNumBytes, long inFlags, long inFormat, bool inCollect);
	MALLOC PyrObject* NewFrame(size_t inNumBytes, long inFlags, long inFormat, bool inAccount);

	MALLOC static PyrObject* NewPermanent(size_t inNumBytes,
						long inFlags, long inFormat);

	MALLOC PyrObject* NewFinalizer(ObjFuncPtr finalizeFunc, PyrObject *inObject, bool inCollect);

	bool IsBlack(PyrObjectHdr* inObj)  { return inObj->gc_color == mBlackColor; }
	bool IsWhite(PyrObjectHdr* inObj)  { return inObj->gc_color == mWhiteColor; }
	bool IsGrey(PyrObjectHdr* inObj)   { return inObj->gc_color == mGreyColor; }
	static bool IsMarker(PyrObjectHdr* inObj) { return inObj->gc_color == obj_gcmarker; }
	bool IsFree(PyrObjectHdr* inObj)   { return (!(IsMarker(inObj) ||
													inObj->IsPermanent() ||
													IsBlack(inObj) ||
													IsWhite(inObj) ||
													IsGrey(inObj))); }

	bool ObjIsBlack(PyrObjectHdr* inObj) { return IsBlack(inObj); }
	bool ObjIsGrey(PyrObjectHdr* inObj) { return IsGrey(inObj); }
	bool ObjIsFree(PyrObjectHdr* inObj) { return IsFree(inObj); }


	// general purpose write barriers:
	void GCWrite(PyrObjectHdr* inParent, PyrSlot* inSlot)
		{
			if (IsBlack(inParent) && IsObj(inSlot) && IsWhite(slotRawObject(inSlot))) {
				ToGrey(slotRawObject(inSlot));
			}
		}
	void GCWrite(PyrObjectHdr* inParent, PyrObjectHdr* inChild)
		{
			if (IsBlack(inParent) && IsWhite(inChild)) {
				ToGrey(inChild);
			}
		}
	// when you know the parent is black:
	void GCWriteBlack(PyrSlot* inSlot)
		{
			if (IsObj(inSlot)) {
				if (IsWhite(slotRawObject(inSlot))) {
					ToGrey(slotRawObject(inSlot));
				}
			}
		}
	void GCWriteBlack(PyrObjectHdr* inChild)
		{
			if (IsWhite(inChild)) {
				ToGrey(inChild);
			}
		}
	// when you know the child is white
	void GCWriteNew(PyrObjectHdr* inParent, PyrObjectHdr* inChild)
		{
			assert(IsWhite(inChild));
			if (IsBlack(inParent)) {
				ToGrey(inChild);
			}
		}

// users should not call anything below.

	void Collect();
	void Collect(int32 inNumToScan);
	void LazyCollect()
	{
		if (mUncollectedAllocations > kLazyCollectThreshold)
			Collect();
	}
	void FullCollection();
	void ScanFinalizers();
	void RunAllFinalizers();
	GCSet* GetGCSet(PyrObjectHdr* inObj);
	void CompletePartialScan(PyrObject *obj);

	inline void ToGrey(PyrObjectHdr* inObj);
	inline void ToGrey2(PyrObjectHdr* inObj);
	void ToBlack(PyrObjectHdr* inObj);
	void ToWhite(PyrObjectHdr *inObj);
	void Free(PyrObjectHdr* inObj);


	long StackDepth() { return mVMGlobals->sp - mStack->slots + 1; }
	PyrObject* Stack() { return mStack; }
	void SetStack(PyrObject* inStack) { mStack = inStack; }

	bool SanityCheck();
	bool SanityCheck2();
	bool LinkSanity();
	bool ListSanity();
	bool BlackToWhiteCheck(PyrObject *objA);
	bool SanityMarkObj(PyrObject *objA, PyrObject *fromObj, int level);
	bool SanityClearObj(PyrObject *objA, int level);
	void DumpInfo();
	void DumpGrey();
	void DumpSet(int set);

	void BecomePermanent(PyrObject *inObject);
	void BecomeImmutable(PyrObject *inObject);

	bool IsPartialScanObject(PyrObject* inObject) const { return inObject == mPartialScanObj; }
	int32 GetPartialScanIndex() const { return mPartialScanSlot; }

private:
	inline PyrObject * Allocate(size_t inNumBytes, int32 sizeclass, bool inCollect);
	static void throwMemfailed(size_t inNumBytes);

	void ScanSlots(PyrSlot *inSlots, long inNumToScan);
	void SweepBigObjects();
	void DoPartialScan(int32 inObjSize);
	bool ScanOneObj();
	void Flip();
	void ScanStack();
	void ScanFrames();
	void DLRemove(PyrObjectHdr *obj);
	void DLInsertAfter(PyrObjectHdr *after, PyrObjectHdr *obj);
	void DLInsertBefore(PyrObjectHdr *before, PyrObjectHdr *obj);

	void ClearMarks();
	void Finalize(PyrObject *obj);

	void beginPause();
	void endPause();
	void reportPause();

	VMGlobals *mVMGlobals;
	AllocPool *mPool;
	AdvancingAllocPool mNewPool;
	GCSet mSets[kNumGCSets];
	PyrProcess *mProcess; // the root is the pyrprocess which contains this struct
	PyrObject *mStack;
	PyrObject *mPartialScanObj;
	PyrObjectHdr mGrey;

	int32 mPartialScanSlot;
	int32 mNumToScan;
	int32 mNumGrey;

	int32 mFlips, mCollects, mAllocTotal, mScans, mNumAllocs, mStackScans, mNumPartialScans, mSlotsScanned, mUncollectedAllocations;

	unsigned char mBlackColor, mGreyColor, mWhiteColor, mFreeColor;
	bool mCanSweep;
	bool mRunning;
};

inline void PyrGC::DLRemove(PyrObjectHdr *obj)
{
	obj->next->prev = obj->prev;
	obj->prev->next = obj->next;
}

inline void PyrGC::DLInsertAfter(PyrObjectHdr *after, PyrObjectHdr *obj)
{
	obj->next = after->next;
	obj->prev = after;
	after->next->prev = obj;
	after->next = obj;
}

inline void PyrGC::DLInsertBefore(PyrObjectHdr *before, PyrObjectHdr *obj)
{
	obj->prev = before->prev;
	obj->next = before;
	before->prev->next = obj;
	before->prev = obj;
}

inline GCSet* PyrGC::GetGCSet(PyrObjectHdr* inObj)
{
	return mSets + (inObj->classptr == class_finalizer ? kFinalizerSet : inObj->obj_sizeclass);
}

inline void PyrGC::ToBlack(PyrObjectHdr *obj)
{
	if (IsGrey(obj)) {
		mNumGrey--;
		//post("ToBlack %d\n", mNumGrey);
	}

	DLRemove(obj);

	GCSet *gcs = GetGCSet(obj);
	DLInsertAfter(&gcs->mBlack, obj);

	obj->gc_color = mBlackColor;
}

inline void PyrGC::ToWhite(PyrObjectHdr *obj)
{
	if (IsGrey(obj)) {
		mNumGrey--;
		//post("ToWhite %d\n", mNumGrey);
	}

	DLRemove(obj);

	GCSet *gcs = GetGCSet(obj);
	DLInsertAfter(&gcs->mWhite, obj);

	obj->gc_color = mWhiteColor;
}

inline void PyrGC::Free(PyrObjectHdr* obj)
{
	if (IsGrey(obj)) {
		mNumGrey--;
		//post("ToWhite %d\n", mNumGrey);
	}

	DLRemove(obj);

	GCSet *gcs = GetGCSet(obj);
	DLInsertBefore(gcs->mFree, obj);
	gcs->mFree = obj;

	obj->gc_color = mFreeColor;
	obj->size = 0;
}

inline void PyrGC::ToGrey(PyrObjectHdr* obj)
{
	/* move obj from white to grey */
	/* link around object */
	DLRemove(obj);

	/* link in new place */
	DLInsertAfter(&mGrey, obj);

	/* set grey list pointer to obj */
	obj->gc_color = mGreyColor;
	mNumGrey ++ ;
	mNumToScan += 1L << obj->obj_sizeclass;
}

inline void PyrGC::ToGrey2(PyrObjectHdr* obj)
{
	/* move obj from white to grey */
	/* link around object */
	DLRemove(obj);

	/* link in new place */
	DLInsertAfter(&mGrey, obj);

	/* set grey list pointer to obj */
	obj->gc_color = mGreyColor;
	mNumGrey ++ ;
}

inline PyrObject * PyrGC::Allocate(size_t inNumBytes, int32 sizeclass, bool inRunCollection)
{
	if (inRunCollection && mNumToScan >= kScanThreshold)
		Collect();
	else {
		if (inRunCollection)
			mUncollectedAllocations = 0;
		else
			++mUncollectedAllocations;
	}

	GCSet *gcs = mSets + sizeclass;

	PyrObject * obj = (PyrObject*)gcs->mFree;
	if (!IsMarker(obj)) {
		// from free list
		gcs->mFree = obj->next;
		assert(obj->obj_sizeclass == sizeclass);
	} else {
		if (sizeclass > kMaxPoolSet) {
			SweepBigObjects();
			size_t allocSize = sizeof(PyrObjectHdr) + (sizeof(PyrSlot) << sizeclass);
			obj = (PyrObject*)mPool->Alloc(allocSize);
		} else {
			size_t allocSize = sizeof(PyrObjectHdr) + (sizeof(PyrSlot) << sizeclass);
			obj = (PyrObject*)mNewPool.Alloc(allocSize);
		}
		if (!obj)
			throwMemfailed(inNumBytes);
		DLInsertAfter(&gcs->mWhite, obj);
		obj->obj_sizeclass = sizeclass;
	}
	return obj;
}
