import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.util.regex.Pattern
import java.util.stream.Collectors

static List<Path> getInterfaces(String path) {
    Files.walk(Paths.get(path), 1).withCloseable { stream ->
        return stream
            .filter { file -> file.fileName.toString().endsWith("Assertions.kt") }
            .sorted(new Comparator<Path>() {
                @Override
                int compare(Path a, Path b) {
                    return a.getFileName().toString() <=> b.getFileName().toString()

                }
            }).collect(Collectors.toList())
    }
}

def createGenerateLogicTask(
    Project project,
    String implsFileName,
    String suffix = '',
    Map<String, Closure<Tuple3<String, String, String>>> additionalPackages = [:]
) {
    def generateLogic = task('generateLogic', description: 'generates ext. methods for AssertionContainer based on interfaces') {}
    Map<String, Closure<Tuple3<String, String, String>>> all = (['': { Path p ->
        new Tuple3(
            'import ch.tutteli.atrium.creating.AssertionContainer',
            '<T> AssertionContainer<T>',
            'getImpl'
        )
    }] + additionalPackages)
    all.forEach { relativePackagePath, f ->
        def task = createGenerateLogicTaskForPackage(project, implsFileName, relativePackagePath, suffix, f)
        generateLogic.dependsOn(task)
    }
    return generateLogic
}

static String getType(Path input) {
    def fileName = input.getFileName().toString()
    return fileName.substring(0, fileName.length() - "Assertions.kt".length())
}

def createGenerateLogicTaskForPackage(
    Project project,
    String implsFileName,
    String relativePackagePath,
    String suffix,
    Closure<Tuple3<String, String, String>> f
) {

    String generatedFolder = project.generatedFolder

    return task("generateLogic_${relativePackagePath.replaceAll('/', '_')}", description: "generates ext. methods for package $relativePackagePath") {
        def packagePath = 'ch/tutteli/atrium/logic' + relativePackagePath + (suffix != '' ? "/" + suffix : '')
        def fullPackage = packagePath.replaceAll('/', '.')
        def path = "$project.projectDir/src/main/kotlin/$packagePath/"
        def interfaces = getInterfaces(path)
        Map<Path, Tuple3<String, String, String>> interfacesWithTuple =
            interfaces.collectEntries { [it, f(it)] }

        def generatedPath = "$project.projectDir/$generatedFolder/$packagePath"
        inputs.files(interfaces)
        outputs.dir(generatedPath)

        def ln = System.lineSeparator()

        doFirst {

            //TODO delete all files in folder first (as we might have removed things)

            def header = """\
                // @formatter:off
                //---------------------------------------------------
                //  Generated content, modify:
                //  logic/generateLogic.gradle
                //  if necessary - enjoy the day 🙂
                //---------------------------------------------------

                """.stripIndent().replace("\n", ln)

            def identifier = /[a-zA-Z0-9]+/
            def newLine = /(?:\r\n?|\n)/
            def newLineAndIndent = /$newLine\s*/
            def parameter = { paramNumber -> /,(?: |$newLineAndIndent)(?<paramName$paramNumber>$identifier): (?<typeName$paramNumber>[^:]+?)/}
            def returnType = /(?:$newLineAndIndent)?\)(?<returnType>:.+)/

            def typeIdentifier = / *fun (?<generics><.+?> )?(?<funcName>$identifier)\((?:$newLineAndIndent)?$identifier: (?<type>$identifier(?:\.$identifier)*<.+>)/
            def patterns = (6..0).collect { int numOfParams ->
                def params = numOfParams > 0 ? (1..numOfParams) : []
                new Tuple3<Pattern, String, String>(
                    Pattern.compile(typeIdentifier + params.collect { paramNumber -> parameter(paramNumber)}.join("") + returnType),
                    /fun ${ '${generics}${type}.${funcName}' }\(/ + params.collect { paramNumber -> /${'${paramName' + paramNumber + '}'}: ${ '${typeName' + paramNumber + '}'}/ }.join(", ") + /\)${ '${returnType}' } =/ + (numOfParams > 1 ? "$ln    " : " "),
                    /.${ '${funcName}' }\(this/ + (numOfParams > 0 ? ", " : "") + params.collect { paramNumber -> /${ '${paramName'+ paramNumber + '}'}/ }.join(", ") + /\)/
                )
            }

            interfacesWithTuple.forEach { interfacePath, tuple ->
                def type = getType(interfacePath)
                def (String importStatements,
                String      extensionTypeSignature,
                String      getImpl) = tuple

                def uncapitalized = type.uncapitalize()
                def output = new File("$generatedPath/${uncapitalized}.kt")
                def content = interfacePath.text
                def interfaceName = "${type}Assertions"
                def implValName = "impl"

                String tmp = content.replaceAll(/($newLine\/\*\*[\S\s]+?\*\/)?${newLine}interface $interfaceName \{/,
                    """\
                    import ch.tutteli.atrium.core.ExperimentalNewExpectTypes
                    import ${fullPackage}.impl.Default${type}Assertions
                    """.stripIndent().replace("\n", ln))
                patterns.forEach { triple ->
                    tmp = tmp.replaceAll(triple.first, triple.second + implValName + triple.third)
                }

                String generatedContent = tmp.substring(0, tmp.lastIndexOf("}"))
                output.withWriter('utf-8') { w ->
                    w << header
                    w << generatedContent
                    w << """\

                    @Suppress("DEPRECATION" /* OptIn is only available since 1.3.70 which we cannot use if we want to support 1.2 */)
                    @UseExperimental(ExperimentalNewExpectTypes::class)
                    private inline val ${extensionTypeSignature}.impl: ${type}Assertions
                        get() = $getImpl(${type}Assertions::class) { Default${type}Assertions() }
                     """.stripIndent().replace("\n", ln)
                }
            }
        }
    }
}

ext {
    createGenerateLogicTask = this.&createGenerateLogicTask
    generatedFolder = "src/generated/kotlin"
}
