/*
 * Copyright 2010-2024 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(ExperimentalAtomicApi::class)
@file:Suppress("DEPRECATION_ERROR") // Freezing API

package test.native.concurrent

import kotlin.concurrent.atomics.AtomicInt
import kotlin.concurrent.atomics.ExperimentalAtomicApi
import kotlin.concurrent.atomics.incrementAndFetch
import kotlin.native.concurrent.*
import kotlin.native.concurrent.WorkerBoundReference
import kotlin.test.*

private class C(var a: Int)
private class Wrapper(val ref: WorkerBoundReference<C>)

private val global1: WorkerBoundReference<C> = WorkerBoundReference(C(3))
private val global2: WorkerBoundReference<C> = WorkerBoundReference(C(3))
private val global3: WorkerBoundReference<C> = WorkerBoundReference(C(3).freeze())
private val global4: WorkerBoundReference<C> = WorkerBoundReference(C(3))
private val global5: WorkerBoundReference<C> = WorkerBoundReference(C(3))
private val global6: WorkerBoundReference<C> = WorkerBoundReference(C(3))
private val global7: WorkerBoundReference<C> = WorkerBoundReference(C(3))

class WorkerBoundReference {
    @Test
    fun global() {
        assertEquals(3, global1.value.a)
        assertEquals(3, global1.valueOrNull?.a)

        val worker = Worker.start()
        val future = worker.execute(TransferMode.SAFE, {}) {
            global1
        }

        val value = future.result
        assertEquals(3, value.value.a)
        assertEquals(3, value.valueOrNull?.a)
        worker.requestTermination().result
    }

    @Test
    fun globalOnWorker() {
        assertEquals(3, global2.value.a)

        val worker = Worker.start()
        val future = worker.execute(TransferMode.SAFE, {}) {
            assertEquals(global2.value, global2.valueOrNull)
            global2.value.a
        }

        val value = future.result
        assertEquals(3, value)
        worker.requestTermination().result
    }

    @Test
    fun globalOnWorkerFrozenInitially() {
        assertEquals(3, global3.value.a)
        assertEquals(3, global3.valueOrNull?.a)

        val worker = Worker.start()
        val future = worker.execute(TransferMode.SAFE, {}) {
            global3.value.a
        }

        val value = future.result
        assertEquals(3, value)
        worker.requestTermination().result
    }

    @Test
    fun globalOnWorkerFrozenBeforePassing() {
        assertEquals(3, global4.value.a)
        global4.value.freeze()

        val worker = Worker.start()
        val future = worker.execute(TransferMode.SAFE, {}) {
            global4.value.a
        }

        val value = future.result
        assertEquals(3, value)
        worker.requestTermination().result
    }

    @Test
    fun globalOnWorkerFrozenBeforeAccess() {
        val semaphore = AtomicInt(0)

        assertEquals(3, global5.value.a)

        val worker = Worker.start()
        val future = worker.execute(TransferMode.SAFE, { semaphore }) { semaphore ->
            semaphore.incrementAndFetch()
            while (semaphore.load() < 2) {
            }

            global5.value.a
        }

        while (semaphore.load() < 1) {
        }
        global5.value.freeze()
        semaphore.incrementAndFetch()

        val value = future.result
        assertEquals(3, value)
        worker.requestTermination().result
    }

    @Test
    fun globalModification() {
        val semaphore = AtomicInt(0)

        assertEquals(3, global6.value.a)

        val worker = Worker.start()
        val future = worker.execute(TransferMode.SAFE, { semaphore }) { semaphore ->
            semaphore.incrementAndFetch()
            while (semaphore.load() < 2) {
            }
            global6
        }

        while (semaphore.load() < 1) {
        }
        global6.value.a = 4
        semaphore.incrementAndFetch()

        val value = future.result
        assertEquals(4, value.value.a)
        assertEquals(4, value.valueOrNull?.a)
        worker.requestTermination().result
    }

    @Test
    fun globalGetWorker() {
        val ownerId = Worker.current.id
        assertEquals(ownerId, global7.worker.id)

        val worker = Worker.start()
        val future = worker.execute(TransferMode.SAFE, { ownerId }) { ownerId ->
            assertEquals(ownerId, global7.worker.id)
        }

        future.result
        worker.requestTermination().result
    }

    @Test
    fun local() {
        val local = WorkerBoundReference(C(3))
        assertEquals(3, local.value.a)
        assertEquals(3, local.valueOrNull?.a)
    }

    @Test
    fun localFrozen() {
        val local = WorkerBoundReference(C(3)).freeze()
        assertEquals(3, local.value.a)
        assertEquals(3, local.valueOrNull?.a)

        val worker = Worker.start()
        val future = worker.execute(TransferMode.SAFE, { local }) { local ->
            local
        }

        val value = future.result
        assertEquals(3, value.value.a)
        assertEquals(3, value.valueOrNull?.a)
        worker.requestTermination().result
    }

    @Test
    fun localOnWorkerFrozen() {
        val local = WorkerBoundReference(C(3)).freeze()
        assertEquals(3, local.value.a)

        val worker = Worker.start()
        val future = worker.execute(TransferMode.SAFE, { local }) { local ->
            assertEquals(local.value, local.valueOrNull)
            local.value.a
        }

        val value = future.result
        assertEquals(3, value)
        worker.requestTermination().result
    }

    @Test
    fun localOnWorkerFrozenInitiallyFrozen() {
        val local = WorkerBoundReference(C(3).freeze()).freeze()
        assertEquals(3, local.value.a)
        assertEquals(3, local.valueOrNull?.a)

        val worker = Worker.start()
        val future = worker.execute(TransferMode.SAFE, { local }) { local ->
            local.value.a
        }

        val value = future.result
        assertEquals(3, value)
        worker.requestTermination().result
    }

    @Test
    fun localOnWorkerFrozenBeforePassingFrozen() {
        val local = WorkerBoundReference(C(3)).freeze()
        assertEquals(3, local.value.a)
        local.value.freeze()

        val worker = Worker.start()
        val future = worker.execute(TransferMode.SAFE, { local }) { local ->
            local.value.a
        }

        val value = future.result
        assertEquals(3, value)
        worker.requestTermination().result
    }

    @Test
    fun localOnWorkerFrozenBeforeAccessFrozen() {
        val semaphore = AtomicInt(0)

        val local = WorkerBoundReference(C(3)).freeze()
        assertEquals(3, local.value.a)

        val worker = Worker.start()
        val future = worker.execute(TransferMode.SAFE, { Pair(local, semaphore) }) { (local, semaphore) ->
            semaphore.incrementAndFetch()
            while (semaphore.load() < 2) {
            }

            local.value.a
        }

        while (semaphore.load() < 1) {
        }
        local.value.freeze()
        semaphore.incrementAndFetch()

        val value = future.result
        assertEquals(3, value)
        worker.requestTermination().result
    }

    @Test
    fun localOnMainThread() {
        val worker = Worker.start()
        val future = worker.execute(TransferMode.SAFE, {}) {
            WorkerBoundReference(C(3))
        }

        assertEquals(3, future.result.value.a)

        worker.requestTermination().result
    }

    @Test
    fun localOnMainThreadFrozen() {
        val worker = Worker.start()
        val future = worker.execute(TransferMode.SAFE, {}) {
            WorkerBoundReference(C(3)).freeze()
        }

        val value = future.result
        assertEquals(3, value.value.a)
        assertEquals(value.value, value.valueOrNull)

        worker.requestTermination().result
    }

    @Test
    fun localModificationFrozen() {
        val semaphore = AtomicInt(0)

        val local = WorkerBoundReference(C(3)).freeze()
        assertEquals(3, local.value.a)

        val worker = Worker.start()
        val future = worker.execute(TransferMode.SAFE, { Pair(local, semaphore) }) { (local, semaphore) ->
            semaphore.incrementAndFetch()
            while (semaphore.load() < 2) {
            }
            local
        }

        while (semaphore.load() < 1) {
        }
        local.value.a = 4
        semaphore.incrementAndFetch()

        val value = future.result
        assertEquals(4, value.value.a)
        assertEquals(4, value.valueOrNull?.a)
        worker.requestTermination().result
    }

    @Test
    fun localGetWorkerFrozen() {
        val local = WorkerBoundReference(C(3)).freeze()

        val ownerId = Worker.current.id
        assertEquals(ownerId, local.worker.id)

        val worker = Worker.start()
        val future = worker.execute(TransferMode.SAFE, { Pair(local, ownerId) }) { (local, ownerId) ->
            assertEquals(ownerId, local.worker.id)
        }

        future.result
        worker.requestTermination().result
    }

    @Test
    fun localForeignGetWorker() {
        val worker = Worker.start()
        val ownerId = worker.id
        val future = worker.execute(TransferMode.SAFE, { ownerId }) { ownerId ->
            val local = WorkerBoundReference(C(3))
            assertEquals(ownerId, local.worker.id)
            local
        }

        val value = future.result
        assertEquals(ownerId, value.worker.id)

        worker.requestTermination().result
    }

    @Test
    fun localForeignGetWorkerFrozen() {
        val worker = Worker.start()
        val ownerId = worker.id
        val future = worker.execute(TransferMode.SAFE, { ownerId }) { ownerId ->
            val local = WorkerBoundReference(C(3)).freeze()
            assertEquals(ownerId, local.worker.id)
            local
        }

        val value = future.result
        assertEquals(ownerId, value.worker.id)

        worker.requestTermination().result
    }

    @Test
    fun localWithWrapperFrozen() {
        val local = Wrapper(WorkerBoundReference(C(3))).freeze()
        assertEquals(3, local.ref.value.a)

        val worker = Worker.start()
        val future = worker.execute(TransferMode.SAFE, { local }) { local ->
            local
        }

        val value = future.result
        assertEquals(3, value.ref.value.a)
        worker.requestTermination().result
    }

    @Test
    fun localAccessWithWrapperFrozen() {
        val local = Wrapper(WorkerBoundReference(C(3))).freeze()
        assertEquals(3, local.ref.value.a)

        val worker = Worker.start()
        val future = worker.execute(TransferMode.SAFE, { local }) { local ->
            assertEquals(local.ref.value, local.ref.valueOrNull)
            local.ref.value.a
        }

        val value = future.result
        assertEquals(3, value)
        worker.requestTermination().result
    }

    @Test
    fun concurrentAccessFrozen() {
        val workerCount = 10
        val workerUnlocker = AtomicInt(0)

        val ref = WorkerBoundReference(C(3)).freeze()
        assertEquals(3, ref.value.a)

        val workers = Array(workerCount) {
            Worker.start()
        }
        val futures = Array(workers.size) {
            workers[it].execute(TransferMode.SAFE, { Pair(ref, workerUnlocker) }) { (ref, workerUnlocker) ->
                while (workerUnlocker.value < 1) {
                }

                ref.value.a
            }
        }
        workerUnlocker.incrementAndFetch()

        for (future in futures) {
            val value = future.result
            assertEquals(3, value)
        }

        for (worker in workers) {
            worker.requestTermination().result
        }
    }

    @Test
    fun doubleFreeze() {
        val ref = WorkerBoundReference(C(3))
        val wrapper = Wrapper(ref)
        ref.freeze()
        ref.freeze()
        wrapper.freeze()
    }
}