/*
* Licensed Materials - Property of IBM Corp.
* IBM UrbanCode Build
* IBM UrbanCode Deploy
* IBM UrbanCode Release
* IBM AnthillPro
* (c) Copyright IBM Corporation 2002, 2013. 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.servicecontrolmanager;

import com.urbancode.air.CommandHelper;
import org.apache.commons.io.output.NullOutputStream

public class ServiceControlManagerHelper {

    def serviceList = [];
    def waitFor;
    def argStrings;
    def ch;
    def timeout;

    public ServiceControlManagerHelper(def props) {
        def servicesList = props['Services'];
        if (servicesList && servicesList.trim().length() > 0) {
            serviceList = servicesList.tokenize(',').findAll{ it && it.trim().length() > 0 }.collectNested{ it.trim() }
        }
        waitFor = Boolean.valueOf(props['waitFor']);
        argStrings = (props['argString']?props['argString'].split('\n'):null);
        ch = new CommandHelper(new File('.'));
        ch.ignoreExitValue=true;
        timeout = props['timeout'] != "" ? props['timeout']?.toLong() : null
    }

    public void startServices() {
        def failed = false
        serviceList.each { service ->
            if (isServiceRunning(service)) {
                println "Service $service already running!"
            }
            else {
                def args = ['sc.exe', 'start', service];
                argStrings.each { arg ->
                    args << arg;
                }
                def exitCode = runCommand("Starting Service $service", args);

                if (exitCode != 0) {
                    println "Exit Code: " + exitCode;
                    failed = true
                }
                else if (waitFor) {
                    try {
                        waitForServiceToBeStarted(service)
                    }
                    catch (Exception e) {
                        println e.message
                        failed = true
                    }
                }
            }
        }
        if (failed) System.exit 1
    }

    public void disableServices() {
        def failed = false
        serviceList.each { service ->
            def args = ['sc.exe', 'config', service, "start=", "disabled"];

            argStrings.each { arg ->
                args << arg;
            }
            def exitCode = runCommand("Disabling Service $service", args);
            println exitCode
            if (exitCode != 0) {
                println "Exit Code: " + exitCode;
                failed = true
            }
        }
        if (failed) System.exit 1
    }

    public void enableServices(def type) {
        def failed = false
        serviceList.each { service ->

            def args = ['sc.exe', 'config', service, "start=", type];

            argStrings.each { arg ->
                args << arg;
            }
            def exitCode = runCommand("Enabling Service $service", args);

            if (exitCode != 0) {
                println "Exit Code: " + exitCode;
                failed = true
            }
            else if (waitFor) {
                try {
                    waitForServiceToBeStarted(service)
                }
                catch (Exception e) {
                    println e.message
                    failed = true
                }
            }
        }
        if (failed) System.exit 1
    }

    public void stopServices() {
        def failed = false
        serviceList.each { service ->
            def startTime = System.currentTimeMillis();

            def args = ['sc.exe', 'stop', service];
            argStrings.each { arg ->
                args << arg;
            }
            def exitCode = runCommand("Stoping Service $service", args);

            if (exitCode != 0) {
                println "Exit Code: " + exitCode;
                failed = true
            }
            else if (waitFor) {
                try {
                    waitForServiceToBeStopped(service);
                }
                catch (Exception e) {
                    println e.message
                    failed = true
                }
            }
        }
        if (failed) System.exit 1
    }

    public void createService(serviceName) {
        def args = ['sc.exe', 'create', serviceName];
        argStrings.each { arg ->
             args << arg;
        }
        def exitCode = runCommand("Creating service $serviceName", args);

        if (exitCode == 0) {
            System.exit 0
        }
        else {
            println "Exit Code: " + exitCode;
            System.exit 1
        }
    }

    public void deleteServices() {
        def failed = false
        serviceList.each {service ->
            def args = ['sc.exe', 'delete', service];
            argStrings.each { arg ->
                 args << arg;
            }
            def exitCode = runCommand("Deleting service $service", args);

            if (exitCode != 0) {
                println "Exit Code: " + exitCode;
                failed = true
            }
        }
        if (failed) System.exit 1
    }

    public void checkIfExists() {
        def failed = false
        serviceList.each { service->
            def args = ['sc.exe', 'query', service];
            def exitCode = runCommand("Checking for service $service", args);

            if (exitCode == 0) {
                System.exit 0
            }
            else {
                println "Exit Code: " + exitCode;
                failed = true
            }
        }
        if (failed) System.exit 1
    }

    public void waitForServiceToBeStarted(serviceName) {
        println "Waiting for service to start";
        def started = false;
        def args = ['sc.exe', 'query', serviceName];
        def nullStream = new NullOutputStream();
        def nullPrintStream = new PrintStream(nullStream);
        def curSysOut = System.out;
        System.setOut(nullPrintStream);
        while (!started) {
            runCommand("Checking Service Status", args) { output ->
               if (output.contains("RUNNING")) {
                   started = true;
               }
               else if (output.contains("STOPPED")) {
                   System.setOut(curSysOut);
                   throw new Exception("Service moved back to stopped state. Check Service log for failures.");
               }
           }
           Thread.sleep(1000);
       }
       System.setOut(curSysOut);
    }

    public void waitForServiceToBeStopped(serviceName) {
        println "Waiting for service to stop";
        def stopped = false;
        def args = ['sc.exe', 'query', serviceName];
        def nullStream = new NullOutputStream();
        def nullPrintStream = new PrintStream(nullStream);
        def curSysOut = System.out;
        def startTime = System.currentTimeMillis();
        def needKill = false

        while (!stopped) {
            if (timeout != null) {
                if (System.currentTimeMillis() - startTime > timeout) {
                    needKill = true
                    break
                }
            }
            System.setOut(nullPrintStream);
            try {
                runCommand("Checking Service Status", args) { output ->
                    if (output.contains("STOPPED")) {
                        stopped = true;
                    }
                    else if (timeout == null) {
                        if (!output.contains("STOP_PENDING")) {
                            System.setOut(curSysOut);
                            throw new Exception("Service not in STOP PENDING state. Check Service log for failures.");
                        }
                    }

                }
            }
            finally {
                System.setOut(curSysOut);
            }


            Thread.sleep(1000);
        }
        System.setOut(curSysOut);

        if (needKill) {
            println "Service shutdown timed out! Killing service process!"
            def workDir = new File(".").canonicalFile
            final def cmdHelper = new CommandHelper(workDir)
            def baos = new ByteArrayOutputStream();
            def ps = new PrintStream(baos)

            args = ['sc.exe', 'queryex', serviceName]
            cmdHelper.runCommand("Getting service PID", args) { proc ->
                proc.consumeProcessOutputStream(ps)
                ps.flush();
                proc.withWriter { it << "\n" };
            }

            println baos.toString()

            def pid = ""

            baos.toString().eachLine { line ->
                if (line.contains("PID")) {
                    pid = line.split(":")[1].trim();
                }
            }

            if (pid != "") {
                try {
                    args = ['taskkill', '/F', '/PID', pid]
                    runCommand("Killing unresponsive service", args)
                }
                catch (Exception e) {
                    throw e
                }
            }
            else {
                println "No active service was found with the name ${serviceName}"
            }
        }
    }

    public boolean isServiceRunning(service) {
        def result = true
        println "Checking the service status"
        def args = ['sc.exe', 'query', service]

        runCommand("Checking Service Status", args) { output ->
            if (output.contains("RUNNING")) {
                println "Service is running"
            }
            else if (output.contains("STOPPED")) {
                println "Service is stopped."
                result = false
            }
            else if (output.contains("PAUSED")) {
                println "Service is paused"
                result = false
            }
            else {
                println "Unrecognized state. Exiting with failure"
                result = false
            }
        }

        return result
    }

    public void checkStatus() {
        def failed = false
        serviceList.each { service->
            if (!isServiceRunning(service)) {
                failed = true
            }
        }

        if (failed) System.exit 1
    }


    private int runCommand(def message, def args) {
       runCommand(message, args, null);
    }

    private int runCommand(def message, def args, def closure) {
        def result;
        def outStream = new ByteArrayOutputStream();
        def exitCode = ch.runCommand(message, args) { proc ->

            proc.out.close();//close std in
            proc.waitForProcessOutput(outStream, outStream);
            result = outStream.toString();
        }

        if (result.contains("FAILED") || result.contains("SYNTAX") || result.contains("USAGE")) {
            exitCode = 1
        }
        System.out.println("Command Result : ");
        System.out.println(result);

        if (closure) {
            closure(result);
        }

        return exitCode
    }
}
