/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * The contents of this file are subject to the Netscape Public
 * License Version 1.1 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.mozilla.org/NPL/
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * The Original Code is mozilla.org code.
 *
 * The Initial Developer of the Original Code is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation. All
 * Rights Reserved.
 *
 * Contributor(s): 
 */
#ifndef _EF_MONITORS
#define _EF_MONITORS

#include "JavaObject.h"
#include "HashTable.h"
#include "prtypes.h"
#include "pratom.h"
#include "prlock.h"
#include "prcvar.h"
#include "prthread.h"

struct PointerKeyOps {
  static bool equals(void* userKey, void* hashTableKey) {
    return (userKey == hashTableKey);
  }

  static void* copyKey(Pool &/*pool*/, void* userKey) {
    return userKey;
  }

  static Uint32 hashCode(void* userKey) {
    return reinterpret_cast<Uint32>(userKey);
  }
};

template<class N> 
class PointerHashTable : public HashTable<N, void*, PointerKeyOps> {
public:
  PointerHashTable(Pool &p) : HashTable<N, void*, PointerKeyOps>(p) { } 
};

struct MonitorArgStruct { // for debugging
	void (*f)(void*);
	void* arg;
};

class NS_EXTERN Monitor {
public:
	enum {
		KeySize=30,
		KeyShift=32-KeySize,
		KeyMask=0xFFFFFFFC, // '1...1100'
		LockMask=0x3,
		XHash=0x2,
		Waiting=0x2,  // w-flag when object is locked
		IHash=0x1,
		Unlocked=0x1
	};

	class Exception {};

	static inline bool cmpAndSwap(Int32* w, Int32 ov, Int32 nv);

	static inline Int32 exchange(Int32* w, Int32 v) {
		return (Int32)PR_AtomicSet((PRInt32*)w,(PRInt32)v);
	}

	static inline bool inStack(Int32 v);

	static inline Int32 pushStack();

	static inline Int32 popStack();

	static void threadInit();

	static void lock(JavaObject& o);

	static void unlock(JavaObject& o);

	static void wlock(JavaObject& o, Int32 sp);

	static void wait(JavaObject& o, Int64 tim);

	static inline void wait(JavaObject& o) {
		wait(o, PR_INTERVAL_NO_TIMEOUT);
	}

	static void notify(JavaObject& o);

	static void notifyAll(JavaObject& o);

	static void staticInit();

	static void destroy();

 	static inline void run(void* arg) { // for debugging
		MonitorArgStruct* mas = (MonitorArgStruct*)arg;
#if 0
		DWORD handler = (DWORD)NULL;//*win32HardwareThrow;
		__asm
		{
			push handler
			push fs:[0]
			mov fs:[0],esp
		}
		threadInit();
		mas->f(mas->arg);
		__asm {
			mov eax,[esp] // old fs:[0]
			mov fs:[0],eax
			add esp,8
		}
#else
		threadInit();
		mas->f(mas->arg);
#endif
	}

	static void hashWrite(JavaObject& obj, Int32 k);

	static Int32 hashRead(JavaObject& obj);

	static Int32 hashCode(JavaObject& obj);
};

struct MonitorObject {
	JavaObject* obj;
 	Int32 susp;
 	Int32 _wait;
 	PRLock* slock;
 	PRCondVar* svar;
 	PRLock* wlock;
 	PRCondVar* wvar;
public:
	class Exception {};
	MonitorObject* next;
	MonitorObject* prev;

 	MonitorObject() : obj(NULL), susp(0), _wait(0), next(NULL), prev(NULL) {
 		slock = PR_NewLock(); svar = PR_NewCondVar(slock);
 		wlock = PR_NewLock(); wvar = PR_NewCondVar(wlock);
	}

	~MonitorObject() { 
 		PR_DestroyLock(slock); PR_DestroyCondVar(svar);
 		PR_DestroyLock(wlock); PR_DestroyCondVar(wvar);
	}

 	inline void init(JavaObject& o) { 
		next=NULL; obj = &o; 
		susp = 0; _wait = 0;
	}

	inline bool empty() { 
		return _wait == 0 && susp == 0; 
	}

	inline bool isSuspended() { 
		return susp > 0; 
	}

	inline bool suspend() 
	{ 
		PRStatus stat;
		PR_Lock(slock);
		susp++; 
		if (susp<1) {
			PR_Unlock(slock);
			return true;
		}
		stat = PR_WaitCondVar(svar,PR_INTERVAL_NO_TIMEOUT); 
		if (stat == PR_FAILURE) {
			susp--;
			return false;
		}
		PR_Unlock(slock);
		return true;
	}

	inline void resume() 
	{ 
		PRStatus stat;
		PR_Lock(slock);
		susp--; 
		stat = PR_NotifyCondVar(svar);
		assert(stat != PR_FAILURE);
		PR_Unlock(slock);
	}

	inline bool wait(Int32 tim)
	{ 
		PRStatus stat;
		PR_Lock(wlock);
		_wait++; 
		if (_wait<1) {
			PR_Unlock(wlock);
			return true;
		}
		stat = PR_WaitCondVar(wvar,(tim == 0 ? PR_INTERVAL_NO_TIMEOUT : PR_MillisecondsToInterval(tim)));
		if (stat == PR_FAILURE) {
			_wait--;
			return false;
		}
		PR_Unlock(wlock);
		return true;
	}

	inline void notify() 
	{
		PRStatus stat;
		PR_Lock(wlock);
		_wait--; 
		stat = PR_NotifyCondVar(wvar); 
		assert(stat != PR_FAILURE);
		PR_Unlock(wlock);
	}

	inline void notifyAll() 
	{ 
		PR_Lock(wlock);
		_wait--;  // Is this right?
		PR_NotifyAllCondVar(wvar); 
		PR_Unlock(wlock);
	}
};

template<class M>
class Monitors {
private:
	PRUintn thr_stack; // temporary
	PRUintn thr_top;   // ditto
	PRLock* _lock;
	M* free;   // for allocation
	M* taken;  // for deallocation and cleanup
	M* newList(int l);
	Pool pool;
	PointerHashTable<M*>* table;
	enum { cacheSize=4 }; // must be even!!!
	Int32* keyCache[cacheSize];
	M* valCache[cacheSize];
	Int32* hashVec;
	int cacheNext;
	int stateNext;
	int hashNext;
	int hashVecTop;
	int hashVecSize;

	inline Int32* threadVec() {
		Int32* a;
		a = new(pool) Int32[256];
		for (int i=0; i<256; i++)
			a[i] = -1;	
		return a;
	}

	inline void threadInit() {
		PRStatus stat;
		
		stat = PR_SetThreadPrivate(thr_stack, (void *)threadVec());
		assert(stat != PR_FAILURE);
		stat = PR_SetThreadPrivate(thr_top, (void *)0);
		assert(stat != PR_FAILURE);
	}

	friend void Monitor::threadInit();

public:

	class Exception {};

	Monitors(int l);

	~Monitors();

	M* allocate(JavaObject& o);

	void deallocate(M* m);

	inline void lock() {
		PR_Lock(_lock);
	}

	inline void unlock() {
		PR_Unlock(_lock);
	}

	int enqueue(JavaObject& o);

	void dequeue(JavaObject& o, Int32 v);

	void wait(JavaObject& o, Int32 sp, Int32 tim);

	void notify(JavaObject& o);

	void notifyAll(JavaObject& o);

	M* getObject(JavaObject& o);

	void addObject(JavaObject& o, M* m);

	void removeObject(JavaObject& o);

	inline bool inStack(Int32 v);

	inline Int32 pushStack();

	inline Int32 popStack();
	
	Int32 hashGet(Int32 k);

	Int32 hashAdd(Int32 k);

	void hashRemove(Int32 k);

	Int32 nextHash();
};

extern Monitors<MonitorObject>* monitors;
#endif
