module Process

def create(args)

def create(args)
  unless args.kind_of?(Hash)
    raise TypeError, 'Expecting hash-style keyword arguments'
  end
  valid_keys = %w/
    app_name command_line inherit creation_flags cwd environment
    startup_info thread_inherit process_inherit close_handles with_logon
    domain password
  /
  valid_si_keys = %/
    startf_flags desktop title x y x_size y_size x_count_chars
    y_count_chars fill_attribute sw_flags stdin stdout stderr
  /
  # Set default values
  hash = {
    'app_name'       => nil,
    'creation_flags' => 0,
    'close_handles'  => true
  }
  # Validate the keys, and convert symbols and case to lowercase strings.
  args.each{ |key, val|
    key = key.to_s.downcase
    unless valid_keys.include?(key)
      raise ArgumentError, "invalid key '#{key}'"
    end
    hash[key] = val
  }
  si_hash = {}
  # If the startup_info key is present, validate its subkeys
  if hash['startup_info']
    hash['startup_info'].each{ |key, val|
      key = key.to_s.downcase
      unless valid_si_keys.include?(key)
        raise ArgumentError, "invalid startup_info key '#{key}'"
      end
      si_hash[key] = val
    }
  end
  # The +command_line+ key is mandatory unless the +app_name+ key
  # is specified.
  unless hash['command_line']
    if hash['app_name']
      hash['command_line'] = hash['app_name']
      hash['app_name'] = nil
    else
      raise ArgumentError, 'command_line or app_name must be specified'
    end
  end
  # The environment string should be passed as an array of A=B paths, or
  # as a string of ';' separated paths.
  if hash['environment']
    env = hash['environment']
    if !env.respond_to?(:join)
      # Backwards compat for ; separated paths
      env = hash['environment'].split(File::PATH_SEPARATOR)
    end
    # The argument format is a series of null-terminated strings, with an additional null terminator.
    env = env.map { |e| e + "\0" }.join("") + "\0"
    if hash['with_logon']
      env = env.multi_to_wide(e)
    end
    env = [env].pack('p*').unpack('L').first
  else
    env = nil
  end
  startinfo = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
  startinfo = startinfo.pack('LLLLLLLLLLLLSSLLLL')
  procinfo  = [0,0,0,0].pack('LLLL')
  # Process SECURITY_ATTRIBUTE structure
  process_security = 0
  if hash['process_inherit']
    process_security = [0,0,0].pack('LLL')
    process_security[0,4] = [12].pack('L') # sizeof(SECURITY_ATTRIBUTE)
    process_security[8,4] = [1].pack('L')  # TRUE
  end
  # Thread SECURITY_ATTRIBUTE structure
  thread_security = 0
  if hash['thread_inherit']
    thread_security = [0,0,0].pack('LLL')
    thread_security[0,4] = [12].pack('L') # sizeof(SECURITY_ATTRIBUTE)
    thread_security[8,4] = [1].pack('L')  # TRUE
  end
  # Automatically handle stdin, stdout and stderr as either IO objects
  # or file descriptors.  This won't work for StringIO, however.
  ['stdin', 'stdout', 'stderr'].each{ |io|
    if si_hash[io]
      if si_hash[io].respond_to?(:fileno)
        handle = get_osfhandle(si_hash[io].fileno)
      else
        handle = get_osfhandle(si_hash[io])
      end
      if handle == INVALID_HANDLE_VALUE
        raise Error, get_last_error
      end
      # Most implementations of Ruby on Windows create inheritable
      # handles by default, but some do not. RF bug #26988.
      bool = SetHandleInformation(
        handle,
        HANDLE_FLAG_INHERIT,
        HANDLE_FLAG_INHERIT
      )
      raise Error, get_last_error unless bool
      si_hash[io] = handle
      si_hash['startf_flags'] ||= 0
      si_hash['startf_flags'] |= STARTF_USESTDHANDLES
      hash['inherit'] = true
    end
  }
  # The bytes not covered here are reserved (null)
  unless si_hash.empty?
    startinfo[0,4]  = [startinfo.size].pack('L')
    startinfo[8,4]  = [si_hash['desktop']].pack('p*') if si_hash['desktop']
    startinfo[12,4] = [si_hash['title']].pack('p*') if si_hash['title']
    startinfo[16,4] = [si_hash['x']].pack('L') if si_hash['x']
    startinfo[20,4] = [si_hash['y']].pack('L') if si_hash['y']
    startinfo[24,4] = [si_hash['x_size']].pack('L') if si_hash['x_size']
    startinfo[28,4] = [si_hash['y_size']].pack('L') if si_hash['y_size']
    startinfo[32,4] = [si_hash['x_count_chars']].pack('L') if si_hash['x_count_chars']
    startinfo[36,4] = [si_hash['y_count_chars']].pack('L') if si_hash['y_count_chars']
    startinfo[40,4] = [si_hash['fill_attribute']].pack('L') if si_hash['fill_attribute']
    startinfo[44,4] = [si_hash['startf_flags']].pack('L') if si_hash['startf_flags']
    startinfo[48,2] = [si_hash['sw_flags']].pack('S') if si_hash['sw_flags']
    startinfo[56,4] = [si_hash['stdin']].pack('L') if si_hash['stdin']
    startinfo[60,4] = [si_hash['stdout']].pack('L') if si_hash['stdout']
    startinfo[64,4] = [si_hash['stderr']].pack('L') if si_hash['stderr']
  end
  if hash['with_logon']
    logon  = multi_to_wide(hash['with_logon'])
    domain = multi_to_wide(hash['domain'])
    app    = hash['app_name'].nil? ? nil : multi_to_wide(hash['app_name'])
    cmd    = hash['command_line'].nil? ? nil : multi_to_wide(hash['command_line'])
    cwd    = multi_to_wide(hash['cwd'])
    passwd = multi_to_wide(hash['password'])
    hash['creation_flags'] |= CREATE_UNICODE_ENVIRONMENT
    process_ran = CreateProcessWithLogonW(
      logon,                  # User
      domain,                 # Domain
      passwd,                 # Password
      LOGON_WITH_PROFILE,     # Logon flags
      app,                    # App name
      cmd,                    # Command line
      hash['creation_flags'], # Creation flags
      env,                    # Environment
      cwd,                    # Working directory
      startinfo,              # Startup Info
      procinfo                # Process Info
    )
  else
    process_ran = CreateProcess(
      hash['app_name'],       # App name
      hash['command_line'],   # Command line
      process_security,       # Process attributes
      thread_security,        # Thread attributes
      hash['inherit'],        # Inherit handles?
      hash['creation_flags'], # Creation flags
      env,                    # Environment
      hash['cwd'],            # Working directory
      startinfo,              # Startup Info
      procinfo                # Process Info
    )
  end
  # TODO: Close stdin, stdout and stderr handles in the si_hash unless
  # they're pointing to one of the standard handles already. [Maybe]
  if !process_ran
    raise_last_error("CreateProcess()")
  end
  # Automatically close the process and thread handles in the
  # PROCESS_INFORMATION struct unless explicitly told not to.
  if hash['close_handles']
    CloseHandle(procinfo[0,4].unpack('L').first)
    CloseHandle(procinfo[4,4].unpack('L').first)
  end
  ProcessInfo.new(
    procinfo[0,4].unpack('L').first, # hProcess
    procinfo[4,4].unpack('L').first, # hThread
    procinfo[8,4].unpack('L').first, # hProcessId
    procinfo[12,4].unpack('L').first # hThreadId
  )
end