#
# Copyright:: 2012-2018, Nordstrom, Inc.
# Copyright:: Copyright (c) Chef Software 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.
#

require_relative "../resource"

class Chef
  class Resource
    class WindowsPagefile < Chef::Resource

      provides(:windows_pagefile) { true }

      description "Use the **windows_pagefile** resource to configure pagefile settings on Windows."
      introduced "14.0"
      examples <<~DOC
      **Set the system to manage pagefiles**:

      ```ruby
      windows_pagefile 'Enable automatic management of pagefiles' do
        automatic_managed true
      end
      ```

      **Delete a pagefile**:

      ```ruby
      windows_pagefile 'Delete the pagefile' do
        path 'C'
        action :delete
      end
      ```

      **Switch to system managed pagefiles**:

      ```ruby
      windows_pagefile 'Change the pagefile to System Managed' do
        path 'E:\\'
        system_managed true
        action :set
      end
      ```

      **Create a pagefile with an initial and maximum size**:

      ```ruby
      windows_pagefile 'create the pagefile with these sizes' do
        path 'f:\\'
        initial_size 100
        maximum_size 200
      end
      ```
      DOC

      property :path, String,
        coerce: proc { |x| x.tr("/", "\\") },
        description: "An optional property to set the pagefile name if it differs from the resource block's name.",
        name_property: true

      property :system_managed, [TrueClass, FalseClass],
        description: "Configures whether the system manages the pagefile size."

      property :automatic_managed, [TrueClass, FalseClass],
        description: "Enable automatic management of pagefile initial and maximum size. Setting this to true ignores `initial_size` and `maximum_size` properties."

      property :initial_size, Integer,
        description: "Initial size of the pagefile in megabytes."

      property :maximum_size, Integer,
        description: "Maximum size of the pagefile in megabytes."

      action :set, description: "Configures the default pagefile, creating if it doesn't exist." do
        automatic_managed = new_resource.automatic_managed

        if automatic_managed
          set_automatic_managed unless automatic_managed?
        elsif automatic_managed == false
          unset_automatic_managed
        else
          pagefile = clarify_pagefile_name
          initial_size = new_resource.initial_size
          maximum_size = new_resource.maximum_size
          system_managed = new_resource.system_managed

          # the method below is designed to raise an exception if the drive you are trying to create a pagefile for doesn't exist.
          # PowerShell will happily let you create a pagefile called h:\pagefile.sys even though you don't have an H:\ drive.

          pagefile_drive_exist?(pagefile)
          create(pagefile) unless exists?(pagefile)

          if (initial_size && maximum_size) || system_managed
            if system_managed
              set_system_managed(pagefile) unless max_and_min_set?(pagefile, 0, 0)
            else
              unless max_and_min_set?(pagefile, initial_size, maximum_size)
                set_custom_size(pagefile, initial_size, maximum_size)
              end
            end
          end
        end
      end

      action :delete, description: "Deletes the specified pagefile." do
        pagefile = clarify_pagefile_name
        delete(pagefile) if exists?(pagefile)
      end

      action_class do
        private

        # We are adding support for a number of possibilities for how users will express the drive and location they want the pagefile written to.
        def clarify_pagefile_name
          case new_resource.path
          # user enters C, C:, C:\, C:\\
          when /^[a-zA-Z]/
            new_resource.path[0] + ":\\pagefile.sys"
          # user enters C:\pagefile.sys OR c:\foo\bar\pagefile.sys as the path
          when /^[a-zA-Z]:.*.sys/
            new_resource.path
          else
            raise "#{new_resource.path} does not match the format DRIVE:\\path\\pagefile.sys for pagefiles. Example: C:\\pagefile.sys"
          end
        end

        # raise an exception if the target drive location is invalid
        def pagefile_drive_exist?(pagefile)
          if ::Dir.exist?(pagefile[0] + ":\\") == false
            raise "You are trying to create a pagefile on a drive that does not exist!"
          end
        end

        # See if the pagefile exists
        #
        # @param [String] pagefile path to the pagefile
        # @return [Boolean]
        def exists?(pagefile)
          @exists ||= begin
            logger.trace("Checking if #{pagefile} exists by running: Get-CimInstance Win32_PagefileSetting | Where-Object { $_.name -eq $($pagefile)} ")
            powershell_code = <<~CODE
              $page_file_name = '#{pagefile}';
              $pagefile = Get-CimInstance Win32_PagefileSetting | Where-Object { $_.name -eq $($page_file_name)}
              if ([string]::IsNullOrEmpty($pagefile)) { return $false } else { return $true }
            CODE
            powershell_exec!(powershell_code).result
          end
        end

        # is the max/min pagefile size set?
        #
        # @param [String] pagefile path to the pagefile
        # @param [String] min the minimum size of the pagefile
        # @param [String] max the minimum size of the pagefile
        # @return [Boolean]
        def max_and_min_set?(pagefile, min, max)
          logger.trace("Checking if #{pagefile} has max and initial disk size values set")
          powershell_code = <<-CODE
            $page_file = '#{pagefile}';
            $driveLetter = $page_file.split(':')[0];
            $page_file_settings = Get-CimInstance -ClassName Win32_PageFileSetting -Filter "SettingID='pagefile.sys @ $($driveLetter):'" -Property * -ErrorAction Stop;
            if ($page_file_settings.InitialSize -eq #{min} -and $page_file_settings.MaximumSize -eq #{max})
              { return $true }
            else
              { return $false }
          CODE
          powershell_exec!(powershell_code).result
        end

        # create a pagefile
        #
        # @param [String] pagefile path to the pagefile
        def create(pagefile)
          converge_by("create pagefile #{pagefile}") do
            logger.trace("Running New-CimInstance -ClassName Win32_PageFileSetting to create new pagefile : #{pagefile}")
            powershell_exec! <<~ELM
              New-CimInstance -ClassName Win32_PageFileSetting -Property  @{Name = "#{pagefile}"}
            ELM
          end
        end

        # delete a pagefile
        #
        # @param [String] pagefile path to the pagefile
        def delete(pagefile)
          converge_by("remove pagefile #{pagefile}") do
            logger.trace("Running Remove-CimInstance for pagefile : #{pagefile}")
            powershell_exec! <<~EOL
              $page_file = "#{pagefile}"
              $driveLetter = $page_file.split(':')[0]
              $PageFile = (Get-CimInstance -ClassName Win32_PageFileSetting -Filter "SettingID='pagefile.sys @ $($driveLetter):'" -ErrorAction Stop)
              $null = ($PageFile | Remove-CimInstance -ErrorAction SilentlyContinue)
            EOL
          end
        end

        # see if the pagefile is automatically managed by Windows
        #
        # @return [Boolean]
        def automatic_managed?
          @automatic_managed ||= begin
            logger.trace("Checking if pagefiles are automatically managed")
            cmd = "$sys = Get-CimInstance Win32_ComputerSystem -Property *;"
            cmd << "return $sys.AutomaticManagedPagefile"
            powershell_exec!(cmd).result
          end
        end

        # turn on automatic management of all pagefiles by Windows
        def set_automatic_managed
          converge_by("Set pagefile to Automatic Managed") do
            logger.trace("Running Set-CimInstance -InputObject $sys -Property @{AutomaticManagedPagefile=$true} -PassThru")
            powershell_exec! <<~EOH
              $sys = Get-CimInstance Win32_ComputerSystem -Property *
              Set-CimInstance -InputObject $sys -Property @{AutomaticManagedPagefile=$true} -PassThru
            EOH
          end
        end

        # turn off automatic management of all pagefiles by Windows
        def unset_automatic_managed
          if automatic_managed?
            converge_by("Turn off Automatically Managed on pagefiles") do
              logger.trace("Running Set-CimInstance -InputObject $sys -Property @{AutomaticManagedPagefile=$false} -PassThru")
              powershell_exec! <<~EOH
                $sys = Get-CimInstance Win32_ComputerSystem -Property *
                Set-CimInstance -InputObject $sys -Property @{AutomaticManagedPagefile=$false} -PassThru
              EOH
            end
          end
        end

        # set a custom size for the pagefile (vs the defaults)
        #
        # @param [String] pagefile path to the pagefile
        # @param [String] min the minimum size of the pagefile
        # @param [String] max the minimum size of the pagefile
        def set_custom_size(pagefile, min, max)
          unset_automatic_managed
          converge_by("set #{pagefile} to InitialSize=#{min} & MaximumSize=#{max}") do
            logger.trace("Set-CimInstance -Property @{InitialSize = #{min} MaximumSize = #{max}")
            powershell_exec! <<~EOD
              $page_file = "#{pagefile}"
              $driveLetter = $page_file.split(':')[0]
              Get-CimInstance -ClassName Win32_PageFileSetting -Filter "SettingID='pagefile.sys @ $($driveLetter):'" -ErrorAction Stop | Set-CimInstance -Property @{InitialSize = #{min}; MaximumSize = #{max};}
            EOD
          end
        end

        # set a pagefile size to be system managed
        #
        # @param [String] pagefile path to the pagefile
        def set_system_managed(pagefile)
          converge_by("set #{pagefile} to System Managed") do
            logger.trace("Running ")
            powershell_exec! <<~EOM
              $page_file = "#{pagefile}"
              $driveLetter = $page_file.split(':')[0]
              Get-CimInstance -ClassName Win32_PageFileSetting -Filter "SettingID='pagefile.sys @ $($driveLetter):'" -ErrorAction Stop | Set-CimInstance -Property @{
              InitialSize = 0
              MaximumSize = 0}
            EOM
          end
        end
      end
    end
  end
end
