package link.roadio.riff.ui import java.text.Normalizer import java.util.Locale /** Prepared on the A-Z/reload worker. UI reads never start sorting or wait for its monitor. * https://wiki.271.pics/Riff/1-1-0/catalog-consumption#prepared-collection-views */ internal class OrderedBrowserRows( private val source: List, private val ignoreLeadingArticles: Boolean, private val foldersFirst: Boolean, ) : BrowserRows() { private data class Prepared( val keys: List, val order: IntArray, ) @Volatile private var prepared: Prepared? = null override val ready get() = prepared != null && (source as? BrowserRows)?.ready != false override val size get() = source.size @Synchronized override fun prepare() { if (prepared != null) return (source as? BrowserRows)?.prepare() val keys = source.indices.map(source::rowKey) val normalized = keys.map { Normalizer .normalize( browseComparisonName(it.comparisonName, ignoreLeadingArticles), Normalizer.Form.NFKC, ).lowercase(Locale.ROOT) } val compare = compareBy { normalized[it] }.thenBy { keys[it].comparisonName }.thenBy { keys[it].genreId }.thenBy { keys[it].id } val order = if (foldersFirst) { keys.indices.filter { keys[it].kind == BrowserKind.FOLDER }.sortedWith(compare) + keys.indices.filter { keys[it].kind != BrowserKind.FOLDER } } else { keys.indices.sortedWith(compare) } prepared = Prepared(keys, order.toIntArray()) } private fun data(): Prepared = prepared ?: error("Unprepared browse order must not be read on UI") override fun key(index: Int) = data().let { it.keys[it.order[index]] } override fun rowId(index: Int) = key(index).id override fun mediaId(index: Int) = data().order[index].let { if (source is BrowserRows) source.mediaId(it) else source[it].mediaId } override fun playable(index: Int) = data().order[index].let { if (source is BrowserRows) source.playable(it) else source[it].playable } override fun get(index: Int) = source[data().order[index]] override fun findRow(id: String) = source.rowById(id) } internal fun List.orderedRows( ignoreLeadingArticles: Boolean, alphabetical: Boolean, foldersFirst: Boolean, ): List = if (!alphabetical && !foldersFirst) this else OrderedBrowserRows(this, ignoreLeadingArticles, foldersFirst)