/*
 * Licensed Materials - Property of IBM Corp.
 * IBM UrbanCode Deploy
 * (c) Copyright IBM Corporation 2016. All Rights Reserved.
 *
 * U.S. Government Users Restricted Rights - Use, duplication or disclosure restricted by
 * GSA ADP Schedule Contract with IBM Corp.
 */
package com.urbancode.air.plugin.cf.ic.helper

import com.urbancode.air.CommandHelper
import com.urbancode.air.ExitCodeException

import groovy.json.JsonException
import groovy.json.JsonSlurper

class CFICHelper {
    private CommandHelper ch
    private commandPath = "cf"

    public CFICHelper(def workDir, def commandPath) {
        ch = new CommandHelper(workDir)

        if (commandPath.trim()) {
            this.commandPath = commandPath.trim()
        }
    }

    public def installCFICPlugin(def pathToPlugin) {
        def osName = System.getProperty('os.name').toLowerCase()
        def osArchitecture = System.getProperty('os.arch')
        def icPluginPath = "https://static-ice.ng.bluemix.net/ibm-containers-"

        if (pathToPlugin.trim()) {
            icPluginPath = pathToPlugin.trim()
        }
        else {
            if ((osName =~ /(?i)windows/).find()) {
                icPluginPath += (osArchitecture =~ /(?i)64/).find() ? "windows_x64.exe" : "windows_x86.exe"
            }
            else if ((osName =~ /(?i)linux/).find()) {
                icPluginPath += (osArchitecture =~ /(?i)64/).find() ? "linux_x64" : "linux_x86"
            }
            else if ((osName =~ /(?i)mac/).find()) {
                icPluginPath += "mac"
            }
            else {
                Println("Operating system not supported, please provide either a local path"
                        + "or url to the IBM Containers plugin installation for your operating system.")
                throw new RuntimeException("${osName} is not a supported operating system.")
            }
        }

        def cmdArgs = [
            commandPath,
            "install-plugin",
            icPluginPath
        ]

        runHelperCommand("Installing IBM Containers plugin", cmdArgs)
        return icPluginPath
    }

    public void uninstallCFICPlugin() {
        def pluginName = "IBM-Containers"
        def cmdArgs = [
            commandPath,
            "uninstall-plugin",
            pluginName
        ]

        runHelperCommand("Uninstalling IBM-Containers plugin", cmdArgs)
    }

    // refresh the bearer token and reinitialize the CLI
    public def initialize() {
        def cmdArgs = [commandPath, "ic", "init"]

        def outputStream = runHelperWithOutput("Initializing the Cloud Foundry Containers CLI", cmdArgs)
        def exports = []

        // parse export statements into array
        int beginIndex = outputStream.indexOf("export") // first export start
        outputStream.delete(0, beginIndex + 1) // remove all before first export
        int endIndex = outputStream.lastIndexOf("export") // last export start
        outputStream.delete(endIndex, outputStream.length()) // remove everything from last import

        exports.addAll(outputStream.toString().split('\n'))

        return exports
    }

    // login to Cloud Foundry
    public void login(def user, def password , def api, def org, def space) {
        def cmdArgs = [
            commandPath,
            "login",
            "-u",
            user.trim(),
            "-p",
            password.trim(),
            "-a",
            api.trim()
        ]

        if (org) {
            cmdArgs.addAll("-o", org)
        }

        if (space) {
            cmdArgs.addAll("-s", space)
        }

        runHelperCommand("Logging into Cloud Foundry with API Endpoint ${api}", cmdArgs)
    }

    // logout of Cloud Foundry
    public void logout() {
        def cmdArgs = [commandPath, "logout"]

        runHelperCommand("Logging out of Cloud Foundry", cmdArgs)
    }

    // assign the current organization with a namespace
    public void setNamespace(def namespace, def onFail) {
        def cmdArgs = [
            commandPath,
            "ic",
            "namespace",
            "set",
            namespace
        ]

        def response = ""

        try {
            def outputStream = runHelperWithOutput("Setting organization's namespace to ${namespace}", cmdArgs)
            response = outputStream.toString()
        }
        catch(ExitCodeException ex) {
            JsonSlurper slurper = new JsonSlurper()
            def responseJson

            def beginIndex = response.indexOf('{')
            def endIndex = response.indexOf('}')

            // parse json body returned when namespace is already assigned
            if (beginIndex != -1 && endIndex != -1) {
                def substring = response.substring(beginIndex, endIndex + 1)
                response = substring.split('\n').join()
                responseJson = slurper.parseText(response)
            }
            else {
                throw new ExitCodeException(response, ex)
            }

            def responseName = responseJson.name
            def responseDescription = responseJson.description
            println("Error thrown with name: ${responseName} and description: ${responseDescription}")

            if (onFail == "warn" && responseName == "NamespaceAlreadyAssigned") {
                println("Process execution will continue due to warn on failure.")
            }
            else {
                println("Process will now exit.")
                throw ex
            }
        }
    }

    public void buildImage(def dockerfile, def tag, def additionalArgs) {
        def cmdArgs = [
            commandPath,
            "ic",
            "build",
            "-t",
            tag,
            dockerfile
        ]

        if (additionalArgs.trim()) {
            cmdArgs.addAll(parseArgs(additionalArgs))
        }

        runHelperCommand("Building Docker image", cmdArgs)
    }

    public void runImage(def image, def name, def ports, boolean exposedPorts, def command, def bindApp, def additionalArgs) {
        def cmdArgs = [commandPath, "ic", "run"]

        if (name.trim()) {
            cmdArgs.addAll("--name", name.trim())
        }

        if (ports.trim()) {
            cmdArgs << "-p"
            cmdArgs.addAll(parseArgs(ports))
        }

        if (exposedPorts) {
            cmdArgs << "-P"
        }

        if (bindApp.trim()) {
            cmdArgs.addAll("-e", "CCS_BIND_APP=${bindApp.trim()}")
        }

        cmdArgs.addAll(parseArgs(additionalArgs))

        cmdArgs << image.trim()

        if (command.trim()) {
            cmdArgs << command.trim()
        }

        runHelperCommand("Starting Container using image: ${image}", cmdArgs)
    }

    // request a floating ip address in your space and set it as an output property
    public def requestIP() {
        def cmdArgs = [
            commandPath,
            "ic",
            "ip",
            "request"
        ]

        def outputStream = runHelperWithOutput("Acquiring IP address", cmdArgs)
        def ipAddress = ""

        // parse out ip address from output
        int beginIndex = outputStream.indexOf('"')
        outputStream.delete(0, beginIndex + 1)
        int endIndex = outputStream.lastIndexOf('"')
        outputStream.delete(endIndex, outputStream.length())

        ipAddress = outputStream.toString()

        return ipAddress
    }

    // release the IP address
    public def releaseIP(def ipAddress) {
        def cmdArgs = [
            commandPath,
            "ic",
            "ip",
            "release",
            ipAddress
        ]

        runHelperCommand("Releasing IP address: ${ipAddress}", cmdArgs)

        return ipAddress
    }

    public def bindIp(def ipAddress, def container) {
        def cmdArgs = [
            commandPath,
            "ic",
            "ip",
            "bind",
            ipAddress,
            container
        ]

        runHelperCommand("Binding IP address: ${ipAddress} to container: ${container}", cmdArgs)
    }

    // determine if an ip address is bound to a container and unbind it
    public def unbindIp(def ipAddress, def container) {
        if (container.trim()) {
            cmdArgs << container
        }
        // determine the container by checking which container is bound to the ip address
        else {
            def addressArgs = [
                commandPath,
                "ic",
                "ip",
                "list"
            ]

            StringBuilder outputStream = runHelperWithOutput("Acquiring the container bound to ip address: ${ipAddress}", addressArgs)
            // parse out container from output
            int endIndex = outputStream.indexOf('\n')
            outputStream.delete(0, endIndex + 1)
            def outputLines = outputStream.toString().split('\n')

            for (def line : outputLines) {
                def elements = line.split("\\s+")
                def currentIp = elements[0].trim()
                def currentContainer = elements[1].trim()

                if (ipAddress == currentIp) {
                    container = currentContainer
                    break
                }
            }
        }

        // unbind the address from the container
        def cmdArgs = [
            commandPath,
            "ic",
            "ip",
            "unbind",
            ipAddress
        ]

        runHelperCommand("Unbinding IP address: ${ipAddress} from container: ${container}", cmdArgs)

        return container
    }

    public void executeCommand(def container, def command, def additionalArgs) {
        def cmdArgs = [commandPath, "ic", "exec"]

        if (additionalArgs.trim()) {
            cmdArgs.addAll(parseArgs(additionalArgs))
        }

        cmdArgs << container

        // CommandHelper will try to add quotes to commands with spaces
        if (command.trim()) {
            cmdArgs.addAll(command.trim().split("\\s+"))
        }

        runHelperCommand("Executing command on container: ${container}", cmdArgs)
    }

    public void startContainers(def containers) {
        def cmdArgs = [commandPath, "ic", "start"]

        for (def container : containers.split('\n')) {
            container = container.trim()

            cmdArgs << container
        }

        runHelperCommand("Starting Container(s) ${containers.toString()}", cmdArgs)
    }

    public void stopContainers(def containers) {
        def cmdArgs = [commandPath, "ic", "stop"]

        for (def container : containers.split('\n')) {
            container = container.trim()

            cmdArgs << container
        }

        runHelperCommand("Stopping container(s): ${containers.toString()}", cmdArgs)
    }

    public void createGroup(
        def image,
        def groupName,
        def hostname,
        def domain,
        def port,
        def environment,
        def minInstances,
        def maxInstances,
        def desiredInstances,
        def additionalArgs)
    {
        def cmdArgs = [commandPath, "ic", "group", "create"]

        if (hostname) {
            cmdArgs.addAll("-n", hostname)
        }
        if (domain) {
            cmdArgs.addAll("-d", domain)
        }
        if (port) {
            cmdArgs.addAll("-p", port)
        }

        if (environment) {
            for (def env : environment.split('\n')) {
                cmdArgs << "-e"
                cmdArgs.addAll(env.trim())
            }
        }

        if (minInstances) {
            cmdArgs.addAll("--min", minInstances)
        }
        if (maxInstances) {
            cmdArgs.addAll("--max", maxInstances)
        }
        if (desiredInstances) {
            cmdArgs.addAll("--desired", desiredInstances)
        }

        if (additionalArgs) {
            for (def arg : additionalArgs.split('\n')) {
                def args = arg.split("\\s+")

                cmdArgs.addAll(args)
            }
        }

        cmdArgs.addAll("--name", groupName)

        // remove spaces due to command being appended to the end of the image name
        def imageArgs = image.split("\\s+")
        cmdArgs.addAll(imageArgs)

        runHelperCommand("Creating container group: ${groupName}...", cmdArgs)
    }

    public void removeGroup(def groupName, def forced) {
        def cmdArgs = [commandPath, "ic", "group", "rm"]

        if (forced) {
            cmdArgs << "-f"
        }

        cmdArgs << groupName

        runHelperCommand("Removing container group: ${groupName}...", cmdArgs)
    }

    public void updateGroup(
        def groupName,
        def minInstances,
        def maxInstances,
        def desiredInstances,
        def autoRestart)
    {
        def cmdArgs = [commandPath, "ic", "group", "update"]

        if (minInstances) {
            cmdArgs.addAll("--min", minInstances)
        }
        if (maxInstances) {
            cmdArgs.addAll("--max", maxInstances)
        }
        if (desiredInstances) {
            cmdArgs.addAll("--desired", desiredInstances)
        }
        if (autoRestart) {
            cmdArgs << "--auto"
        }

        cmdArgs << groupName

        runHelperCommand("Updating container group: ${groupName}...", cmdArgs)
    }


    public def getBluemixRegistryUrl() {
        def cmdArgs = [commandPath, "ic", "info"]
        def output = runHelperWithOutput("Obtaining information about the container cloud service instance.",
            cmdArgs).toString().toLowerCase()

        int fromIndex = output.indexOf("host/url")
        int endIndex = output.indexOf("\n", fromIndex)

        // split line by first occurrence of ':'
        def registryUrl = output.substring(fromIndex, endIndex).split("^([^:]+:)")[1].trim()

        return registryUrl.replaceAll("http(s)?://", "")
    }

    private void runHelperCommand(def message, def cmdArgs) {
        runHelperCommand(message, cmdArgs, null)
    }

    private void runHelperCommand(def message, def cmdArgs, Closure closure) {

        if (closure) {
            ch.runCommand(message, cmdArgs, closure)
        }
        else {
            ch.runCommand(message, cmdArgs)
        }
    }

    // return outputStream from running helper command
    private def runHelperWithOutput(def message, def cmdArgs) {
        def result

        def outputSet = {
            it.out.close() // close stdin
            def out = new PrintStream(System.out, true)
            def outputStream = new StringBuilder()
            try {
                it.waitForProcessOutput(outputStream, out)
            }
            finally {
                out.flush()
            }

            result = outputStream
            def outputString = result.toString().toLowerCase()

            if (outputString.contains("not logged in")) {
                def errorMessage = "Not logged in. Either run the 'Login to Cloud Foundry'" +
                        "step or login on the agent machine manually using the 'cf login' command."
                throw new ExitCodeException(errorMessage)
            }
        }

        runHelperCommand(message, cmdArgs, outputSet)

        return result
    }

    // parse string argument fields into elements in an array list
    private def parseArgs(def args) {
        def parsed = []

        for (def arg : args.split('\n')) {
            arg = arg.trim()

            if (arg && arg.length() > 0) {
                // split argument if preceding flag is included
                if (arg.startsWith("-")) {
                    def splitArgs = arg.split(' ', 2) // split the flag from the argument
                    parsed << splitArgs[0].trim()

                    if (splitArgs[1].trim()) {
                        parsed << splitArgs[1].trim()
                    }
                }
                else {
                    parsed << arg
                }
            }
        }

        return parsed
    }
}