package org.jetbrains.kmark.test

import org.junit.runner.*
import org.junit.runner.notification.*
import java.io.File
import org.junit.runners.ParentRunner
import java.io.Serializable
import kotlin.properties.Delegates
import org.junit.ComparisonFailure

data class MarkdownTestUniqueId(val id: Int) : Serializable {
    default object {
        var id = 0
        fun next() = MarkdownTestUniqueId(id++)
    }
}

public open class MarkdownSpecification(val path: String, val processor: (String) -> String)


trait MarkdownTest {
    fun description(): Description
}

public open class MarkdownTestCase(val spec: MarkdownSpecification, val input: String, val expected: String) : MarkdownTest, Runner() {
    val _description by Delegates.lazy {
        Description.createSuiteDescription(input, MarkdownTestUniqueId.next())!!
    }

    override fun description(): Description = _description

    override fun getDescription(): Description? = description()
    override fun run(notifier: RunNotifier?) {
        notifier!!

        notifier.fireTestStarted(_description)
        val result = spec.processor(input)
        when (result) {
            expected -> notifier.fireTestFinished(_description)
            else -> notifier.fireTestFailure(Failure(_description, ComparisonFailure("Output mismatch", expected, result)))
        }
    }
}

public open class MarkdownTestSection(val spec: MarkdownSpecification, val title: String) : MarkdownTest, ParentRunner<MarkdownTest>(spec.javaClass) {
    val children = arrayListOf<MarkdownTest>();

    val _description by Delegates.lazy {
        val desc = Description.createSuiteDescription(title, MarkdownTestUniqueId.next())!!
        for (item in getChildren()!!) {
            desc.addChild(describeChild(item))
        }
        desc
    }

    override fun description(): Description = _description

    override fun getChildren(): MutableList<MarkdownTest>? = children

    override fun describeChild(child: MarkdownTest?): Description? = child!!.description()

    override fun runChild(child: MarkdownTest?, notifier: RunNotifier?) {
        notifier!!
        when (child) {
            is MarkdownTestCase -> child.run(notifier)
            is MarkdownTestSection -> {
                if (child.children.size == 0) {
                    notifier.fireTestStarted(child.description())
                    notifier.fireTestFinished(child.description())
                } else {
                    child.run(notifier)
                }
            }
        }
    }
}

public class MarkdownTestRunner(specificationClass: Class<MarkdownSpecification>) : MarkdownTestSection(specificationClass.newInstance(), "Tests") {
    {
        val lines = File(spec.path).readLines()
        createSections(this, lines, 1)
    }

    private fun createTests(parent: MarkdownTestSection, lines: List<String>): Int {
        val testMark = lines.takeWhile { it.trim() != "." }
        val testHtml = lines.drop(testMark.size).drop(1).takeWhile { it.trim() != "." }
        val markdown = testMark.join("\n", postfix = "\n", prefix = "\n")
        val html = testHtml.join("\n", postfix = "\n")
        val markdownTestCase = MarkdownTestCase(spec, markdown, html)
        parent.children.add(markdownTestCase)
        return testMark.size + testHtml.size + 3
    }

    private fun createSections(parent: MarkdownTestSection, lines: List<String>, level: Int): Int {
        var sectionNumber = 1
        var index = 0
        while (index < lines.size) {
            val line = lines[index]

            if (line.trim() == ".") {
                index = createTests(parent, lines.subList(index + 1, lines.lastIndex)) + index + 1
                continue
            }

            val head = line.takeWhile { it == '#' }.length
            if (head == 0) {
                index++
                continue
            }

            if (head < level) {
                return index
            }

            if (head == level) {
                val title = lines[index].dropWhile { it == '#' }.dropWhile { it.isWhitespace() }
                sectionNumber++
                val section = MarkdownTestSection(spec, title)
                val lastIndex = createSections(section, lines.subList(index + 1, lines.lastIndex), level + 1) + index + 1
                if (section.children.size > 0)
                    parent.children.add(section)
                val nextHead = lines[lastIndex].takeWhile { it == '#' }.length
                if (nextHead < level) {
                    return lastIndex
                }
                index = lastIndex
                continue
            }
            index++
        }
        return lines.size
    }
}