import kotlinx.atomicfu.*
import kotlin.test.*

class TraceTest {
    private val defaultTrace = Trace()
    private val a1 = atomic(5, defaultTrace)

    private val traceWithSize = Trace(30)
    private val a2 = atomic(1, traceWithSize)

    private val traceWithFormat = Trace(format = TraceFormat { i, text -> "[$i: $text]" })
    private val a = atomic(0, traceWithFormat)

    private val traceWithSizeAndFormat = Trace(30, TraceFormat { id, text -> "$id: $text"})
    private val a3 = atomic(2)

    private val shortTrace = Trace(4)
    private val s = atomic(0, shortTrace.named("s"))

    fun testDefaultTrace() {
        val oldValue = a1.value
        defaultTrace { "before CAS value = $oldValue" }
        val res = a1.compareAndSet(oldValue, oldValue * 10)
        val newValue = a1.value
        defaultTrace { "after CAS value = $newValue" }
    }

    fun testTraceWithSize() {
        val oldValue = a2.value
        traceWithSize { "before CAS value = $oldValue" }
        assertTrue(a2.compareAndSet(oldValue, oldValue * 10))
        traceWithSize { "after CAS value = ${a2.value}" }
        traceWithSize { "before getAndDecrement value = ${a2.value}" }
        a2.getAndDecrement()
        assertEquals(9, a2.value)
        traceWithSize { "after getAndDecrement value = ${a2.value}" }
    }

    fun testTraceWithFormat() {
        val oldValue = a3.value
        traceWithFormat { "before CAS value = $oldValue" }
        assertTrue(a3.compareAndSet(oldValue, oldValue * 10))
        traceWithFormat { "after CAS value = ${a3.value}" }
        traceWithFormat { "before getAndDecrement value = ${a3.value}" }
        a3.getAndDecrement()
        assertEquals(19, a3.value)
        traceWithFormat { "after getAndDecrement value = ${a3.value}" }
    }

    fun testNamedTrace() {
        s.value = 5
        shortTrace { "before CAS value = ${s.value}" }
        s.compareAndSet(5, -2)
        assertEquals(-2, s.value)
        shortTrace { "after CAS value = ${s.value}" }
    }

    private enum class Status { START, END }

    fun testMultipleAppend() {
        val i = 1
        traceWithFormat.append(i, Status.START)
        assertEquals(0, a.value)
        a.incrementAndGet()
        traceWithFormat.append(i, a.value, "incAndGet")
        assertEquals(1, a.value)
        a.lazySet(10)
        traceWithFormat.append(i, a.value, "lazySet")
        assertEquals(10, a.value)
        traceWithFormat.append(i, Status.END)
    }

    fun testTraceInBlock() {
        a1.lazySet(5)
        if (a1.value == 5) {
            defaultTrace { "Value checked" }
            if (a1.compareAndSet(5, 10)) {
                defaultTrace { "CAS succeeded" }
            }
        }
        assertEquals(10, a1.value)
        while (true) {
            if (a1.value == 10) {
                defaultTrace.append("Value checked", a1.value)
                a1.value = 15
                break
            } else {
                defaultTrace.append("Wrong value", a1.value)
            }
        }
    }

    fun test() {
        testDefaultTrace()
        testTraceWithSize()
        testTraceWithFormat()
        testNamedTrace()
        testMultipleAppend()
        testTraceInBlock()
    }
}

fun box(): String {
    TraceTest().test()
    return "OK"
}