/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#pragma once

/*
 * Wrapper - Helper class for wrapper objects.
 *
 * This helps to construct a shared_ptr object which wraps access to an underlying handle.
 * (The handle could be a pointer to some low-level type, a conventional C handle, an int ID, a GUID, etc.)
 *
 * Usage:
 *   To obtain a FooPtr from a foo_handle_t, call FooPtr Foo::wrap(foo_handle_t);
 *
 * To implement Foo using Wrapper, Foo needs to include this macro in its class definition:
 *   CSF_DECLARE_WRAP(Foo, foo_handle_t);
 * It also needs to include this in the cpp file, to provide the wrap() implementation and define the static Wrapper.
 *   CSF_IMPLEMENT_WRAP(Foo, foo_handle_t);
 * These are all declared in common/Wrapper.h - Foo.h needs to include this too.
 * The client needs to declare Foo(foo_handle_t) as private, and provide a suitable implementation, as well as
 *   implementing wrappers for any other functions to be exposed.
 * The client needs to implement ~Foo() to perform any cleanup as usual.
 *
 * wrap() will always return the same FooPtr for a given foo_handle_t, it will not construct additional objects
 *        if a suitable one already exists.
 * changeHandle() is used in rare cases where the underlying handle is changed, but the wrapper object is intended
 *                to remain.  This is the case for the "fake" CC_DPCall generated on CC_DPLine::CreateCall(), where
 *                the correct IDPCall* is provided later.
 * reset() is a cleanup step to wipe the handle map and allow memory to be reclaimed.
 *
 * Future enhancements:
 * - For now, objects remain in the map forever.  Better would be to add a releaseHandle() function which would
 *   allow the map to be emptied as underlying handles expired.  While we can't force the client to give up its
 *   shared_ptr<Foo> objects, we can remove our own copy, for instance on a call ended event.
 */

#include <map>
#include "prlock.h"
#include "mozilla/Assertions.h"

/*
 * Wrapper has its own autolock class because the instances are declared
 * statically and mozilla::Mutex will not work properly when instantiated
 * in a static constructor.
 */

class LockNSPR {
public:
  LockNSPR() : lock_(nullptr) {
    lock_ = PR_NewLock();
    MOZ_ASSERT(lock_);
  }
  ~LockNSPR() {
    PR_DestroyLock(lock_);
  }

  void Acquire() {
    PR_Lock(lock_);
  }

  void Release() {
    PR_Unlock(lock_);
  }

private:
  PRLock *lock_;
};

class AutoLockNSPR {
public:
  explicit AutoLockNSPR(LockNSPR& lock) : lock_(lock) {
    lock_.Acquire();
  }
  ~AutoLockNSPR() {
    lock_.Release();
  }

private:
  LockNSPR& lock_;
};

template <class T>
class Wrapper
{
private:
    typedef std::map<typename T::Handle, typename T::Ptr>      	HandleMapType;
    HandleMapType handleMap;
    LockNSPR handleMapMutex;

public:
	Wrapper() {}

	typename T::Ptr wrap(typename T::Handle handle)
	{
		AutoLockNSPR lock(handleMapMutex);
		typename HandleMapType::iterator it = handleMap.find(handle);
		if(it != handleMap.end())
		{
			return it->second;
		}
		else
		{
			typename T::Ptr p(new T(handle));
			handleMap[handle] = p;
			return p;
		}
	}

	bool changeHandle(typename T::Handle oldHandle, typename T::Handle newHandle)
	{
		AutoLockNSPR lock(handleMapMutex);
		typename HandleMapType::iterator it = handleMap.find(oldHandle);
		if(it != handleMap.end())
		{
			typename T::Ptr p = it->second;
			handleMap.erase(it);
			handleMap[newHandle] = p;
			return true;
		}
		else
		{
			return false;
		}
	}

	bool release(typename T::Handle handle)
	{
		AutoLockNSPR lock(handleMapMutex);
		typename HandleMapType::iterator it = handleMap.find(handle);
		if(it != handleMap.end())
		{
			handleMap.erase(it);
			return true;
		}
		else
		{
			return false;
		}
	}

	void reset()
	{
		AutoLockNSPR lock(handleMapMutex);
		handleMap.clear();
	}
};

#define CSF_DECLARE_WRAP(classname, handletype) \
	public: \
		static classname ## Ptr wrap(handletype handle); \
		static void reset(); \
                static void release(handletype handle); \
	private: \
		friend class Wrapper<classname>; \
		typedef classname ## Ptr Ptr; \
		typedef handletype Handle; \
		static Wrapper<classname>& getWrapper() { \
			static Wrapper<classname> wrapper; \
			return wrapper; \
		}

#define CSF_IMPLEMENT_WRAP(classname, handletype) \
	classname ## Ptr classname::wrap(handletype handle) \
	{ \
		return getWrapper().wrap(handle); \
	} \
	void classname::reset() \
	{ \
		getWrapper().reset(); \
	} \
        void classname::release(handletype handle) \
        { \
                getWrapper().release(handle); \
        }
