// Copyright 2020 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
//    list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
//    this list of conditions and the following disclaimer in the documentation
//    and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
//    contributors may be used to endorse or promote products derived from
//    this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#ifndef SRC_DAWN_COMMON_NSREF_H_
#define SRC_DAWN_COMMON_NSREF_H_

#import <Foundation/NSObject.h>

#include "src/dawn/common/RefBase.h"

#if !defined(__OBJC__)
#error "NSRef can only be used in Objective C/C++ code."
#endif

#if __has_feature(objc_arc)
#error "Dawn expects to be built with ARC disabled."
#endif

namespace dawn {

// This file contains smart pointers that automatically reference and release Objective C objects
// and protocols in a manner very similar to Ref<>. Note that NSRef<> and NSPRef's constructor add
// a reference to the object by default, so the pattern to get a reference for a newly created
// NSObject is the following:
//
//    NSRef<NSFoo> foo = AcquireNSRef([NSFoo alloc]);
//
// NSRef overloads -> and * but these operators don't work extremely well with Objective C's
// features. For example automatic dereferencing when doing the following doesn't work:
//
//    NSFoo* foo;
//    foo.member = 1;
//    someVar = foo.member;
//
// Instead use the message passing syntax:
//
//    NSRef<NSFoo> foo;
//    [*foo setMember: 1];
//    someVar = [*foo member];
//
// Also did you notice the extra '*' in the example above? That's because Objective C's message
// passing doesn't automatically call a C++ operator to dereference smart pointers (like -> does) so
// we have to dereference manually using '*'. In some cases the extra * or message passing syntax
// can get a bit annoying so instead a local "naked" pointer can be borrowed from the NSRef. This
// would change the syntax overload in the following:
//
//    NSRef<NSFoo> foo;
//    [*foo setA:1];
//    [*foo setB:2];
//    [*foo setC:3];
//
// Into (note access to members of ObjC classes referenced via pointer is done with . and not ->):
//
//    NSRef<NSFoo> fooRef;
//    NSFoo* foo = fooRef.Get();
//    foo.a = 1;
//    foo.b = 2;
//    boo.c = 3;
//
// Which can be subjectively easier to read.

template <typename T>
struct NSRefTraits {
    static constexpr T kNullValue = nullptr;
    static void AddRef(T value) { [value retain]; }
    static void Release(T value) { [value release]; }
};

template <typename T>
class NSRef : public RefBase<T*, NSRefTraits<T*>> {
  public:
    using RefBase<T*, NSRefTraits<T*>>::RefBase;

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

    T* operator*() { return this->Get(); }
};

template <typename T>
NSRef<T> AcquireNSRef(T* pointee) {
    NSRef<T> ref;
    ref.Acquire(pointee);
    return ref;
}

// This is a RefBase<> for an Objective C protocol (hence the P). Objective C protocols must always
// be referenced with id<ProtocolName> and not just ProtocolName* so they cannot use NSRef<>
// itself. That's what the P in NSPRef stands for: Protocol.
template <typename T>
class NSPRef : public RefBase<T, NSRefTraits<T>> {
  public:
    using RefBase<T, NSRefTraits<T>>::RefBase;

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

    T operator*() { return this->Get(); }
};

template <typename T>
NSPRef<T> AcquireNSPRef(T pointee) {
    NSPRef<T> ref;
    ref.Acquire(pointee);
    return ref;
}

}  // namespace dawn

#endif  // SRC_DAWN_COMMON_NSREF_H_
