/* 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/. */

#ifndef DOM_FS_SHARED_FILESYSTEMHELPERS_H_
#define DOM_FS_SHARED_FILESYSTEMHELPERS_H_

#include "FileSystemTypes.h"
#include "mozilla/RefPtr.h"

namespace mozilla::dom::fs {

// XXX Consider moving this class template to MFBT.

// A wrapper class template on top of the RefPtr. The RefPtr provides us the
// automatic reference counting of objects with AddRef() and Release() methods.
// `Registered` provides automatic registration counting of objects with
// Register() and Unregister() methods. Registration counting works similarly
// as reference counting, but objects are not deleted when the number of
// registrations drops to zero (that's managed by reference counting). Instead,
// an object can trigger an asynchronous close operation which still needs to
// hold and use the referenced object. Example:
//
// using BoolPromise = MozPromise<bool, nsresult, false>;
//
// class MyObject {
//  public:
//   NS_INLINE_DECL_REFCOUNTING(MyObject)
//
//   void Register() {
//     mRegCnt++;
//   }
//
//   void Unregister() {
//     mRegCnt--;
//     if (mRegCnt == 0) {
//       BeginClose();
//     }
//   }
//
//  private:
//   RefPtr<BoolPromise> BeginClose() {
//     return InvokeAsync(mIOTaskQueue, __func__,
//                []() {
//                  return BoolPromise::CreateAndResolve(true, __func__);
//                })
//         ->Then(GetCurrentSerialEventTarget(), __func__,
//                [self = RefPtr<MyObject>(this)](
//                    const BoolPromise::ResolveOrRejectValue&) {
//                  return self->mIOTaskQueue->BeginShutdown();
//                })
//         ->Then(GetCurrentSerialEventTarget(), __func__,
//                [self = RefPtr<MyObject>(this)](
//                    const ShutdownPromise::ResolveOrRejectValue&) {
//                  return BoolPromise::CreateAndResolve(true, __func__);
//                });
//   }
//
//   RefPtr<TaskQueue> mIOTaskQueue;
//   uint32_t mRegCnt = 0;
// };

template <class T>
class Registered {
 private:
  RefPtr<T> mObject;

 public:
  ~Registered() {
    if (mObject) {
      mObject->Unregister();
    }
  }

  Registered() = default;

  Registered(const Registered& aOther) : mObject(aOther.mObject) {
    mObject->Register();
  }

  Registered(Registered&& aOther) noexcept = default;

  MOZ_IMPLICIT Registered(RefPtr<T> aObject) : mObject(std::move(aObject)) {
    if (mObject) {
      mObject->Register();
    }
  }

  Registered<T>& operator=(decltype(nullptr)) {
    RefPtr<T> oldObject = std::move(mObject);
    mObject = nullptr;
    if (oldObject) {
      oldObject->Unregister();
    }
    return *this;
  }

  Registered<T>& operator=(const Registered<T>& aRhs) {
    if (aRhs.mObject) {
      aRhs.mObject->Register();
    }
    RefPtr<T> oldObject = std::move(mObject);
    mObject = aRhs.mObject;
    if (oldObject) {
      oldObject->Unregister();
    }
    return *this;
  }

  Registered<T>& operator=(Registered<T>&& aRhs) noexcept {
    RefPtr<T> oldObject = std::move(mObject);
    mObject = std::move(aRhs.mObject);
    aRhs.mObject = nullptr;
    if (oldObject) {
      oldObject->Unregister();
    }
    return *this;
  }

  const RefPtr<T>& inspect() const { return mObject; }

  RefPtr<T> unwrap() {
    RefPtr<T> oldObject = std::move(mObject);
    mObject = nullptr;
    if (oldObject) {
      oldObject->Unregister();
    }
    return oldObject;
  }

  T* get() const { return mObject; }

  operator T*() const& { return get(); }

  T* operator->() const { return get(); }
};

// Spec says valid names don't include (os-dependent) path separators,
// and is not equal to a dot . or two dots ..
// We want to use the same validator from both child and parent.
bool IsValidName(const fs::Name& aName);

}  // namespace mozilla::dom::fs

#endif  // DOM_FS_SHARED_FILESYSTEMHELPERS_H_
