/*
 * Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
 * that can be found in the LICENSE file.
 */

@file:OptIn(ObsoleteWorkersApi::class)
package runtime.basic.initializers6

import kotlin.test.*
import kotlin.concurrent.AtomicInt
import kotlin.concurrent.*
import kotlin.native.concurrent.*

val aWorkerId = AtomicInt(0)
val bWorkersCount = 3

val aWorkerUnlocker = AtomicInt(0)
val bWorkerUnlocker = AtomicInt(0)

object A {
    init {
        // Must be called by aWorker only.
        assertEquals(aWorkerId.value, Worker.current.id)
        // Only allow b workers to run, when a worker has started initialization.
        bWorkerUnlocker.incrementAndGet()
        // Only proceed with initialization, when all b workers have started executing.
        while (aWorkerUnlocker.value < bWorkersCount) {}
        // And now wait a bit, to increase probability of races.
        Worker.current.park(100 * 1000L)
    }
    val a = produceA()
    val b = produceB()
}

fun produceA(): String {
    // Must've been called by aWorker only.
    assertEquals(aWorkerId.value, Worker.current.id)
    return "A"
}

fun produceB(): String {
    // Must've been called by aWorker only.
    assertEquals(aWorkerId.value, Worker.current.id)
    // Also check that it's ok to get A.a while initializing A.b.
    return "B+${A.a}"
}

fun box(): String {
    val aWorker = Worker.start()
    aWorkerId.value = aWorker.id
    val bWorkers = Array(bWorkersCount, { _ -> Worker.start() })

    val aFuture = aWorker.execute(TransferMode.SAFE, {}, {
        A.b
    })
    val bFutures = Array(bWorkers.size, {
        bWorkers[it].execute(TransferMode.SAFE, {}, {
            // Wait until A has started to initialize.
            while (bWorkerUnlocker.value < 1) {}
            // Now allow A initialization to continue.
            aWorkerUnlocker.incrementAndGet()
            // And this should not've tried to init A itself.
            A.a + A.b
        })
    })

    for (future in bFutures) {
        assertEquals("AB+A", future.result)
    }
    assertEquals("B+A", aFuture.result)

    for (worker in bWorkers) {
        worker.requestTermination().result
    }
    aWorker.requestTermination().result
    return "OK"
}
