/**
 * Collection-related utilities.
 */
package theorycrafter.utils


/**
 * Returns an immutable, fixed-size list of `null` values.
 */
fun <T> nulls(size: Int): List<T?> = Nulls(size)



/**
 * A fixed-size list of `null` values.
 */
private class Nulls<T>(override val size: Int): AbstractList<T?>(){

    override fun get(index: Int): T? = null

}


/**
 * Runs the given [action] on each non-`null` element and its index.
 */
inline fun <T> Iterable<T?>.forEachNonNullIndexed(action: (Int, T) -> Unit){
    this.forEachIndexed{ index, value ->
        if (value != null)
            action.invoke(index, value)
    }
}


/**
 * Removes the tail of the list, starting at the given index
 */
fun <T> MutableList<T>.removeTailFrom(fromIndex: Int) = subList(fromIndex, size).clear()


/**
 * Returns the index of the given element, or `null` if none exists in the list.
 */
fun <T> Collection<T>.indexOfOrNull(element: T) = indexOf(element).let{ if (it == -1) null else it }


/**
 * Add the given item, if it's not `null` to the collection.
 */
fun <T: Any> MutableCollection<T>.addNotNull(item: T?) = item?.let { add(it) }


/**
 * Returns the sum of the values returned by [selector] on the elements in the iterable.
 *
 * This is needed due to https://youtrack.jetbrains.com/issue/KT-46360.
 */
inline fun <T> Iterable<T>.sumOfInts(selector: (T) -> Int): Int {
    return sumOf(selector)
}


/**
 * Returns a list whose prefix is the given one, but it is padded with the given item until its size is at least the
 * given value.
 */
fun <T> List<T>.padEnd(length: Int, padValue: T): List<T> {
    val list = this
    return buildList {
        addAll(list)
        while (size < length)
            add(padValue)
    }
}


/**
 * Same as [padEnd], but pads with `null`s.
 */
@Suppress("unused")
fun <T: Any> List<T?>.padEnd(length: Int): List<T?> {
    return padEnd(length, padValue = null)
}


/**
 * Same as [Iterable.associateBy] but skips `null` keys.
 */
@Suppress("unused")
inline fun <T, K: Any> Iterable<T>.associateByNotNullKey(keySelector: (T) -> K?): Map<K, T> {
    val destination = mutableMapOf<K, T>()
    for (element in this) {
        val key = keySelector(element) ?: continue
        destination[key] = element
    }
    return destination
}


/**
 * Returns a new list, with the item at the given index removed.
 */
fun <T> List<T>.withIndexRemoved(index: Int): List<T> = buildList(size-1) {
    addAll(this@withIndexRemoved.subList(0, index))
    addAll(this@withIndexRemoved.subList(index+1, this@withIndexRemoved.size))
}


/**
 * Filters the given list if [condition] it `true`; otherwise returns the list.
 */
inline fun <T> Collection<T>.filterIf(condition: Boolean, predicate: (T) -> Boolean): List<T> {
    return when {
        condition -> filter(predicate)
        this is List<T> -> this
        else -> this.toList()
    }
}


/**
 * Swaps the values at the two given indices.
 */
fun <T> MutableList<T>.swap(index1: Int, index2: Int) {
    val tmp = this[index1]
    this[index1] = this[index2]
    this[index2] = tmp
}


/**
 * Returns a list with the items from the given lists interleaved.
 *
 * Once a list is exhausted, it is skipped.
 */
fun <T> interleave(vararg lists: List<T>): List<T> = buildList {
    val iterators = lists.map { it.iterator() }
    while (iterators.any { it.hasNext() }) {
        iterators.forEach { iterator ->
            if (iterator.hasNext()) {
                add(iterator.next())
            }
        }
    }
}
