/*
 * Win32Service.java
 *
 * Created on 12. September 2007, 12:05
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package jnacontrib.win32;

import jnacontrib.jna.*;
import com.sun.jna.Pointer;

/**
 * Baseclass for a Win32 service.
 */
public abstract class Win32Service {
  protected String serviceName;
  private ServiceMain serviceMain;
  private ServiceControl serviceControl;
  private Pointer serviceStatusHandle;
  private Object waitObject = new Object();
  
  /**
   * Creates a new instance of Win32Service.
   *
   * @param serviceName internal name of the service
   */
  public Win32Service(String serviceName) {
    this.serviceName = serviceName;
  }
  
  /**
   * Install the service.
   *
   * @param displayName visible name
   * @param description description
   * @param dependencies array of other services to depend on or null
   * @param account service account or null for LocalSystem
   * @param password password for service account or null
   * @throws java.lang.Exception 
   * @return true on success
   */
  public boolean install(String displayName, String description, String[] dependencies, String account, String password) {
    return(install(displayName, description, dependencies, account, password, "java.exe -cp \"" + 
           System.getProperty("java.class.path") + "\" -Xrs " + this.getClass().getName()));
  }
  
  /**
   * Install the service.
   * 
   * @return true on success
   * @param displayName visible name
   * @param description description
   * @param dependencies array of other services to depend on or null
   * @param account service account or null for LocalSystem
   * @param password password for service account or null
   * @param command command line to start the service
   * @throws java.lang.Exception 
   */
  public boolean install(String displayName, String description, String[] dependencies, String account, String password, String command) {
    Advapi32 advapi32;
    Advapi32.SERVICE_DESCRIPTION desc;
    Pointer serviceManager, service;
    boolean success = false;
    String dep = "";
    
    if(dependencies != null) {
      for(String s : dependencies) {
        dep += s + "\0";
      }
    }
    dep += "\0";
    
    desc = new Advapi32.SERVICE_DESCRIPTION();
    desc.lpDescription = description;
    
    advapi32 = Advapi32.INSTANCE;
    serviceManager = openServiceControlManager(null, WINSVC.SC_MANAGER_ALL_ACCESS);
    
    if(serviceManager != null) {
      service = advapi32.CreateService(serviceManager, serviceName, displayName,
              WINSVC.SERVICE_ALL_ACCESS, WINSVC.SERVICE_WIN32_OWN_PROCESS, WINSVC.SERVICE_DEMAND_START, 
              WINSVC.SERVICE_ERROR_NORMAL, 
              command, 
              null, null, dep, account, password);
      
      if(service != null) {
        success = advapi32.ChangeServiceConfig2(service, WINSVC.SERVICE_CONFIG_DESCRIPTION, desc);
        advapi32.CloseServiceHandle(service);
      }
      advapi32.CloseServiceHandle(serviceManager);
    }
    return(success);
  }
  
  /**
   * Uninstall the service.
   *
   * @throws java.lang.Exception 
   * @return true on success
   */
  public boolean uninstall() {
    Advapi32 advapi32;
    Pointer serviceManager, service;
    boolean success = false;
    
    advapi32 = Advapi32.INSTANCE;
    serviceManager = openServiceControlManager(null, WINSVC.SC_MANAGER_ALL_ACCESS);
    
    if(serviceManager != null) {
      service = advapi32.OpenService(serviceManager, serviceName, WINSVC.SERVICE_ALL_ACCESS);
      
      if(service != null) {
        success = advapi32.DeleteService(service);
        advapi32.CloseServiceHandle(service);
      }
      advapi32.CloseServiceHandle(serviceManager);
    }
    return(success);
  }
  
  /**
   * Ask the ServiceControlManager to start the service.
   * @return true on success
   */
  public boolean start() {
    Advapi32 advapi32;
    Pointer serviceManager, service;
    boolean success = false;
    
    advapi32 = Advapi32.INSTANCE;
    
    serviceManager = openServiceControlManager(null, WINNT.GENERIC_EXECUTE);
    
    if(serviceManager != null) {
      service = advapi32.OpenService(serviceManager, serviceName, WINNT.GENERIC_EXECUTE);
      
      if(service != null) {
        success = advapi32.StartService(service, 0, null);
        advapi32.CloseServiceHandle(service);
      }
      advapi32.CloseServiceHandle(serviceManager);
    }
    
    return(success);
  }
  
  /**
   * Ask the ServiceControlManager to stop the service.
   * @return true on success
   */
  public boolean stop() throws Exception {
    Advapi32 advapi32;
    Pointer serviceManager, service;
    Advapi32.SERVICE_STATUS serviceStatus;
    boolean success = false;
    
    advapi32 = Advapi32.INSTANCE;
    
    serviceManager = openServiceControlManager(null, WINNT.GENERIC_EXECUTE);
    
    if(serviceManager != null) {
      service = advapi32.OpenService(serviceManager, serviceName, WINNT.GENERIC_EXECUTE);
      
      if(service != null) {
        serviceStatus = new Advapi32.SERVICE_STATUS();
        success = advapi32.ControlService(service, WINSVC.SERVICE_CONTROL_STOP, serviceStatus);
        advapi32.CloseServiceHandle(service);
      }
      advapi32.CloseServiceHandle(serviceManager);
    }
    
    return(success);
  }
  
  /**
   * Initialize the service, connect to the ServiceControlManager.
   */
  public void init() {
    Advapi32 advapi32;
    Advapi32.SERVICE_TABLE_ENTRY entry;
    
    serviceMain = new ServiceMain();
    advapi32 = Advapi32.INSTANCE;
    entry = new Advapi32.SERVICE_TABLE_ENTRY();
    entry.lpServiceName = serviceName;
    entry.lpServiceProc = serviceMain;
    
    advapi32.StartServiceCtrlDispatcher(entry.toArray(2));
  }
  
  /**
   * Get a handle to the ServiceControlManager.
   *
   * @param machine name of the machine or null for localhost
   * @param access access flags
   * @return handle to ServiceControlManager or null when failed
   */
  private Pointer openServiceControlManager(String machine, int access) {
    Pointer handle = null;
    Advapi32 advapi32;
    
    advapi32 = Advapi32.INSTANCE;
    handle = advapi32.OpenSCManager(machine, null, access);
    return(handle);
  }
  
  /**
   * Report service status to the ServiceControlManager.
   *
   * @param status status
   * @param win32ExitCode exit code 
   * @param waitHint time to wait
   */
  private void reportStatus(int status, int win32ExitCode, int waitHint) {
    Advapi32 advapi32;
    Advapi32.SERVICE_STATUS serviceStatus;
    
    advapi32 = Advapi32.INSTANCE;
    serviceStatus = new Advapi32.SERVICE_STATUS();
    serviceStatus.dwServiceType = WINNT.SERVICE_WIN32_OWN_PROCESS;
    serviceStatus.dwControlsAccepted = WINSVC.SERVICE_ACCEPT_STOP | WINSVC.SERVICE_ACCEPT_SHUTDOWN;
    serviceStatus.dwWin32ExitCode = win32ExitCode;
    serviceStatus.dwWaitHint = waitHint;
    serviceStatus.dwCurrentState = status;
    
    advapi32.SetServiceStatus(serviceStatusHandle, serviceStatus);
  }
  
  /**
   * Called when service is starting.
   */
  public abstract void onStart();
  
  /*
   * Called when service should stop.
   */
  public abstract void onStop();
  
  
  /**
   * Implementation of the service main function.
   */
  private class ServiceMain implements Advapi32.SERVICE_MAIN_FUNCTION {
    
    /**
     * Called when the service is starting.
     *
     * @param dwArgc number of arguments
     * @param lpszArgv pointer to arguments
     */
    public void callback(int dwArgc, Pointer lpszArgv) {
      Advapi32 advapi32;
      
      advapi32 = Advapi32.INSTANCE;
      
      serviceControl = new ServiceControl();
      serviceStatusHandle = advapi32.RegisterServiceCtrlHandlerEx(serviceName, serviceControl, null);
      
      reportStatus(WINSVC.SERVICE_START_PENDING, WINERROR.NO_ERROR, 3000);
      reportStatus(WINSVC.SERVICE_RUNNING, WINERROR.NO_ERROR, 0);
      
      onStart();
      
      try {
        synchronized(waitObject) {
          waitObject.wait();
        }
      } catch (InterruptedException ex) {
      }
      reportStatus(WINSVC.SERVICE_STOPPED, WINERROR.NO_ERROR, 0);

      // Avoid returning from ServiceMain, which will cause a crash
      // See http://support.microsoft.com/kb/201349, which recommends
      // having init() wait for this thread.  
      // Waiting on this thread in init() won't fix the crash, though.
      //System.exit(0);
    }
  }
  
  
  /**
   * Implementation of the service control function.
   */
  private class ServiceControl implements Advapi32.HandlerEx {

    /**
     * Called when the service get a control code.
     *
     * @param dwControl 
     * @param dwEventType 
     * @param lpEventData 
     * @param lpContext 
     */
    public int callback(int dwControl, int dwEventType, Pointer lpEventData, Pointer lpContext) {
      switch(dwControl) {
        case WINSVC.SERVICE_CONTROL_STOP:
        case WINSVC.SERVICE_CONTROL_SHUTDOWN:
          reportStatus(WINSVC.SERVICE_STOP_PENDING, WINERROR.NO_ERROR, 5000);
          onStop();
          synchronized(waitObject) {
            waitObject.notifyAll();
          }
      }
      return WINERROR.NO_ERROR;
    }
  }
}
