/*
 * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
 * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
 */

package org.jetbrains.kotlin.pill

import org.jetbrains.kotlin.pill.model.*
import org.jetbrains.kotlin.pill.util.*
import java.io.File

class PFile(val path: File, private val text: String) {
    constructor(path: File, xml: XmlNode) : this(path, xml.toString())

    fun write() {
        path.parentFile.mkdirs()
        path.writeText(text)
    }
}

fun render(project: PProject): List<PFile> {
    val files = mutableListOf<PFile>()

    files += renderModulesFile(project)
    project.modules.forEach { files += renderModule(project, it) }
    project.libraries.forEach { files += renderLibrary(project, it) }

    return files
}

private fun renderModulesFile(project: PProject) = PFile(
    File(project.rootDirectory, ".idea/modules.xml"),
    xml("project", "version" to 4) {
        xml("component", "name" to "ProjectModuleManager") {
            xml("modules") {
                val pathContext = ProjectContext(project)

                for (module in project.modules) {
                    val moduleFilePath = pathContext(module.moduleFile)
                    xml("module", "fileurl" to "file://$moduleFilePath", "filepath" to moduleFilePath)
                }
            }
        }
    }
)

private fun renderModule(project: PProject, module: PModule) = PFile(
    module.moduleFile,
    xml(
        "module",
        "type" to "JAVA_MODULE",
        "version" to 4
    ) {
        val moduleForProductionSources = module.moduleForProductionSources
        if (moduleForProductionSources != null) {
            xml("component", "name" to "TestModuleProperties", "production-module" to moduleForProductionSources.name)
        }

        val kotlinCompileOptions = module.kotlinOptions
        val pathContext = ModuleContext(project, module)

        val platformVersion = (kotlinCompileOptions?.jvmTarget ?: "1.8")
        val classesDirectory = File(project.rootDirectory, "out/production/${module.name}")

        if (kotlinCompileOptions != null) {
            xml("component", "name" to "FacetManager") {
                xml("facet", "type" to "kotlin-language", "name" to "Kotlin") {
                    xml("configuration", "version" to 3, "platform" to "JVM $platformVersion", "useProjectSettings" to "false") {
                        xml("compilerSettings") {
                            xml(
                                "option",
                                "name" to "additionalArguments",
                                "value" to kotlinCompileOptions.extraArguments.joinToString(" ")
                            )
                        }
                        xml("compilerArguments") {
                            xml("option", "name" to "destination", "value" to pathContext(classesDirectory))

                            fun Any?.option(name: String) {
                                if (this != null) xml("option", "name" to name, "value" to this.toString())
                            }

                            kotlinCompileOptions.noStdlib.option("noStdlib")
                            kotlinCompileOptions.noReflect.option("noReflect")
                            module.name.option("moduleName")
                            xml("option", "name" to "jvmTarget", "value" to platformVersion)
                            kotlinCompileOptions.languageVersion.option("languageVersion")
                            kotlinCompileOptions.apiVersion.option("apiVersion")

                            xml("option", "name" to "pluginOptions") { xml("array") }
                            xml("option", "name" to "pluginClasspaths") {
                                xml("array") {
                                    for (path in kotlinCompileOptions.pluginClasspath) {
                                        xml("option", "value" to path)
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        xml(
            "component",
            "name" to "NewModuleRootManager",
            "LANGUAGE_LEVEL" to "JDK_${platformVersion.replace('.', '_')}",
            "inherit-compiler-output" to "true"
        ) {
            xml("exclude-output")

            for (contentRoot in module.contentRoots.filter { it.path.exists() }) {
                xml("content", pathContext.url(contentRoot.path)) {
                    for (sourceRoot in contentRoot.sourceRoots) {
                        var args = arrayOf(pathContext.url(sourceRoot.directory))

                        args += when (sourceRoot.kind) {
                            PSourceRoot.Kind.PRODUCTION -> ("isTestSource" to "false")
                            PSourceRoot.Kind.TEST -> ("isTestSource" to "true")
                            PSourceRoot.Kind.RESOURCES -> ("type" to "java-resource")
                            PSourceRoot.Kind.TEST_RESOURCES -> ("type" to "java-test-resource")
                        }

                        xml("sourceFolder", *args)
                    }

                    for (excludedDir in contentRoot.excludedDirectories) {
                        xml("excludeFolder", pathContext.url(excludedDir))
                    }
                }
            }

            when (val javaLanguageVersion = module.javaLanguageVersion) {
                null -> xml("orderEntry", "type" to "inheritedJdk")
                else -> xml("orderEntry", "type" to "jdk", "jdkName" to javaLanguageVersion.toString(), "jdkType" to "JavaSDK")
            }

            xml("orderEntry", "type" to "sourceFolder", "forTests" to "false")

            for (orderRoot in module.orderRoots) {
                val dependency = orderRoot.dependency

                val args = when (dependency) {
                    is PDependency.ModuleLibrary -> mutableListOf(
                        "type" to "module-library"
                    )
                    is PDependency.Module -> mutableListOf(
                        "type" to "module",
                        "module-name" to dependency.module.name
                    )
                    is PDependency.Library -> mutableListOf(
                        "type" to "library",
                        "name" to dependency.name,
                        "level" to "project"
                    )
                }

                if (orderRoot.scope != POrderRoot.Scope.COMPILE) {
                    args.add(1, "scope" to orderRoot.scope.toString())
                }

                if (!module.forTests && dependency is PDependency.Module && dependency.module.forTests) {
                    args += ("production-on-test" to "")
                }

                if (orderRoot.isExported) {
                    args += ("exported" to "")
                }

                xml("orderEntry", *args.toTypedArray()) {
                    if (dependency is PDependency.ModuleLibrary) {
                        add(renderLibraryToXml(dependency.library, pathContext, named = false))
                    }
                }
            }
        }
    }
)

private fun renderLibrary(project: PProject, library: PLibrary): PFile {
    val pathContext = ProjectContext(project)

    // TODO find how IDEA escapes library names
    val escapedName = library.renderName().replace(" ", "_").replace(".", "_").replace("-", "_")

    return PFile(
        File(project.rootDirectory, ".idea/libraries/$escapedName.xml"),

        xml("component", "name" to "libraryTable") {
            add(renderLibraryToXml(library, pathContext))
        })
}

private fun renderLibraryToXml(library: PLibrary, pathContext: PathContext, named: Boolean = true): XmlNode {
    val args = if (named) arrayOf("name" to library.renderName()) else emptyArray()

    return xml("library", *args) {
        xml("CLASSES") {
            library.classes.forEach { xml("root", pathContext.url(it)) }
        }

        xml("JAVADOC") {
            library.javadoc.forEach { xml("root", pathContext.url(it)) }
        }

        xml("SOURCES") {
            library.sources.forEach { xml("root", pathContext.url(it)) }
        }
    }
}

fun PLibrary.renderName() = name.takeIf { it != "unspecified" } ?: classes.first().nameWithoutExtension
