/*
 * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
 * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
 */
@file:OptIn(kotlin.experimental.ExperimentalNativeApi::class, kotlin.native.runtime.NativeRuntimeApi::class)

import kotlin.test.*
import kotlin.native.ref.WeakReference
import kotlinx.cinterop.*
import kotlinx.cinterop.internal.detachObjCObject
import objcTests.*

class detachObjCObjectTests {
    @AfterTest
    private fun gc() {
        // An attempt to make sure the GC is fine after detachObjCObject.
        kotlin.native.runtime.GC.collect()
        kotlin.native.runtime.GC.collect()
    }

    private fun checkThroughWeakRef(checkWeakRefBeforeReset: Boolean) {
        val obj = NSObject()
        val ref = WeakReference(obj)

        if (checkWeakRefBeforeReset) {
            repeat(2) {
                val refValue = ref.value
                assertNotNull(refValue)
                // refValue is actually a new wrapper, so we need to reset it as well:
                detachObjCObject(refValue)
                // The next iteration will check that the object is not yet removed.
            }
        }

        detachObjCObject(obj)
        val refValue = ref.value
        assertNull(refValue)
    }

    @Test
    fun checkThroughWeakRef() {
        checkThroughWeakRef(checkWeakRefBeforeReset = false)
    }

    @Test
    fun checkThroughWeakRefWithMultipleWrappers() {
        checkThroughWeakRef(checkWeakRefBeforeReset = true)
    }

    @Test
    fun checkThroughDeallocFlag() {
        val obj = ObjectWithDeallocFlag()
        val deallocFlagHolder = obj.deallocFlagHolder

        assertFalse(deallocFlagHolder.deallocated)
        detachObjCObject(obj)
        assertTrue(deallocFlagHolder.deallocated)
    }

    @Test
    fun checkThroughDeallocFlagWithMultipleWrappers() {
        val obj = ObjectWithDeallocFlag()
        val deallocFlagHolder = obj.deallocFlagHolder

        assertFalse(deallocFlagHolder.deallocated)

        val sameObj = obj.sameObject() // Same object, different wrapper

        detachObjCObject(obj)
        assertFalse(deallocFlagHolder.deallocated)

        detachObjCObject(obj)
        assertFalse(deallocFlagHolder.deallocated)

        detachObjCObject(sameObj)
        assertTrue(deallocFlagHolder.deallocated)
    }
}
