#include "JSONNode.h"
#include "JSONGlobals.h"

#ifdef JSON_MUTEX_CALLBACKS

json_mutex_callback_t json_lock_callback = 0;
json_mutex_callback_t json_unlock_callback = 0;
void * global_mutex = 0;
void * manager_mutex = 0;

struct AutoLock {
public:
	LIBJSON_OBJECT(AutoLock);
    AutoLock(void) json_nothrow {
		LIBJSON_CTOR;
	   json_lock_callback(manager_mutex);
    }
    ~AutoLock(void) json_nothrow {
		LIBJSON_DTOR;
	   json_unlock_callback(manager_mutex);
    }
private:
    AutoLock(const AutoLock &);
    AutoLock & operator = (const AutoLock &);
};

#ifdef JSON_MUTEX_MANAGE
    json_mutex_callback_t json_destroy = 0;

    //make sure that the global mutex is taken care of too
    struct auto_global {
    public:
		LIBJSON_OBJECT(auto_global;)
	   auto_global(void) json_nothrow { LIBJSON_CTOR; }
	   ~auto_global(void) json_nothrow {
		  LIBJSON_DTOR;
		  if (global_mutex){
			 JSON_ASSERT_SAFE(json_destroy != 0, JSON_TEXT("No json_destroy in mutex managed mode"), return;);
			 json_destroy(global_mutex);
		  }
	   }
    private:
        auto_global(const auto_global &);
        auto_global & operator = (const auto_global &);
    };
    auto_global cleanupGlobal;
#endif

void JSONNode::register_mutex_callbacks(json_mutex_callback_t lock, json_mutex_callback_t unlock, void * manager_lock) json_nothrow {
    json_lock_callback = lock;
    json_unlock_callback = unlock;
    manager_mutex = manager_lock;
}

void JSONNode::set_global_mutex(void * mutex) json_nothrow {
    global_mutex = mutex;
}

void JSONNode::set_mutex(void * mutex) json_nothrow {
    makeUniqueInternal();
    internal -> _set_mutex(mutex);
}

void * JSONNode::getThisLock(JSONNode * pthis) json_nothrow {
    if (pthis -> internal -> mylock != 0){
	   return pthis -> internal -> mylock;
    }
    JSON_ASSERT(global_mutex != 0, JSON_TEXT("No global_mutex"));  //this is safe, because it's just goingi to return 0 anyway
    return global_mutex;
}

void JSONNode::lock(int thread) json_nothrow {
    JSON_ASSERT_SAFE(json_lock_callback != 0, JSON_TEXT("No locking callback"), return;);

    AutoLock lockControl;

    //first, figure out what needs to be locked
    void * thislock = getThisLock(this);
    #ifdef JSON_SAFE
	   if (json_unlikely(thislock == 0)) return;
    #endif

    //make sure that the same thread isn't locking it more than once (possible due to complex ref counting)
    JSON_MAP(int, JSON_MAP(void *, unsigned int) )::iterator it = json_global(THREAD_LOCKS).find(thread);
    if (it == json_global(THREAD_LOCKS).end()){
		JSON_MAP(void *, unsigned int) newthread;
		newthread[thislock] = 1;
		json_global(THREAD_LOCKS).insert(std::pair<int, JSON_MAP(void *, unsigned int) >(thread, newthread));
    } else {  //this thread already has some things locked, check if the current mutex is
		JSON_MAP(void *, unsigned int) & newthread = it -> second;
		JSON_MAP(void *, unsigned int)::iterator locker(newthread.find(thislock));
		if (locker == newthread.end()){  //current mutex is not locked, set it to locked
			newthread.insert(std::pair<void *, unsigned int>(thislock, 1));
		} else {  //it's already locked, don't relock it
			++(locker -> second);
			return;  //don't try to relock, it will deadlock the program
		}
    }

    //if I need to, lock it
    json_lock_callback(thislock);
}

void JSONNode::unlock(int thread) json_nothrow{
    JSON_ASSERT_SAFE(json_unlock_callback != 0, JSON_TEXT("No unlocking callback"), return;);
	
    AutoLock lockControl;
	
    //first, figure out what needs to be locked
    void * thislock = getThisLock(this);
	#ifdef JSON_SAFE
		if (thislock == 0) return;
	#endif
	
    //get it out of the map
    JSON_MAP(int, JSON_MAP(void *, unsigned int) )::iterator it = json_global(THREAD_LOCKS).find(thread);
    JSON_ASSERT_SAFE(it != json_global(THREAD_LOCKS).end(), JSON_TEXT("thread unlocking something it didn't lock"), return;);
	
    //get the mutex out of the thread
    JSON_MAP(void *, unsigned int) & newthread = it -> second;
    JSON_MAP(void *, unsigned int)::iterator locker = newthread.find(thislock);
    JSON_ASSERT_SAFE(locker != newthread.end(), JSON_TEXT("thread unlocking mutex it didn't lock"), return;);
	
    //unlock it
    if (--(locker -> second)) return;  //other nodes is this same thread still have a lock on it
	
    //if I need to, unlock it
    newthread.erase(locker);
    json_unlock_callback(thislock);
	
}

#ifdef JSON_MUTEX_MANAGE
    void JSONNode::register_mutex_destructor(json_mutex_callback_t destroy) json_nothrow {
	   json_destroy = destroy;
    }
#endif


void internalJSONNode::_set_mutex(void * mutex, bool unset) json_nothrow {
    if (unset) _unset_mutex();  //for reference counting
    mylock = mutex;
    if (mutex != 0){
	   #ifdef JSON_MUTEX_MANAGE
		  JSON_MAP(void *, unsigned int)::iterator it = json_global(MUTEX_MANAGER).find(mutex);
		  if (it == json_global(MUTEX_MANAGER).end()){
			 json_global(MUTEX_MANAGER).insert(std::pair<void *, unsigned int>(mutex, 1));
		  } else {
			 ++it -> second;
		  }
	   #endif
	   if (isContainer()){
		  json_foreach(CHILDREN, myrunner){
			 (*myrunner) -> set_mutex(mutex);
		  }
	   }
    }
}

void internalJSONNode::_unset_mutex(void) json_nothrow {
    #ifdef JSON_MUTEX_MANAGE
	   if (mylock != 0){
		  JSON_MAP(void *, unsigned int)::iterator it = json_global(MUTEX_MANAGER).find(mylock);
		  JSON_ASSERT_SAFE(it != json_global(MUTEX_MANAGER).end(), JSON_TEXT("Mutex not managed"), return;);
		  --it -> second;
		  if (it -> second == 0){
			 JSON_ASSERT_SAFE(json_destroy, JSON_TEXT("You didn't register a destructor for mutexes"), return;);
			 json_global(MUTEX_MANAGER).erase(it);
		  }
	   }
    #endif
}

#ifdef JSON_DEBUG
    #ifndef JSON_LIBRARY
	   JSONNode internalJSONNode::DumpMutex(void) const json_nothrow {
		  JSONNode mut(JSON_NODE);
		  mut.set_name(JSON_TEXT("mylock"));
		  #ifdef JSON_MUTEX_MANAGE
			 if (mylock != 0){
				mut.push_back(JSON_NEW(JSONNode(JSON_TEXT("this"), (long)mylock)));
				JSON_MAP(void *, unsigned int)::iterator it = json_global(MUTEX_MANAGER).find(mylock);
				if (it == json_global(MUTEX_MANAGER).end()){
				    mut.push_back(JSON_NEW(JSONNode(JSON_TEXT("references"), JSON_TEXT("error"))));
				} else {
				    mut.push_back(JSON_NEW(JSONNode(JSON_TEXT("references"), it -> second)));
				}
			 } else {
				mut = (long)mylock;
			 }
		  #else
			 mut = (long)mylock;
		  #endif
		  return mut;
	   }
    #endif
#endif

#else
    #ifdef JSON_MUTEX_MANAGE
	   #error You can not have JSON_MUTEX_MANAGE on without JSON_MUTEX_CALLBACKS
    #endif
#endif
