# This provider has been deprecated as the same logic is included
# in Chef 12.6 and later. In the near future this provider will be
# removed from this cookbook.

require 'chef/resource/lwrp_base'
require 'chef/provider/lwrp_base'

require 'win32/registry' if RUBY_PLATFORM =~ /mswin|mingw32|windows/

require 'chef/mixin/shell_out'
require 'chef/mixin/language'
class Chef
  class Provider
    class WindowsCookbookPackage < Chef::Provider::LWRPBase # ~FC058
      include Chef::Mixin::ShellOut
      include Windows::Helper

      use_inline_resources

      # the logic in all action methods mirror that of
      # the Chef::Provider::Package which will make
      # refactoring into core chef easy

      action :install do
        # If we specified a version, and it's not the current version, move to the specified version
        if !@new_resource.version.nil? && @new_resource.version != @current_resource.version
          install_version = @new_resource.version
        # If it's not installed at all, install it
        elsif @current_resource.version.nil?
          install_version = candidate_version
        end

        if install_version
          Chef::Log.info("Installing #{@new_resource} version #{install_version}")
          status = install_package(@new_resource.package_name, install_version)
          new_resource.updated_by_last_action(true) if status
        end
      end

      action :upgrade do
        if @current_resource.version != candidate_version
          orig_version = @current_resource.version || 'uninstalled'
          Chef::Log.info("Upgrading #{@new_resource} version from #{orig_version} to #{candidate_version}")
          status = upgrade_package(@new_resource.package_name, candidate_version)
          new_resource.updated_by_last_action(true) if status
        end
      end

      action :remove do
        if removing_package?
          Chef::Log.info("Removing #{@new_resource}")
          remove_package(@current_resource.package_name, @new_resource.version)
          new_resource.updated_by_last_action(true)
        end
      end

      def removing_package?
        if @current_resource.version.nil?
          false # nothing to remove
        elsif @new_resource.version.nil?
          true # remove any version of a package
        elsif @new_resource.version == @current_resource.version
          true # remove the version we have
        else
          false # we don't have the version we want to remove
        end
      end

      def expand_options(options)
        options ? " #{options}" : ''
      end

      # these methods are the required overrides of
      # a provider that extends from Chef::Provider::Package
      # so refactoring into core Chef should be easy

      def load_current_resource
        @current_resource = Chef::Resource::WindowsPackage.new(@new_resource.name)
        @current_resource.package_name(@new_resource.package_name)
        @current_resource.version(nil)

        unless current_installed_version.nil?
          @current_resource.version(current_installed_version)
        end

        @current_resource
      end

      def current_installed_version
        @current_installed_version ||= begin
          if installed_packages.include?(@new_resource.package_name)
            installed_packages[@new_resource.package_name][:version]
          end
        end
      end

      def candidate_version
        @candidate_version ||= begin
          @new_resource.version || 'latest'
        end
      end

      def install_package(_name, _version)
        Chef::Log.debug("Processing #{@new_resource} as a #{installer_type} installer.")
        install_args = [cached_file(@new_resource.source, @new_resource.checksum), expand_options(unattended_installation_flags), expand_options(@new_resource.options)]
        Chef::Log.info('Starting installation...this could take awhile.')
        Chef::Log.debug "Install command: #{sprintf(install_command_template, *install_args)}"
        shell_out!(sprintf(install_command_template, *install_args), timeout: @new_resource.timeout, returns: @new_resource.success_codes)
      end

      def remove_package(_name, _version)
        uninstall_string = installed_packages[@new_resource.package_name][:uninstall_string]
        Chef::Log.info("Registry provided uninstall string for #{@new_resource} is '#{uninstall_string}'")
        uninstall_command = begin
          if uninstall_string =~ /msiexec/i
            "#{uninstall_string} /qn"
          else
            uninstall_string.delete!('"')
            "start \"\" /wait /d\"#{::File.dirname(uninstall_string)}\" #{::File.basename(uninstall_string)}#{expand_options(@new_resource.options)} /S & exit %%%%ERRORLEVEL%%%%"
          end
        end
        Chef::Log.info("Removing #{@new_resource} with uninstall command '#{uninstall_command}'")
        shell_out!(uninstall_command, returns: @new_resource.success_codes)
      end

      private

      def install_command_template
        case installer_type
        when :msi
          'msiexec%2$s "%1$s"%3$s'
        else
          'start "" /wait "%1$s"%2$s%3$s & exit %%%%ERRORLEVEL%%%%'
        end
      end

      # http://unattended.sourceforge.net/installers.php
      def unattended_installation_flags
        case installer_type
        when :msi
          # this is no-ui
          '/qn /i'
        when :installshield
          '/s /sms'
        when :nsis
          '/S /NCRC'
        when :inno
          # "/sp- /silent /norestart"
          '/verysilent /norestart'
        when :wise
          '/s'
        end
      end

      def installer_type
        @installer_type || begin
          if @new_resource.installer_type
            @new_resource.installer_type
          else
            basename = ::File.basename(cached_file(@new_resource.source, @new_resource.checksum))
            if basename.split('.').last.casecmp('msi').zero? # Microsoft MSI
              :msi
            else
              # search the binary file for installer type
              contents = ::Kernel.open(::File.expand_path(cached_file(@new_resource.source)), 'rb', &:read) # TODO: limit data read in
              case contents
              when /inno/i # Inno Setup
                :inno
              when /wise/i # Wise InstallMaster
                :wise
              when /nsis/i # Nullsoft Scriptable Install System
                :nsis
              else
                # if file is named 'setup.exe' assume installshield
                if basename == 'setup.exe'
                  :installshield
                else
                  raise Chef::Exceptions::AttributeNotFound, 'installer_type could not be determined, please set manually'
                end
              end
            end
          end
        end
      end
    end
  end
end

class Chef
  class Resource
    class WindowsCookbookPackage < Chef::Resource::LWRPBase
      if Gem::Version.new(Chef::VERSION) >= Gem::Version.new('12.4.0')
        provides :windows_package, os: 'windows', override: true
      elsif Gem::Version.new(Chef::VERSION) >= Gem::Version.new('12')
        provides :windows_package, os: 'windows'
      end
      actions :install, :remove

      default_action :install

      attribute :package_name, kind_of: String, name_attribute: true
      attribute :source, kind_of: String, required: true
      attribute :version, kind_of: String
      attribute :options, kind_of: String
      attribute :installer_type, kind_of: Symbol, default: nil, equal_to: [:msi, :inno, :nsis, :wise, :installshield, :custom]
      attribute :checksum, kind_of: String
      attribute :timeout, kind_of: Integer, default: 600
      attribute :success_codes, kind_of: Array, default: [0, 42, 127]

      if Gem::Version.new(Chef::VERSION) >= Gem::Version.new('12.6.0')
        attribute :remote_file_attributes, kind_of: Hash
        attribute :response_file, kind_of: String
        attribute :response_file_variables, kind_of: Hash
        alias_method :returns, :success_codes
      end

      self.resource_name = 'windows_package'
      def initialize(*args)
        super
        @provider = if Gem::Version.new(Chef::VERSION) >= Gem::Version.new('12.6.0')
                      Chef::Provider::Package::Windows
                    else
                      Chef::Provider::WindowsCookbookPackage
                    end

        Chef::Log.warn <<-EOF
Please use the package resource available in Chef Client 12.6+.
windows_package will be removed in the next major version release
of the Windows cookbook.
EOF
      end
    end
  end
end

if Gem::Version.new(Chef::VERSION) < Gem::Version.new('12.6') && Chef.respond_to?(:set_resource_priority_array)
  # this wires up the dynamic resource resolver to favor the cookbook version of windows_package over
  # the internal version (but the internal Chef::Resource::WindowsPackage is still the internal version
  # and a wrapper cookbook can override this e.g. for users that want to use the windows cookbook but
  # want the internal windows_package resource)
  Chef.set_resource_priority_array(:windows_package, [Chef::Resource::WindowsCookbookPackage], platform: 'windows')
end
