module Omnibus
  class Packager::MacDmg < Packager::Base
    attr_reader :packager

    validate do
      assert_presence!(resource('background.png'))
      assert_presence!(resource('icon.png'))
    end

    setup do
      create_directory(dmg_stage)
      purge_directory(staging_resources_path)
      copy_directory(resources_path, staging_resources_path)

      remove_file(writable_dmg)
      clean_disks
    end

    build do
      copy_assets_to_dmg
      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

    clean do
      remove_file("#{staging_dir}/tmp.icns")
      remove_file("#{staging_dir}/tmp.rsrc")
    end

    #
    # Create a new DMG packager.
    #
    # @param [Packager::MacPkg] mac_packager
    #
    def initialize(mac_packager)
      @packager = mac_packager
      super(mac_packager.project)
    end

    #
    # Cleans any previously left over mounted disks
    #
    def clean_disks
      # We're trying to detach disks that look like below:
      # /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)
      existing_disks = execute "mount | grep /Volumes/#{project.name} | awk '{print $1}'"
      existing_disks.stdout.lines.each do |existing_disk|
        existing_disk.chomp!
        Omnibus.logger.debug { "Detaching disk '#{existing_disk}' before starting dmg packaging." }
        execute "hdiutil detach #{existing_disk}"
      end
    end

    #
    # Copy the assets in the local omnibus project into a staging folder that
    # will soon become our writable dmg.
    #
    def copy_assets_to_dmg
      # Copy the compiled pkg into the dmg
      copy_file(packager.final_pkg, "#{dmg_stage}/#{project.name}.pkg")

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

    #
    # Create a writable dmg we can put assets on.
    #
    def create_writable_dmg
      execute <<-EOH.gsub(/^ {8}/, '')
        hdiutil create \\
          -srcfolder "#{dmg_stage}" \\
          -volname "#{project.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.
    #
    def attach_dmg
      @device = execute(<<-EOH.gsub(/^ {8}/, '')).stdout.strip
        hdiutil attach \\
          -readwrite \\
          -noverify \\
          -noautoopen \\
          "#{writable_dmg}" | egrep '^/dev/' | sed 1q | awk '{print $1}'
      EOH
    end

    #
    # Create the icon for the volume using sips.
    #
    def set_volume_icon
      execute <<-EOH.gsub(/^ {8}/, '')
        # Generate the icns
        mkdir tmp.iconset
        sips -z 16 16     #{resource('icon.png')} --out tmp.iconset/icon_16x16.png
        sips -z 32 32     #{resource('icon.png')} --out tmp.iconset/icon_16x16@2x.png
        sips -z 32 32     #{resource('icon.png')} --out tmp.iconset/icon_32x32.png
        sips -z 64 64     #{resource('icon.png')} --out tmp.iconset/icon_32x32@2x.png
        sips -z 128 128   #{resource('icon.png')} --out tmp.iconset/icon_128x128.png
        sips -z 256 256   #{resource('icon.png')} --out tmp.iconset/icon_128x128@2x.png
        sips -z 256 256   #{resource('icon.png')} --out tmp.iconset/icon_256x256.png
        sips -z 512 512   #{resource('icon.png')} --out tmp.iconset/icon_256x256@2x.png
        sips -z 512 512   #{resource('icon.png')} --out tmp.iconset/icon_512x512.png
        sips -z 1024 1024 #{resource('icon.png')} --out tmp.iconset/icon_512x512@2x.png
        iconutil -c icns tmp.iconset

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

        # Source the icon
        SetFile -a C "/Volumes/#{project.name}"
      EOH
    end

    #
    # Use Applescript to setup the DMG with pretty logos and colors.
    #
    def prettify_dmg
      execute <<-EOH.gsub(/ ^{8}/, '')
        echo '
           tell application "Finder"
             tell disk "'#{project.name}'"
               open
               set current view of container window to icon view
               set toolbar visible of container window to false
               set statusbar visible of container window to false
               set the bounds of container window to {#{project.config[:dmg_window_bounds]}}
               set theViewOptions to the icon view options of container window
               set arrangement of theViewOptions to not arranged
               set icon size of theViewOptions to 72
               set background picture of theViewOptions to file ".support:'background.png'"
               delay 5
               set position of item "'#{project.name}.pkg'" of container window to {#{project.config[:dmg_pkg_position]}}
               update without registering applications
               delay 5
             end tell
           end tell
        ' | osascript
      EOH
    end

    #
    # Compress the dmg using hdiutil and zlib.
    #
    def compress_dmg
      execute <<-EOH.gsub(/ ^{8}/, '')
        chmod -Rf go-w /Volumes/#{project.name}
        sync
        hdiutil detach "#{@device}"
        hdiutil convert \\
          "#{writable_dmg}" \\
          -format UDZO \\
          -imagekey zlib-level=9 \\
          -o "#{final_dmg}"
        rm -rf "#{writable_dmg}"
      EOH
    end

    #
    # Set the dmg icon to our custom icon.
    #
    def set_dmg_icon
      execute <<-EOH.gsub(/^ {8}/, '')
        # Convert the png to an icon
        sips -i "#{resource('icon.png')}"

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

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

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

    # @see Base#package_name
    def package_name
      "#{name}-#{version}-#{iteration}.dmg"
    end

    # The path to the folder that we should stage.
    #
    # @return [String]
    def dmg_stage
      File.expand_path("#{staging_dir}/dmg")
    end

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

    # The path where the final dmg will be produced.
    #
    # @return [String]
    def final_dmg
      File.expand_path("#{project.package_dir}/#{name}-#{version}-#{iteration}.dmg")
    end
  end
end
