package alraune

import alraune.Al.*
import alraune.operations.*
import vgrechka.*
import java.time.Instant
import java.time.ZoneId
import java.time.ZonedDateTime
import java.util.*
import kotlin.reflect.KProperty0
import org.apache.tools.ant.Project
import org.apache.tools.ant.taskdefs.Copy
import org.apache.tools.ant.taskdefs.Delete
import org.apache.tools.ant.taskdefs.optional.ssh.SSHExec
import org.apache.tools.ant.types.FileSet
import java.io.File
import java.io.FileOutputStream
import java.io.OutputStreamWriter
import java.time.format.DateTimeFormatter
import kotlin.reflect.KClass

object Alk {

    /**
     * `prefix` should be like "--foo="
     */
    fun getNamedCmdLineArgumentValue(args: Array<String>, prefix: String): String? {
        val arg = args.find {it.startsWith(prefix)} ?: return null
        return arg.substring(prefix.length)
    }

    fun getNamedCmdLineArgumentValueOrBitch(args: Array<String>, prefix: String) =
        getNamedCmdLineArgumentValue(args, prefix)
            ?: bitch("I want `$prefix`")

    fun checkMaxOneNonNull(vararg props: KProperty0<Any?>) {
        val nonNullProps = props.filter {it.get() != null}
        check(nonNullProps.size <= 1) {
            fun names(xs: List<KProperty0<Any?>>) = xs.joinToString(", ") {it.name}
            "\nAt most one of following should be non-null: ${names(props.toList())}" +
            "\nGot following ${nonNullProps.size} non-null: ${names(nonNullProps)}"
        }
    }


    fun remoteExec(cmd: String) {
        SSHExec().let {
            imf()
//            it.project = Project()
//            it.host = AlConfig.debug.stagingIP
//            it.setUsername(AlConfig.debug.stagingUser)
//            it.setKeyfile(AlConfig.debug.privateKeyFileForSendingFromLocalToStaging)
//            it.setTrust(true)
//            it.setCommand(cmd)
//            clog("Executing on ${it.host}: $cmd")
//            it.execute()
        }
    }


    fun <T : AlCtlCommand> killAlCtlCommand(commandClass: KClass<T>, configName: String) {
        AlProcess.bash(""
            + """ps aux"""
            + """ | grep '${AlExecuteCtlCommand::class.qualifiedName!!.replace(".", "\\.")} ${ctlCommandName(commandClass)} ${AlConst.configArgPrefix}$configName'"""
            + """ | grep -v grep"""
            + """ | awk '{print "["${'$'}2"]"}'"""
            + """ | grep -v '\[${AlProcess.myProcessID()}\]'"""
            + """ | awk '{gsub(/\[|\]/, ""); print}'"""
            + """ | xargs kill -9"""
        )
    }

    fun killStartedBackend(configName: String) {
        killAlCtlCommand(AlCtl_StartBackground::class, configName)
        killAlCtlCommand(AlCtl_StartForeground::class, configName)
    }

    fun hotRemoteInvoke(clazz: KClass<*>, methodName: String) {
        imf()

//        AlExecuteCtlCommand.main(arrayOf(
//            ctlCommandName(AlCtl_PushHotCodeFromIdeaToStaging::class),
//            AlConst.configArgPrefix + "dev",
//            AlCtl_PushHotCodeFromIdeaToStaging.CmdLine.quietUnzip))
//
//        remoteExec(
//            AlConst.alCtlPath(AlConfig.debug.stagingShitDir) +
//                " " + ctlCommandName(AlCtl_InstantiateClassAndInvokeMethod::class) +
//                " " + AlConst.configArgPrefix + "dev" +
//                " " + AlCtl_InstantiateClassAndInvokeMethod.CmdLine.classPrefix + clazz.qualifiedName +
//                " " + AlCtl_InstantiateClassAndInvokeMethod.CmdLine.methodPrefix + methodName)
    }

    fun generateVersionNameFromCurrentKievTime(prefix: String): String {
        val inKiev = TimePile.inKievZone(System.currentTimeMillis())
        return prefix + "__" + DateTimeFormatter.ofPattern("yyyy-MM-dd__HH-mm-ss").format(inKiev)
    }

    class Project(val rootRelativeToFeDir: String) {
        private val classes = "/out/production/classes"
        private val resources = "/out/production/resources"

        val root = FEProps.feDir + "/" + rootRelativeToFeDir
        val ideaOutputClasses = root + classes
        val ideaOutputClassesRelativeToFeDir = rootRelativeToFeDir + classes
        val ideaOutputResources = root + resources
        val ideaOutputResourcesRelativeToFeDir = rootRelativeToFeDir + resources
    }

    object Projects {
        val all = mutableListOf<Project>()

        val alraune = add("alraune/alraune")
        val alrauneAp = add("alraune/alraune-ap")
        val alrauneApLight = add("alraune/alraune-ap-light")
        val alraunePrivate = add("fe-private/alraune-private")
        val hotReloadableIdeaPieceOfShit = add("hot-reloadable-idea-piece-of-shit")
        val ideaBackdoorClient = add("idea-backdoor-client")
        val jaco0 = add("jaco-0")
        val sharedIdea = add("shared-idea")
        val sharedJvm = add("1/shared-jvm")

        fun allIdeaOutputPaths() = all.map {it.ideaOutputClasses} + all.map {it.ideaOutputResources}

        private fun add(path: String): Project {
            val project = Project(path)
            all += project
            return project
        }
    }

    fun writeDistrPropertiesFile(classpathDirectory: String, version: String) {
        val newPropsFile = File(classpathDirectory + "/" + AlConfig.distr.resourceName)
        newPropsFile.parentFile.mkdirs()
        OutputStreamWriter(FileOutputStream(newPropsFile), Charsets.UTF_8).use {wr ->
            val newProps = Properties()
            newProps.putAll(AlConfig.distr.props.raw)
            newProps.setProperty(AlConfig.distr::version.name, version)
            newProps.store(wr, null)
        }
    }

    fun remoteRestartApache(remoteExec: AlRemoteExec) {
        remoteExec.exec("sudo apachectl restart")
    }

    fun createDirOrCleanIfExists(dir: File) {
        if (!dir.exists())
            return check(dir.mkdirs()) {"Can't make your freaking dirs: ${dir.path}"}

        check(dir.isDirectory) {"I want this to be a directory: ${dir.path}"}

        for (child in dir.listFiles()) {
            when {
                child.isFile -> check(child.delete()) {"Can't fucking delete this: ${child.path}"}
                child.isDirectory -> Delete().also {
                    it.project = Project()
                    it.setDir(child)
                    it.execute()
                }
                else -> bitch("What the fuck is this: ${child.path}")
            }
        }
    }

    fun copyMyClasspathLibsToDir1(dirPath: String) {
        copyMyClasspathEntriesExt(
            putFileEntriesToDirPath = dirPath,
            considerEntryPath = {path ->
                path.contains(File.separator + ".gradle" + File.separator + "caches" + File.separator).also {
                    // if (!it) clog("--- Not including: $path")
                }
                /*|| it.contains(File.separator + "out" + File.separator + "production" + File.separator)*/
            }
        )
    }

    fun copyMyClasspathEntriesExt(considerEntryPath: (String) -> Boolean,
                                  putFileEntriesToDirPath: String? = null,
                                  putChildrenOfDirEntriesToDirPath: String? = null)
    {
        putFileEntriesToDirPath?.let {rmRfAndCreateDir(File(it))}
        putChildrenOfDirEntriesToDirPath?.let {rmRfAndCreateDir(File(it))}

        var jarIndex = 1
        val entries = System.getProperty("java.class.path")
            .split(File.pathSeparator)
            .filter {
                // clog("entry: " + it)
                !isJdkPart(File(it)) && considerEntryPath(it)
            }
        val antProject = Project()
        for (entry in entries) {
            // clog("entry: $entry")
            val entryFile = File(entry)
            if (entryFile.isDirectory && putChildrenOfDirEntriesToDirPath != null) {
                Copy().let {
                    it.project = antProject
                    it.setTodir(File(putChildrenOfDirEntriesToDirPath))
                    it.addFileset(FileSet().also {
                        it.dir = entryFile
                        it.setIncludes("**/*")
                    })
                    it.execute()
                }
            } else if (entryFile.isFile && putFileEntriesToDirPath != null) {
                Copy().let {
                    it.project = antProject
                    it.setFile(entryFile)
                    val indexPrefix = String.format("%03d", jarIndex++)
                    it.setTofile(File("$putFileEntriesToDirPath/$indexPrefix-${entryFile.name}"))
                    it.execute()
                }
            }
        }
    }

    private fun isJdkPart(file: File): Boolean {
        if (file.name.contains("kotlin"))
            return false
        if (file.isDirectory && file.name.contains("jdk") || file.name.contains("jre"))
            return true
        val parentFile = file.parentFile ?: return false
        return isJdkPart(parentFile)
    }

    fun envFromCommandLine(args: Array<String>): AlEnvironmentParams {
        return instantiateAndInvokeFromCommandLine(args, "env", AlEnvironmentParams::class.java)
    }


    fun copyDir(path: String, toDir: String) {
        val dir = File(path)
        check(dir.isDirectory) {"I want this to be a directory: $path"}

        Copy().let {
            it.project = Project()
            it.addFileset(FileSet().also {
                it.dir = dir.parentFile
                it.setIncludes("${dir.name}/**")
            })
            it.setTodir(File(toDir))
            it.execute()
        }
    }

    fun currentUtcTimeAsPartOfFileName() =
        TimePile.inUTCZone(System.currentTimeMillis()).toString()
            .replace(':', '-')
            .replace('[', '_')
            .replace("]", "")

}

fun formatFileSizeApprox(loc: AlLocale, totalBytes: Int): String {
    val kb = 1024
    val mb = 1024 * kb
    val gb = 1024 * mb

    if (totalBytes >= gb) bitch("You fucking crazy, I'm not dealing with gigabyte files")

    val point = when (loc) {
        AlLocale.EN -> "."
        AlLocale.UA -> ","
    }

    val megs = totalBytes / mb
    val kils = (totalBytes - megs * mb) / kb
    val bytes = totalBytes - megs * mb * kils * kb

    if (megs > 0) return "" +
        megs +
        (if (kils >= 100) "$point${kils / 100}" else "") +
        when (loc) {
            AlLocale.EN -> " MB"
            AlLocale.UA -> " МБ"
        }

    if (kils > 0) return "" +
        kils +
        when (loc) {
            AlLocale.EN -> " KB"
            AlLocale.UA -> " КБ"
        }

    return "" +
        bytes +
        when (loc) {
            AlLocale.EN -> " B"
            AlLocale.UA -> " Б"
        }
}














