package vgrechka

import vgrechka.fuckaround.*
import java.sql.Timestamp
import kotlin.properties.Delegates
import kotlin.properties.ReadOnlyProperty
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KMutableProperty0
import kotlin.reflect.KProperty
import kotlin.reflect.jvm.isAccessible

fun wtf(msg: String = "...WTF didn't you describe this WTF?"): Nothing = throw BreakOnMeException("WTF: $msg")
fun die(msg: String = "You've just killed me, motherfucker!"): Nothing = throw BreakOnMeException("Aarrgghh... $msg")
fun imf(what: String = "me"): Nothing = throw BreakOnMeException("Implement $what, please, fuck you")
fun bitch(msg: String = "Just bitching..."): Nothing = throw BreakOnMeException(msg)
fun bitchNoBreak(msg: String = "Just bitching..."): Nothing = throw RuntimeException(msg)


var exhaustive: Any? = null

fun <T: Any> notNull(): ReadWriteProperty<Any?, T> = Delegates.notNull()


class notNullOnce<T: Any>(val noisy: Boolean = false) : ReadWriteProperty<Any?, T> {
    val __tests by lazy {listOf(FuckAround_notNullOnce_1)}

    var _value: T? = null
    var assignmentStack by notNull<StackCaptureException>()

    override fun getValue(thisRef: Any?, property: KProperty<*>): T {
        val descr = debugPropertyDescription(thisRef, property)
        return _value ?: throw BreakOnMeException("Property $descr should be initialized before get.")
    }

    override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        val descr = debugPropertyDescription(thisRef, property)

        check(this._value == null) {
            clog("\n********** Previous assignment **********\n")
            clog(assignmentStack.stackTraceString)
            "Property $descr should be assigned only once"
        }

        this._value = value
        assignmentStack = StackCaptureException()

        if (noisy) {
            (thisRef as? WithDebugId)?.let {
                clog("===== $descr = $value")
            }
        }
    }

    private fun debugPropertyDescription(thisRef: Any?, property: KProperty<*>): String {
        val withDebugId = thisRef as? WithDebugId
        return when {
            withDebugId == null -> "`${property.name}`"
            else -> "${withDebugId.debugId}.${property.name}"
        }
    }

    companion object
}

class maybeNullOnce<T: Any> : ReadWriteProperty<Any?, T?> {
    // TODO:vgrechka Unify with notNullOnce
    var assigned = false
    var _value: T? = null

    override fun getValue(thisRef: Any?, property: KProperty<*>): T? {
        if (!assigned) throw IllegalStateException("Property `${property.name}` should be initialized before get.")
        return _value
    }

    override fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) {
        check(!assigned) {"Property `${property.name}` should be assigned only once"}
        this.assigned = true
        this._value = value
    }
}

class maybeNull<T: Any> : ReadWriteProperty<Any?, T?> {
    var assigned = false
    var _value: T? = null

    override fun getValue(thisRef: Any?, property: KProperty<*>): T? {
        if (!assigned) throw IllegalStateException("Property `${property.name}` should be initialized before get.")
        return _value
    }

    override fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) {
        this.assigned = true
        this._value = value
    }
}

class StackCaptureException(message: String = "Capturing freaking stack") : Exception(message)

interface WithDebugId {
    val debugId: String

    class Impl(host: Any) : WithDebugId {
        override val debugId = Debug.nextDebugId(host)
    }
}

fun <T> l(vararg elements: T): List<T> = listOf(*elements)

fun <T> l(block: (MutableList<T>) -> Unit): List<T> = mutableListOf<T>().also(block)

inline fun <reified T> a(vararg elements: T): Array<T> = arrayOf(*elements)

fun clamp(x: Int, min: Int, max: Int) = when {
    x < min -> min
    x > max -> max
    else -> x
}

fun clamp(x: Double, min: Double, max: Double) = when {
    x < min -> min
    x > max -> max
    else -> x
}

class place<T> : ReadWriteProperty<Any?, T> {
    var assigned = false
    var _value: T? = null

    override fun getValue(thisRef: Any?, property: KProperty<*>): T {
        if (!assigned) throw IllegalStateException("Property `${property.name}` should be initialized before get.")
        return _value as T
    }

    override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        this.assigned = true
        this._value = value
    }

    companion object {
        fun <T> isAssigned(prop: KMutableProperty0<T>): Boolean {
            prop.isAccessible = true
            return (prop.getDelegate() as place<*>).assigned
        }
    }
}

class once<T> : ReadWriteProperty<Any?, T> {
    var assigned = false
    var _value: T? = null

    override fun getValue(thisRef: Any?, property: KProperty<*>): T {
        if (!assigned) throw IllegalStateException("Property `${property.name}` should be initialized before get.")
        return _value as T
    }

    override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        check(!assigned) {"Property `${property.name}` should be assigned only once"}
        this.assigned = true
        this._value = value
    }
}

fun <T> named(make: (ident: String) -> T) = object : ReadOnlyProperty<Any?, T> {
    private var value: T? = null

    override fun getValue(thisRef: Any?, property: KProperty<*>): T {
        if (value == null)
            value = make(property.name)
        return bang(value)
    }
}

fun myName() = named {it}
fun slashMyName() = named {"/" + it}


fun StringBuilder.ln(x: Any? = "") {
    append(x)
    append("\n")
}

fun String.indexOfOrNull(needle: String, startIndex: Int = 0, ignoreCase: Boolean = false): Int? {
    val index = indexOf(needle, startIndex, ignoreCase)
    return if (index >= 0) index else null
}

fun CharSequence.lastIndexOfOrNull(string: String, startIndex: Int = lastIndex, ignoreCase: Boolean = false): Int? {
    val res = this.lastIndexOf(string, startIndex, ignoreCase)
    return when (res) {
        -1 -> null
        else -> res
    }
}

fun String.indexOfOrBitch(needle: String, startIndex: Int = 0, ignoreCase: Boolean = false): Int =
    this.indexOfOrNull(needle, startIndex, ignoreCase) ?: bitchNeedleNotFound(needle, this)

fun String.lastIndexOfOrBitch(needle: String, startIndex: Int = this.lastIndex, ignoreCase: Boolean = false): Int =
    this.lastIndexOfOrNull(needle, startIndex, ignoreCase) ?: bitchNeedleNotFound(needle, this)

private fun bitchNeedleNotFound(needle: String, haystack: String): Nothing {
    bitch("I want needle `$needle` in haystack `$haystack`")
}

inline fun <T> List<T>.indexOfFirstOrNull(predicate: (T) -> Boolean): Int? {
    val index = this.indexOfFirst(predicate)
    return when (index) {
        -1 -> null
        else -> index
    }
}

fun <T> bang(x: T?): T {
    if (x == null) bitch("Banging on freaking null")
    return x
}

fun <T> List<T>.indexOfOrNull(element: T): Int? {
    val index = this.indexOf(element)
    return when (index) {
        -1 -> null
        else -> index
    }
}

fun stringBuild(block: (StringBuilder) -> Unit) =
    StringBuilder().also(block).toString()

fun String.camelToKebab() = stringBuild {
    for (c in this) {
        it += when {
            c == c.toUpperCase() -> "-${c.toLowerCase()}"
            else -> c
        }
    }
}

operator fun StringBuilder.plusAssign(x: Any?) {
    this.append(x)
}

interface Dancer<out T> {
    fun dance(): T
    operator fun not(): T = dance()
}

abstract class DancerBase<out T> : Dancer<T> {
    override fun toString(): String = bitch("You probably forgot to use ! operator with dancer")
}

fun <T> dance(x: Dancer<T>) = x.dance()













