package at.hannibal2.skyhanni.utils import at.hannibal2.skyhanni.utils.NEUItems.getItemStack import at.hannibal2.skyhanni.utils.renderables.Renderable import net.minecraft.item.ItemStack import java.util.Collections import java.util.Queue import java.util.WeakHashMap 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() } } 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 } 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 } fun MutableList>.addAsSingletonList(text: E) { add(Collections.singletonList(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 } } } /** 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 internal name support, and caching fun MutableList.addItemStack(itemStack: ItemStack) { add(Renderable.itemStack(itemStack)) } fun MutableList.addItemStack(internalName: NEUInternalName) { addItemStack(internalName.getItemStack()) } 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 > 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(" ") } } 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]") })) } 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 } }