#
# Copyright 2014 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.
#

module Omnibus
  class Compressor::DMG < Compressor::Base
    id :dmg

    setup do
      # Clean any previously mounted disks
      clean_disks

      # Create the resources directory
      create_directory(resources_dir)

      # Copy the compiled pkg into the dmg
      copy_file(packager.package_path, "#{resources_dir}/")

      # Copy support files
      support = create_directory("#{resources_dir}/.support")
      copy_file(resource_path('background.png'), "#{support}/background.png")
    end

    build do
      create_writable_dmg
      attach_dmg
      # Give some time to the system so attached dmg shows up in Finder
      sleep 5
      set_volume_icon
      prettify_dmg
      compress_dmg
      set_dmg_icon
    end

    #
    # @!group DSL methods
    # --------------------------------------------------

    #
    # Set or return the starting x,y and ending x,y positions for the created
    # DMG window.
    #
    # @example
    #   window_bounds "100, 100, 750, 600"
    #
    # @param [String] val
    #   the DMG window bounds
    #
    # @return [String]
    #   the DMG window bounds
    #
    def window_bounds(val = NULL)
      if null?(val)
        @window_bounds || '100, 100, 750, 600'
      else
        @window_bounds = val
      end
    end
    expose :window_bounds

    #
    # Set or return the starting x,y position where the .pkg file should live
    # in the DMG window.
    #
    # @example
    #   pkg_position "535, 50"
    #
    # @param [String] val
    #   the PKG position inside the DMG
    #
    # @return [String]
    #   the PKG position inside the DMG
    #
    def pkg_position(val = NULL)
      if null?(val)
        @pkg_position || '535, 50'
      else
        @pkg_position = val
      end
    end
    expose :pkg_position

    #
    # @!endgroup
    # --------------------------------------------------

    #
    # The path where the MSI resources will live.
    #
    # @return [String]
    #
    def resources_dir
      File.expand_path("#{staging_dir}/Resources")
    end

    #
    # Cleans any previously left over mounted disks.
    #
    # We are trying to detach disks that look like:
    #
    #   /dev/disk1s1 on /Volumes/chef (hfs, local, nodev, nosuid, read-only, noowners, quarantine, mounted by serdar)
    #   /dev/disk2s1 on /Volumes/chef 1 (hfs, local, nodev, nosuid, read-only, noowners, quarantine, mounted by serdar)
    #
    # @return [void]
    #
    def clean_disks
      log.info(log_key) { "Cleaning previously mounted disks" }

      existing_disks = shellout!("mount | grep /Volumes/#{volume_name} | awk '{print $1}'")
      existing_disks.stdout.lines.each do |existing_disk|
        existing_disk.chomp!

        Omnibus.logger.debug(log_key) do
          "Detaching disk `#{existing_disk}' before starting dmg packaging."
        end

        shellout!("hdiutil detach '#{existing_disk}'")
      end
    end

    #
    # Create a writable dmg we can put assets on.
    #
    def create_writable_dmg
      log.info(log_key) { "Creating writable dmg" }

      shellout! <<-EOH.gsub(/^ {8}/, '')
        hdiutil create \\
          -srcfolder "#{resources_dir}" \\
          -volname "#{volume_name}" \\
          -fs HFS+ \\
          -fsargs "-c c=64,a=16,e=16" \\
          -format UDRW \\
          -size 512000k \\
          "#{writable_dmg}"
      EOH
    end

    #
    # Attach the dmg, storing a reference to the device for later use.
    #
    # @return [String]
    #   the name of the attached device
    #
    def attach_dmg
      @device ||= Dir.chdir(staging_dir) do
        log.info(log_key) { "Attaching dmg as disk" }

        cmd = shellout! <<-EOH.gsub(/^ {10}/, '')
          hdiutil attach \\
            -readwrite \\
            -noverify \\
            -noautoopen \\
            "#{writable_dmg}" | egrep '^/dev/' | sed 1q | awk '{print $1}'
        EOH

        cmd.stdout.strip
      end
    end

    #
    # Create the icon for the volume using sips.
    #
    # @return [void]
    #
    def set_volume_icon
      log.info(log_key) { "Setting volume icon" }

      icon = resource_path('icon.png')

      Dir.chdir(staging_dir) do
        shellout! <<-EOH.gsub(/^ {10}/, '')
          # Generate the icns
          mkdir tmp.iconset
          sips -z 16 16     #{icon} --out tmp.iconset/icon_16x16.png
          sips -z 32 32     #{icon} --out tmp.iconset/icon_16x16@2x.png
          sips -z 32 32     #{icon} --out tmp.iconset/icon_32x32.png
          sips -z 64 64     #{icon} --out tmp.iconset/icon_32x32@2x.png
          sips -z 128 128   #{icon} --out tmp.iconset/icon_128x128.png
          sips -z 256 256   #{icon} --out tmp.iconset/icon_128x128@2x.png
          sips -z 256 256   #{icon} --out tmp.iconset/icon_256x256.png
          sips -z 512 512   #{icon} --out tmp.iconset/icon_256x256@2x.png
          sips -z 512 512   #{icon} --out tmp.iconset/icon_512x512.png
          sips -z 1024 1024 #{icon} --out tmp.iconset/icon_512x512@2x.png
          iconutil -c icns tmp.iconset

          # Copy it over
          cp tmp.icns "/Volumes/#{volume_name}/.VolumeIcon.icns"

          # Source the icon
          SetFile -a C "/Volumes/#{volume_name}"
        EOH
      end
    end

    #
    # Use Applescript to setup the DMG with pretty logos and colors.
    #
    # @return [void]
    #
    def prettify_dmg
      log.info(log_key) { "Making the dmg all pretty and stuff" }

      render_template(resource_path('create_dmg.osascript.erb'),
        destination: "#{staging_dir}/create_dmg.osascript",
        variables: {
          volume_name:   volume_name,
          pkg_name:      packager.package_name,
          window_bounds: window_bounds,
          pkg_position:  pkg_position,
        }
      )

      Dir.chdir(staging_dir) do
        shellout! <<-EOH.gsub(/^ {10}/, '')
          osascript "#{staging_dir}/create_dmg.osascript"
        EOH
      end
    end

    #
    # Compress the dmg using hdiutil and zlib.
    #
    # @return [void]
    #
    def compress_dmg
      log.info(log_key) { "Compressing dmg" }

      Dir.chdir(staging_dir) do
        shellout! <<-EOH.gsub(/^ {10}/, '')
          chmod -Rf go-w /Volumes/#{volume_name}
          sync
          hdiutil detach "#{@device}"
          hdiutil convert \\
            "#{writable_dmg}" \\
            -format UDZO \\
            -imagekey \\
            zlib-level=9 \\
            -o "#{package_path}"
          rm -rf "#{writable_dmg}"
        EOH
      end
    end

    #
    # Set the dmg icon to our custom icon.
    #
    # @return [void]
    #
    def set_dmg_icon
      log.info(log_key) { "Setting dmg icon" }

      Dir.chdir(staging_dir) do
        shellout! <<-EOH.gsub(/^ {10}/, '')
          # Convert the png to an icon
          sips -i "#{resource_path('icon.png')}"

          # Extract the icon into its own resource
          DeRez -only icns "#{resource_path('icon.png')}" > tmp.rsrc

          # Append the icon reosurce to the DMG
          Rez -append tmp.rsrc -o "#{package_path}"

          # Source the icon
          SetFile -a C "#{package_path}"
        EOH
      end
    end

    # @see Base#package_name
    def package_name
      extname = File.extname(packager.package_name)
      packager.package_name.sub(extname, '.dmg')
    end

    # The path to the writable dmg on disk.
    #
    # @return [String]
    def writable_dmg
      File.expand_path("#{staging_dir}/#{project.name}-writable.dmg")
    end

    #
    # The name of the volume to create. By defauly, this is the project's
    # friendly name.
    #
    # @return [String]
    #
    def volume_name
      project.friendly_name
    end
  end
end