/*
 * Copyright (c) 2006-2008 Hyperic, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.hyperic.sigar.win32;

import java.io.PrintStream;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;

import org.hyperic.sigar.Sigar;
import org.hyperic.sigar.SigarException;

public class Service extends Win32 {
    // Service State
    public static final int SERVICE_STOPPED          = 0x00000001;
    public static final int SERVICE_START_PENDING    = 0x00000002;
    public static final int SERVICE_STOP_PENDING     = 0x00000003;
    public static final int SERVICE_RUNNING          = 0x00000004;
    public static final int SERVICE_CONTINUE_PENDING = 0x00000005;
    public static final int SERVICE_PAUSE_PENDING    = 0x00000006;
    public static final int SERVICE_PAUSED           = 0x00000007;

    private static final String[] STATUS = {
        "Unknown",
        "Stopped",
        "Start Pending",
        "Stop Pending",
        "Running",
        "Continue Pending",
        "Pause Pending",
        "Paused"
    };
    
    // Service Controls
    private static final int CONTROL_START  = 0x00000000;
    private static final int CONTROL_STOP   = 0x00000001;
    private static final int CONTROL_PAUSE  = 0x00000002;
    private static final int CONTROL_RESUME = 0x00000003;

    // Service Control Manager object specific access types
    private static final int STANDARD_RIGHTS_REQUIRED = (int)0x000F0000L;

    private static final int SC_MANAGER_CONNECT            = 0x0001;
    private static final int SC_MANAGER_CREATE_SERVICE     = 0x0002;
    private static final int SC_MANAGER_ENUMERATE_SERVICE  = 0x0004;
    private static final int SC_MANAGER_LOCK               = 0x0008;
    private static final int SC_MANAGER_QUERY_LOCK_STATUS  = 0x0010;
    private static final int SC_MANAGER_MODIFY_BOOT_CONFIG = 0x0020;

    private static final int SC_MANAGER_ALL_ACCESS = 
        (STANDARD_RIGHTS_REQUIRED |
         SC_MANAGER_CONNECT             |
         SC_MANAGER_CREATE_SERVICE      |
         SC_MANAGER_ENUMERATE_SERVICE   |
         SC_MANAGER_LOCK                |
         SC_MANAGER_QUERY_LOCK_STATUS   |
         SC_MANAGER_MODIFY_BOOT_CONFIG);

    // Service object specific access type
    private static final int SERVICE_QUERY_CONFIG         = 0x0001;
    private static final int SERVICE_CHANGE_CONFIG        = 0x0002;
    private static final int SERVICE_QUERY_STATUS         = 0x0004;
    private static final int SERVICE_ENUMERATE_DEPENDENTS = 0x0008;
    private static final int SERVICE_START                = 0x0010;
    private static final int SERVICE_STOP                 = 0x0020;
    private static final int SERVICE_PAUSE_CONTINUE       = 0x0040;
    private static final int SERVICE_INTERROGATE          = 0x0080;
    private static final int SERVICE_USER_DEFINED_CONTROL = 0x0100;

    private static final int SERVICE_ALL_ACCESS =
        (STANDARD_RIGHTS_REQUIRED |
         SERVICE_QUERY_CONFIG           |
         SERVICE_CHANGE_CONFIG          |
         SERVICE_QUERY_STATUS           |
         SERVICE_ENUMERATE_DEPENDENTS   |
         SERVICE_START                  |
         SERVICE_STOP                   |
         SERVICE_PAUSE_CONTINUE         |
         SERVICE_INTERROGATE            |
         SERVICE_USER_DEFINED_CONTROL);
    
    private long manager;
    private long service;
    private String name;

    private Service() throws Win32Exception
    {
        this.manager = OpenSCManager("", SC_MANAGER_ALL_ACCESS);
    }

    public static native List getServiceNames(Sigar sigar, String ptql)
        throws Win32Exception;

    public static List getServiceNames() throws Win32Exception {
        return getServiceNames(null, null);
    }

    public static List getServiceConfigs(Sigar sigar, String exe)
        throws Win32Exception {

        List services = new ArrayList();
        List names = Service.getServiceNames(sigar,
                                             "Service.Exe.Ieq=" + exe);

        for (int i=0; i<names.size(); i++) {
            Service service = null;
            try {
                service = new Service((String)names.get(i));
                ServiceConfig config = service.getConfig();
                services.add(config);
            } catch (Win32Exception e){
                continue;
            } finally {
                if (service != null) {
                    service.close();
                }
            }
        }

        return services;
    }

    /**
     * @deprecated
     */
    public static List getServiceConfigs(String exe)
        throws Win32Exception {

        Sigar sigar = new Sigar();
        try {
            return getServiceConfigs(sigar, exe);
        } finally {
            sigar.close();
        }
    }

    public Service(String serviceName) throws Win32Exception
    {
        this();
        
        this.service = OpenService(this.manager, serviceName, 
                                   SERVICE_ALL_ACCESS);

        this.name = serviceName;
    }

    protected void finalize()
    {
        close();
    }

    public synchronized void close()
    {
        if (this.service != 0) {
            CloseServiceHandle(this.service);
            this.service = 0;
        }

        if (this.manager != 0) {
            CloseServiceHandle(this.manager);
            this.manager = 0;
        }
    }
    
    public static Service create(ServiceConfig config)
        throws Win32Exception
    {
        if (config.getName() == null) {
            throw new IllegalArgumentException("name=null");
        }

        if (config.getPath() == null) {
            throw new IllegalArgumentException("path=null");
        }

        Service service = new Service();
        service.service =
            CreateService(service.manager,
                          config.getName(),
                          config.getDisplayName(),
                          config.getType(),
                          config.getStartType(),
                          config.getErrorControl(),
                          config.getPath(),
                          config.getDependencies(),
                          config.getStartName(),
                          config.getPassword());

        if (config.getDescription() != null) {
            service.setDescription(config.getDescription());
        }
        
        return service;
    }

    public void delete() throws Win32Exception
    {
        DeleteService(this.service);
    }

    public void setDescription(String description)
    {
        ChangeServiceDescription(this.service, description);
    }
            
    /**
     * @deprecated
     * @see #getStatus()
     */    
    public int status()
    {
        return getStatus();
    }
    
    public int getStatus()
    {
        return QueryServiceStatus(this.service);
    }

    public String getStatusString()
    {
        return STATUS[getStatus()];
    }
    
    private void control(int ctl) throws Win32Exception {
        ControlService(this.service, ctl);
    }

    private static class Waiter {
        long start = System.currentTimeMillis();
        Service service;
        long timeout;
        int wantedState;
        int pendingState;
        
        Waiter(Service service, 
               long timeout,
               int wantedState,
               int pendingState)
        {
            this.service = service;
            this.timeout = timeout;
            this.wantedState = wantedState;
            this.pendingState = pendingState;
        }
        
        boolean sleep() {
            int status;
            while ((status = service.getStatus()) != this.wantedState) {
                if (status != this.pendingState) {
                    return false;
                }

                if ((timeout <= 0) ||
                    ((System.currentTimeMillis() - this.start) < this.timeout))
                {
                    try {
                        Thread.sleep(100);
                    } catch(InterruptedException e) {}
                }
                else {
                    break;
                }
            }

            return status == this.wantedState;
        }
    }
    
    public void stop() throws Win32Exception
    {
        control(CONTROL_STOP);
    }

    /**
     * @deprecated
     */
    public void stopAndWait(long timeout) throws Win32Exception {
        stop(timeout);
    }

    public void stop(long timeout) throws Win32Exception
    {
        stop();

        Waiter waiter =
            new Waiter(this, timeout, SERVICE_STOPPED, SERVICE_STOP_PENDING);

        if (!waiter.sleep()) {
            throw new Win32Exception("Failed to stop service");
        }
    }
    
    public void start() throws Win32Exception
    {
        control(CONTROL_START);
    }

    public void start(long timeout) throws Win32Exception
    {
        start();

        Waiter waiter =
            new Waiter(this, timeout, SERVICE_RUNNING, SERVICE_START_PENDING);

        if (!waiter.sleep()) {
            throw new Win32Exception("Failed to start service");
        }
    }

    public void pause() throws Win32Exception
    {
        control(CONTROL_PAUSE);
    }

    public void pause(long timeout) throws Win32Exception
    {
        pause();

        Waiter waiter =
            new Waiter(this, timeout, SERVICE_PAUSED, SERVICE_PAUSE_PENDING);

        if (!waiter.sleep()) {
            throw new Win32Exception("Failed to pause service");
        }
    }

    public void resume() throws Win32Exception
    {
        control(CONTROL_RESUME);
    }

    public ServiceConfig getConfig() throws Win32Exception {
        ServiceConfig config = new ServiceConfig();
        QueryServiceConfig(this.service, config);
        config.setName(this.name);
        return config;
    }
    
    private static native boolean ChangeServiceDescription(long handle,
                                                           String description);

    private static native boolean CloseServiceHandle(long handle);

    private static native long CreateService(long handle,
                                             String serviceName,
                                             String displayName,
                                             int serviceType,
                                             int startType,
                                             int errorControl,
                                             String path,
                                             String[] dependencies,
                                             String startName,
                                             String password) throws Win32Exception;

    private static native void ControlService(long handle,
                                              int control) throws Win32Exception;

    private static native void DeleteService(long handle) throws Win32Exception;

    private static native long OpenSCManager(String machine,
                                             int access) throws Win32Exception;

    private static native long OpenService(long handle,
                                           String service,
                                           int access) throws Win32Exception;

    private static native int QueryServiceStatus(long handle);

    private static native boolean QueryServiceConfig(long handle,
                                                     ServiceConfig config) throws Win32Exception;

    public void list(PrintStream out) throws Win32Exception {
        getConfig().list(out);
        out.println("status........[" + getStatusString() + "]");

        if (getStatus() != SERVICE_RUNNING) {
            return;
        }
        Sigar sigar = new Sigar();
        try {
            long pid = sigar.getServicePid(getConfig().getName());
            out.println("pid...........[" + pid + "]");
        } catch (SigarException e) {
        } finally {
            sigar.close();
        }
    }
    
    public static void main(String[] args) throws Exception {
        List services;
        if (args.length == 0) {
            services = getServiceNames();
        }
        else if ((args.length == 2) && (args[0].equals("-toggle"))) {
            long timeout = 5 * 1000; //5 seconds
            Service service = new Service(args[1]);
            if (service.getStatus() == SERVICE_RUNNING) {
                System.out.println("Stopping service...");
                service.stop(timeout);
            }
            else {
                System.out.println("Starting service...");
                service.start(timeout);
            }
            System.out.println(service.getStatusString());
            return;
        }
        else if ((args.length == 1) && (args[0].startsWith("Service."))) {
            Sigar sigar = new Sigar();
            try {
                services = Service.getServiceNames(sigar, args[0]);
            } finally {
                sigar.close();
            }
        }
        else if ((args.length == 1) && (args[0].endsWith(EXE_EXT))) {
            Sigar sigar = new Sigar();
            try {
                services = getServiceConfigs(args[0]);
            } finally {
                sigar.close();
            }
            for (int i=0; i<services.size(); i++) {
                ServiceConfig config = (ServiceConfig)services.get(i);
                config.list(System.out);
                System.out.println("");
            }
            return;
        }
        else {
            services = Arrays.asList(args);
        }

        for (int i=0; i<services.size(); i++) {
            String name = (String)services.get(i);
            Service service = new Service(name);
            service.list(System.out);
            System.out.println("");
        }
    }
}
