package com.keylesspalace.tusky

import android.text.Spannable
import com.keylesspalace.tusky.util.highlightSpans
import org.junit.Assert
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized

class SpanUtilsTest {
    @Test
    fun matchesMixedSpans() {
        val input = "one #one two: @two three : https://thr.ee/meh?foo=bar&wat=@at#hmm four #four five @five"
        val inputSpannable = FakeSpannable(input)
        highlightSpans(inputSpannable, 0xffffff)
        val spans = inputSpannable.spans
        Assert.assertEquals(5, spans.size)
    }

    @Test
    fun doesntMergeAdjacentURLs() {
        val firstURL = "http://first.thing"
        val secondURL = "https://second.thing"
        val inputSpannable = FakeSpannable("$firstURL $secondURL")
        highlightSpans(inputSpannable, 0xffffff)
        val spans = inputSpannable.spans
        Assert.assertEquals(2, spans.size)
        Assert.assertEquals(firstURL.length, spans[0].end - spans[0].start)
        Assert.assertEquals(secondURL.length, spans[1].end - spans[1].start)
    }

    @RunWith(Parameterized::class)
    class MatchingTests(private val thingToHighlight: String) {
        companion object {
            @Parameterized.Parameters(name = "{0}")
            @JvmStatic
            fun data(): Iterable<Any> {
                return listOf(
                        "@mention",
                        "#tag",
                        "https://thr.ee/meh?foo=bar&wat=@at#hmm",
                        "http://thr.ee/meh?foo=bar&wat=@at#hmm"
                )
            }
        }

        @Test
        fun matchesSpanAtStart() {
            val inputSpannable = FakeSpannable(thingToHighlight)
            highlightSpans(inputSpannable, 0xffffff)
            val spans = inputSpannable.spans
            Assert.assertEquals(1, spans.size)
            Assert.assertEquals(thingToHighlight.length, spans[0].end - spans[0].start)
        }

        @Test
        fun matchesSpanNotAtStart() {
            val inputSpannable = FakeSpannable(" $thingToHighlight")
            highlightSpans(inputSpannable, 0xffffff)
            val spans = inputSpannable.spans
            Assert.assertEquals(1, spans.size)
            Assert.assertEquals(thingToHighlight.length, spans[0].end - spans[0].start)
        }

        @Test
        fun doesNotMatchSpanEmbeddedInText() {
            val inputSpannable = FakeSpannable("aa${thingToHighlight}aa")
            highlightSpans(inputSpannable, 0xffffff)
            val spans = inputSpannable.spans
            Assert.assertTrue(spans.isEmpty())
        }

        @Test
        fun doesNotMatchSpanEmbeddedInAnotherSpan() {
            val inputSpannable = FakeSpannable("@aa${thingToHighlight}aa")
            highlightSpans(inputSpannable, 0xffffff)
            val spans = inputSpannable.spans
            Assert.assertEquals(1, spans.size)
        }

        @Test
        fun spansDoNotOverlap() {
            val begin = "@begin"
            val end = "#end"
            val inputSpannable = FakeSpannable("$begin $thingToHighlight $end")
            highlightSpans(inputSpannable, 0xffffff)
            val spans = inputSpannable.spans
            Assert.assertEquals(3, spans.size)

            val middleSpan = spans.single { span -> span.start > 0 && span.end < inputSpannable.lastIndex }
            Assert.assertEquals(begin.length + 1, middleSpan.start)
            Assert.assertEquals(inputSpannable.length - end.length - 1, middleSpan.end)
        }
    }

    class FakeSpannable(private val text: String) : Spannable {
        val spans = mutableListOf<BoundedSpan>()

        override fun setSpan(what: Any?, start: Int, end: Int, flags: Int) {
            spans.add(BoundedSpan(what, start, end))
        }

        override fun <T : Any?> getSpans(start: Int, end: Int, type: Class<T>?): Array<T> {
            val matching = if (type == null) {
                ArrayList<T>()
            } else {
                spans.filter { it.start >= start && it.end <= end && type.isAssignableFrom(it.span?.javaClass) }
                        .map { it.span }
                        .let { ArrayList(it) }
            }
            return matching.toArray() as Array<T>
        }

        override fun removeSpan(what: Any?) {
            spans.removeIf { span -> span.span == what}
        }

        override fun toString(): String {
            return text
        }

        override val length: Int
            get() = text.length

        class BoundedSpan(val span: Any?, val start: Int, val end: Int)

        override fun nextSpanTransition(start: Int, limit: Int, type: Class<*>?): Int {
            throw NotImplementedError()
        }

        override fun getSpanEnd(tag: Any?): Int {
            throw NotImplementedError()
        }

        override fun getSpanFlags(tag: Any?): Int {
            throw NotImplementedError()
        }

        override fun get(index: Int): Char {
            throw NotImplementedError()
        }

        override fun subSequence(startIndex: Int, endIndex: Int): CharSequence {
            throw NotImplementedError()
        }

        override fun getSpanStart(tag: Any?): Int {
            throw NotImplementedError()
        }
    }
}