package at.hannibal2.skyhanni.utils import at.hannibal2.skyhanni.utils.NEUItems.getItemStack import at.hannibal2.skyhanni.utils.renderables.Renderable import at.hannibal2.skyhanni.utils.renderables.RenderableUtils import at.hannibal2.skyhanni.utils.renderables.Searchable import at.hannibal2.skyhanni.utils.renderables.toSearchable import net.minecraft.enchantment.Enchantment import net.minecraft.item.ItemStack import java.util.Collections import java.util.Queue import java.util.WeakHashMap import kotlin.math.ceil object CollectionUtils { inline fun , reified E> T.drainForEach(action: (E) -> Unit): T { while (true) action(this.poll() ?: break) return this } inline fun , reified E> T.drain(amount: Int): T { for (i in 1..amount) this.poll() ?: break return this } inline fun > Queue.drainTo(list: L, action: (E) -> K): L { while (true) list.add(action(this.poll() ?: break)) return list } inline fun > Queue.drainTo(list: L): L { while (true) list.add(this.poll() ?: break) return list } // Let garbage collector handle the removal of entries in this list fun weakReferenceList(): MutableSet = Collections.newSetFromMap(WeakHashMap()) fun MutableCollection.filterToMutable(predicate: (T) -> Boolean) = filterTo(mutableListOf(), predicate) fun List.indexOfFirst(vararg args: T) = args.map { indexOf(it) }.firstOrNull { it != -1 } infix fun MutableMap.put(pairs: Pair) { this[pairs.first] = pairs.second } // Taken and modified from Skytils @JvmStatic fun T?.equalsOneOf(vararg other: T): Boolean { for (obj in other) { if (this == obj) return true } return false } fun List.getOrNull(index: Int): E? { return if (index in indices) { get(index) } else null } fun T?.toSingletonListOrEmpty(): List { if (this == null) return emptyList() return listOf(this) } fun MutableMap.addOrPut(key: K, number: Int): Int = this.merge(key, number, Int::plus)!! // Never returns null since "plus" can't return null fun MutableMap.addOrPut(key: K, number: Long): Long = this.merge(key, number, Long::plus)!! // Never returns null since "plus" can't return null fun MutableMap.addOrPut(key: K, number: Double): Double = this.merge(key, number, Double::plus)!! // Never returns null since "plus" can't return null fun MutableMap.addOrPut(key: K, number: Float): Float = this.merge(key, number, Float::plus)!! // Never returns null since "plus" can't return null fun Map.sumAllValues(): Double { if (values.isEmpty()) return 0.0 return when (values.first()) { is Double -> values.sumOf { it.toDouble() } is Float -> values.sumOf { it.toDouble() } is Long -> values.sumOf { it.toLong() }.toDouble() else -> values.sumOf { it.toInt() }.toDouble() } } /** Returns a map containing the count of occurrences of each distinct result of the [selector] function. */ inline fun Iterable.countBy(selector: (T) -> K): Map { val map = mutableMapOf() for (item in this) { val key = selector(item) map[key] = map.getOrDefault(key, 0) + 1 } return map } fun List.nextAfter(after: String, skip: Int = 1) = nextAfter({ it == after }, skip) fun List.nextAfter(after: (String) -> Boolean, skip: Int = 1): String? { var missing = -1 for (line in this) { if (after(line)) { missing = skip - 1 continue } if (missing == 0) { return line } if (missing != -1) { missing-- } } return null } fun List.removeNextAfter(after: String, skip: Int = 1) = removeNextAfter({ it == after }, skip) fun List.removeNextAfter(after: (String) -> Boolean, skip: Int = 1): List { val newList = mutableListOf() var missing = -1 for (line in this) { if (after(line)) { missing = skip - 1 continue } if (missing == 0) { missing-- continue } if (missing != -1) { missing-- } newList.add(line) } return newList } inline fun > K.transformAt(index: Int, transform: T.() -> T): K { this[index] = transform(this[index]) return this } /** * This does not work inside a [buildList] block */ fun List.addIfNotNull(element: String?) = element?.let { plus(it) } ?: this fun Map.editCopy(function: MutableMap.() -> Unit) = toMutableMap().also { function(it) }.toMap() fun List.editCopy(function: MutableList.() -> Unit) = toMutableList().also { function(it) }.toList() fun Map.moveEntryToTop(matcher: (Map.Entry) -> Boolean): Map { val entry = entries.find(matcher) if (entry != null) { val newMap = linkedMapOf(entry.key to entry.value) newMap.putAll(this) return newMap } return this } operator fun IntRange.contains(range: IntRange): Boolean = range.first in this && range.last in this fun MutableList>.addAsSingletonList(text: E) { add(Collections.singletonList(text)) } // TODO move to RenderableUtils fun MutableList>.addSingleString(text: String) { add(Collections.singletonList(Renderable.string(text))) } fun > List>.sorted(): List> { return sortedBy { (_, value) -> value } } fun > Map.sorted(): Map { return toList().sorted().toMap() } fun > Map.sortedDesc(): Map { return toList().sorted().reversed().toMap() } fun Sequence.takeWhileInclusive(predicate: (T) -> Boolean) = sequence { with(iterator()) { while (hasNext()) { val next = next() yield(next) if (!predicate(next)) break } } } inline fun Iterator.consumeWhile(block: (T) -> R): R? { while (hasNext()) { return block(next()) ?: continue } return null } inline fun Iterator.collectWhile(block: (T) -> Boolean): List { return collectWhileTo(mutableListOf(), block) } inline fun > Iterator.collectWhileTo(collection: C, block: (T) -> Boolean): C { while (hasNext()) { val element = next() if (block(element)) { collection.add(element) } else { break } } return collection } /** Removes the first element that matches the given [predicate] in the list. */ fun List.removeFirst(predicate: (T) -> Boolean): List { val mutableList = this.toMutableList() val iterator = mutableList.iterator() while (iterator.hasNext()) { if (predicate(iterator.next())) { iterator.remove() break } } return mutableList.toList() } /** Removes the first element that matches the given [predicate] in the map. */ fun Map.removeFirst(predicate: (Map.Entry) -> Boolean): Map { val mutableMap = this.toMutableMap() val iterator = mutableMap.entries.iterator() while (iterator.hasNext()) { if (predicate(iterator.next())) { iterator.remove() break } } return mutableMap.toMap() } /** Updates a value if it is present in the set (equals), useful if the newValue is not reference equal with the value in the set */ inline fun MutableSet.refreshReference(newValue: T) = if (this.contains(newValue)) { this.remove(newValue) this.add(newValue) true } else false @Suppress("UNCHECKED_CAST") fun Iterable.takeIfAllNotNull(): Iterable? = takeIf { null !in this } as? Iterable @Suppress("UNCHECKED_CAST") fun List.takeIfAllNotNull(): List? = takeIf { null !in this } as? List // TODO add cache fun MutableList.addString( text: String, horizontalAlign: RenderUtils.HorizontalAlignment = RenderUtils.HorizontalAlignment.LEFT, verticalAlign: RenderUtils.VerticalAlignment = RenderUtils.VerticalAlignment.CENTER, ) { add(Renderable.string(text, horizontalAlign = horizontalAlign, verticalAlign = verticalAlign)) } // TODO add cache fun MutableList.addSearchString( text: String, searchText: String? = null, horizontalAlign: RenderUtils.HorizontalAlignment = RenderUtils.HorizontalAlignment.LEFT, verticalAlign: RenderUtils.VerticalAlignment = RenderUtils.VerticalAlignment.CENTER, ) { add(Renderable.string(text, horizontalAlign = horizontalAlign, verticalAlign = verticalAlign).toSearchable(searchText)) } // TODO add internal name support, and caching fun MutableList.addItemStack( itemStack: ItemStack, highlight: Boolean = false, scale: Double = NEUItems.itemFontSize, ) { if (highlight) { // Hack to add enchant glint, like Hypixel does it itemStack.addEnchantment(Enchantment.protection, 0) } add(Renderable.itemStack(itemStack, scale = scale)) } fun takeColumn(start: Int, end: Int, startColumn: Int, endColumn: Int, rowSize: Int = 9) = generateSequence(start) { it + 1 }.map { (it / (endColumn - startColumn)) * rowSize + (it % (endColumn - startColumn)) + startColumn } .takeWhile { it <= end } fun MutableList.addItemStack(internalName: NEUInternalName) { addItemStack(internalName.getItemStack()) } // TODO move to RenderableUtils inline fun > MutableList.addSelector( prefix: String, getName: (T) -> String, isCurrent: (T) -> Boolean, crossinline onChange: (T) -> Unit, ) { add(Renderable.horizontalContainer(buildSelector(prefix, getName, isCurrent, onChange))) } inline fun > MutableList.addSearchableSelector( prefix: String, getName: (T) -> String, isCurrent: (T) -> Boolean, crossinline onChange: (T) -> Unit, ) { add(Renderable.horizontalContainer(buildSelector(prefix, getName, isCurrent, onChange)).toSearchable()) } // TODO move to RenderableUtils inline fun > buildSelector( prefix: String, getName: (T) -> String, isCurrent: (T) -> Boolean, crossinline onChange: (T) -> Unit, ) = buildList { addString(prefix) for (entry in enumValues()) { val display = getName(entry) if (isCurrent(entry)) { addString("§a[$display]") } else { addString("§e[") add( Renderable.link("§e$display") { onChange(entry) }, ) addString("§e]") } addString(" ") } } // TODO move to RenderableUtils inline fun MutableList.addButton( prefix: String, getName: String, crossinline onChange: () -> Unit, tips: List = emptyList(), ) { val onClick = { if ((System.currentTimeMillis() - ChatUtils.lastButtonClicked) > 150) { // funny thing happen if I don't do that onChange() SoundUtils.playClickSound() ChatUtils.lastButtonClicked = System.currentTimeMillis() } } add( Renderable.horizontalContainer( buildList { addString(prefix) addString("§a[") if (tips.isEmpty()) { add(Renderable.link("§e$getName", false, onClick)) } else { add(Renderable.clickAndHover("§e$getName", tips, false, onClick)) } addString("§a]") }, ), ) } // TODO move to RenderableUtils fun Collection>.tableStretchXPadding(xSpace: Int): Int { if (this.isEmpty()) return xSpace val off = RenderableUtils.calculateTableXOffsets(this as List>, 0) val xLength = off.size - 1 val emptySpace = xSpace - off.last() if (emptySpace < 0) { // throw IllegalArgumentException("Not enough space for content") } return emptySpace / (xLength - 1) } fun Collection>.tableStretchYPadding(ySpace: Int): Int { if (this.isEmpty()) return ySpace val off = RenderableUtils.calculateTableYOffsets(this as List>, 0) val yLength = off.size - 1 val emptySpace = ySpace - off.last() if (emptySpace < 0) { // throw IllegalArgumentException("Not enough space for content") } return emptySpace / (yLength - 1) } /** Splits the input into equal sized lists. If the list can't get divided clean by [subs] then the last entry gets reduced. e.g. 13/4 = [4,4,4,1]*/ fun Collection.split(subs: Int = 2): List> { if (this.isEmpty()) return listOf(emptyList()) val list = this.chunked(ceil(this.size.toDouble() / subs.toDouble()).toInt()).toMutableList() while (list.size < subs) { list.add(emptyList()) } return list } inline fun Map.mapKeysNotNull(transform: (Map.Entry) -> R?): Map { val destination = LinkedHashMap() for (element in this) { val newKey = transform(element) if (newKey != null) { destination[newKey] = element.value } } return destination } inline fun Iterable.sumOfPair(selector: (T) -> Pair): Pair { var sum = Pair(0.0, 0.0) for (element in this) { val add = selector(element) sum = sum.first + add.first.toDouble() to sum.second + add.second.toDouble() } return sum } inline fun Iterable.zipWithNext3(transform: (a: T, b: T, c: T) -> R): List { val iterator = iterator() if (!iterator.hasNext()) return emptyList() var one = iterator.next() if (!iterator.hasNext()) return emptyList() var two = iterator.next() val result = mutableListOf() while (iterator.hasNext()) { val next = iterator.next() result.add(transform(one, two, next)) one = two two = next } return result } fun Iterable.zipWithNext3(): List> { return zipWithNext3 { a, b, c -> Triple(a, b, c) } } fun Map.filterNotNullKeys(): Map { return filterKeys { it != null } as Map } /** * Inserts the element at the index or appends it to the end if out of bounds of the list. * * @param index index to insert at, or append if >= size * @param element element to insert or add */ fun MutableList.addOrInsert(index: Int, element: E) { if (index < size) add(index, element) else add(element) } }