// RUN: %empty-directory(%t)
// RUN: split-file %s %t

// RUN: %target-swift-frontend %t%{fs-sep}use-cxx-types.swift -module-name UseCxx -typecheck -verify -verify-additional-file %t%{fs-sep}header.h -emit-clang-header-path %t%{fs-sep}UseCxx.h -I %t -enable-experimental-cxx-interop -clang-header-expose-decls=all-public -disable-availability-checking

// RUN: %target-interop-build-clangxx -std=c++20 -c %t%{fs-sep}use-swift-cxx-types.cpp -I %t -o %t%{fs-sep}swift-cxx-execution.o
// RUN: %target-interop-build-swift %t%{fs-sep}use-cxx-types.swift -o %t%{fs-sep}swift-cxx-execution -Xlinker %t%{fs-sep}swift-cxx-execution.o -module-name UseCxx -Xfrontend -entry-point-function-name -Xfrontend swiftMain -I %t -O -Xfrontend -disable-availability-checking

// RUN: %target-codesign %t%{fs-sep}swift-cxx-execution
// RUN: %target-run %t%{fs-sep}swift-cxx-execution | %FileCheck %s

// REQUIRES: executable_test

//--- header.h

#include <stdio.h>

struct Trivial {
    int x, y;

    inline Trivial(int x, int y) : x(x), y(y) {}
};

template<class T>
struct NonTrivialTemplate {
    T x;

    inline NonTrivialTemplate(T x) : x(x) {
        puts("create NonTrivialTemplate");
    }
    inline NonTrivialTemplate(const NonTrivialTemplate<T> &other) : x(other.x) {
        puts("copy NonTrivialTemplate");
    }
    inline NonTrivialTemplate(NonTrivialTemplate<T> &&other) : x(static_cast<T &&>(other.x)) {
        puts("move NonTrivialTemplate");
    }
    inline ~NonTrivialTemplate() {
        puts("~NonTrivialTemplate");
    }
    inline void testPrint() const {
        puts("testPrint");
    }
};

using NonTrivialTemplateTrivial = NonTrivialTemplate<Trivial>;

class ImmortalFRT {
public:
    int x;
} __attribute__((swift_attr("import_reference")))
__attribute__((swift_attr("retain:immortal")))
__attribute__((swift_attr("release:immortal")));

class SharedFRT {
public:
  SharedFRT() {}
  SharedFRT(int x) : x(x) {}
  int x;
} __attribute__((swift_attr("import_reference")))
__attribute__((swift_attr("retain:retainShared")))
__attribute__((swift_attr("release:releaseShared")));

inline void retainShared(SharedFRT *r) { puts("retainShared"); }
inline void releaseShared(SharedFRT *r) { puts("releaseShared"); }

inline SharedFRT* createSharedFRT() { return new SharedFRT(); } // expected-note {{annotate 'createSharedFRT()' with either SWIFT_RETURNS_RETAINED or SWIFT_RETURNS_UNRETAINED}}

//--- module.modulemap
module CxxTest {
    header "header.h"
    requires cplusplus
}

//--- use-cxx-types.swift
import CxxTest

public func consumeNonTrivial(_ x: consuming NonTrivialTemplateTrivial) -> CInt {
    print("x and y: \(x.x.x), \(x.x.y)")
    return x.x.x
}

public struct TakesNonTrivial {
    public init(_ x: NonTrivialTemplateTrivial) {
        self.prop = x
    }

    public var prop: NonTrivialTemplateTrivial
}

public func consumeImmortalFRT(_ x: consuming ImmortalFRT) {
  print("immortal frt x \(x.x)")
}

public func consumeSharedFRT(_ x : consuming SharedFRT) {
  print("consume shared frt x \(x.x)")
}

public func takeSharedFRT(_ x : SharedFRT) {
  print("take shared frt x \(x.x)")
}

public func genericConsumingFunc<T>(_ p: consuming T) {
  print("generic consuming function")
}

public func returnSharedFRT(_ x : SharedFRT) -> SharedFRT {
  print("return shared frt x \(x.x)")
  return x
}

public func returnSharedFRT2() -> SharedFRT {
  return createSharedFRT() // expected-warning {{cannot infer ownership of foreign reference value returned by 'createSharedFRT()'}}
}

public struct ValueWrapper {
  let sharedFRT: SharedFRT
  public init(_ x: SharedFRT) {
    self.sharedFRT = x
  }
}

public func consumeValueWrapper(_ x: consuming  ValueWrapper) {
  print("return shared frt x \(x.sharedFRT.x)")
}

//--- use-swift-cxx-types.cpp

#include "header.h"
#include "UseCxx.h"
#include <assert.h>

int main() {
  {
    auto x = NonTrivialTemplate(Trivial(1, 2));
    UseCxx::consumeNonTrivial(x);
    puts("DoneCall");
  }
// CHECK: create NonTrivialTemplate
// CHECK-NEXT: copy NonTrivialTemplate
// CHECK-NEXT: ~NonTrivialTemplate
// CHECK-NEXT: x and y: 1, 2
// CHECK-NEXT: DoneCall
// CHECK-NEXT: ~NonTrivialTemplate
  {
    auto x = NonTrivialTemplate(Trivial(-4, 0));
    puts("call");
    auto swiftVal = UseCxx::TakesNonTrivial::init(x);
    puts("DoneCall");
    swiftVal.setProp(x);
  }
// CHECK-NEXT: create NonTrivialTemplate
// CHECK-NEXT: call
// CHECK-NEXT: copy NonTrivialTemplate
// CHECK-NEXT: move NonTrivialTemplate
// CHECK-NEXT: ~NonTrivialTemplate
// CHECK-NEXT: DoneCall
// CHECK-NEXT: copy NonTrivialTemplate
// CHECK-NEXT: ~NonTrivialTemplate
// CHECK-NEXT: move NonTrivialTemplate
// CHECK-NEXT: ~NonTrivialTemplate
// CHECK-NEXT: ~NonTrivialTemplate
// CHECK-NEXT: ~NonTrivialTemplate
  {
    ImmortalFRT frt;
    frt.x = 2;
    UseCxx::consumeImmortalFRT(&frt);
  }
  // CHECK-NEXT: immortal frt x 2
  {
    SharedFRT sfrt;
    sfrt.x = 2;
    UseCxx::takeSharedFRT(&sfrt);
    // CHECK-NEXT: retainShared
    // CHECK-NEXT: releaseShared
    // CHECK-NEXT: take shared frt x 2
    UseCxx::consumeSharedFRT(&sfrt);
    // CHECK-NEXT: retainShared
    // CHECK-NEXT: consume shared frt x 2
    SharedFRT *sfrtptr = UseCxx::returnSharedFRT(&sfrt);
    // CHECK-NEXT: releaseShared
    // CHECK-NEXT: retainShared
    // CHECK-NEXT: return shared frt x 2
    SharedFRT *sfrtptr2 = UseCxx::returnSharedFRT2();
    // No retain or release here.
  }
  {
    SharedFRT sfrt;
    sfrt.x = 4;
    auto wrapper = UseCxx::ValueWrapper::init(&sfrt);
    // consumeValueWrapper creates a defensive copy in the thunk.
    UseCxx::consumeValueWrapper(wrapper);
    // CHECK-NEXT: retainShared
    // CHECK-NEXT: retainShared
    // CHECK-NEXT: return shared frt x 4
    // CHECK-NEXT: releaseShared
    // CHECK-NEXT: releaseShared
  }
  {
    SharedFRT sfrt;
    sfrt.x = 4;
    auto wrapper = UseCxx::ValueWrapper::init(&sfrt);
    UseCxx::genericConsumingFunc(wrapper);
    // CHECK-NEXT: retainShared
    // CHECK-NEXT: retainShared
    // CHECK-DAG: releaseShared
    // CHECK-DAG: generic consuming function
    // CHECK-NEXT: releaseShared
  }
  puts("EndOfTest");
// CHECK-NEXT: EndOfTest
  return 0;
}
