/* 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 "mozilla/Assertions.h"
#include "prlock.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); }
