#!/usr/bin/env ruby

require 'hackmac'
include Hackmac
require 'term/ansicolor'
class String
  include Term::ANSIColor
end
require 'tabulo'

def x(cmd, verbose: true)
  prompt = cmd =~ /\A\s*sudo/ ? ?# : ?$
  output = `#{cmd}`
  if $?.success?
    print "#{prompt} #{cmd}".green
    puts verbose ? "" : " >/dev/null".yellow
  else
    print "#{prompt} #{cmd}".red
    puts verbose ? "" : " >/dev/null".yellow
    STDERR.puts "command #{cmd.inspect} failed with exit status #{$?.exitstatus}".on_red.white
  end
  if verbose
    print output.italic
  end
  output
end

def clone(from:, to:)
  print "Cloning from #{from} to #{to} now? (y/n) ".bold.yellow
  if gets !~ /\Ay/i
    return
  end
  x %{sudo mkdir -v "/Volumes/#{from}"}
  x %{sudo mkdir -v "/Volumes/#{to}"}
  x %{sudo diskutil mount -mountPoint "/Volumes/#{from}" "#{from}"}
  x %{sudo diskutil mount -mountPoint "/Volumes/#{to}" "#{to}"}
  x %{rsync -nav --exclude ".*" --delete "/Volumes/#{from}/" "/Volumes/#{to}/"}
  print "This will be copied/deleted. Really do it? (y/n) ".bold.yellow
  if gets !~ /\Ay/i
    puts " *** Interrupted.".bold.yellow
    exit 1
  end
  x %{rsync -av --exclude ".*" --delete "/Volumes/#{from}/" "/Volumes/#{to}/"}
end

def boot_dev
  unless @boot_dev
    value = x(%{bdmesg}, verbose: false).lines.
      find { |l| l =~ /SelfDevicePath=(.*)\r/ and break $1 }
    uuid  = value.split('\\').last[/(?:<?GPT,)([\h-]+)/, 1] or exit 1
    @boot_dev = x(%{partutil --search-uuid #{uuid}}).chomp
  end
  @boot_dev
end

def usage
  default_dev = boot_dev

  puts <<~end

    Usage #{File.basename($0)} [command] [arguments]

    Commands are

      help        this help

      mount       EFI partition

        argument DEVICE (defaults to #{default_dev})

      unmount     EFI partition

        argument DEVICE (defaults to #{default_dev})

      clone       clone EFI partitions

        argument FROM_DEVICE (defaults to #{default_dev})

        argument TO_DEVICE (defaults to #{default_dev})

        The devices have to be different.

      kext        list version info of kext

        argument PATH to the kext

      kexts       list kexts versions by clover

        argument DEVICE (defaults to #{default_dev})

        The EFI partion on DEVICE is used to find the clover kexts in
        subdirectory /Other.

      list        shows boot disk information (which EFI partition was used)

  end
end

$config = Hackmac::Config.load

def device_name(mdev)
  case mdev
  when 'boot'
    boot_dev
  when nil
    $config.devices.main.name
  else
    if n = $config.devices[mdev]&.name
      n
    else
      mdev
    end
  end
end

bold_head = -> h { h.sub(/\A[a-z]/) { $&.upcase }.bold }

case command = ARGV.shift
when 'help', nil
  usage
when 'mount'
  #if File.exist?('/Volumes/EFI')
  #  raise "/Volumes/EFI already exists => Sort this out first."
  #end
  mdev = device_name(ARGV.shift)
  x %{sudo diskutil mount "#{mdev}"}
  # TODO symlink to /Volumes/mdev, mdev should be foo['MountPoint'] from efi boot
when /\Aun?mount\z/
  mdev = device_name(ARGV.shift)
  x %{sudo diskutil unmount "#{mdev}"}
  # TODO remove /Volumes/mdev if it is a symlink
when 'clone'
  from = ARGV.shift or fail "need from argument"
  from = device_name(from)
  to = ARGV.shift or fail "need to argument"
  to = device_name(to)
  from != to or fail "cloning only allowed from one partition to another"
  clone from: from, to: to
when 'kexts'
  mdev = device_name(ARGV.shift)
  x %{sudo diskutil mount "#{mdev}"}
  on_efi = Dir["/Volumes/#{mdev}/#{$config.kext.efi_path}/*.kext"].map { |path|
    Kext.new(path: path, config: $config)
  }.sort_by(&:name)
  puts 'EFI'.yellow.bold + " (#{mdev})".bold
  puts Tabulo::Table.new(on_efi, align_header: :left, border: :modern) { |t|
    t.add_column(:name, header_styler: bold_head)
    t.add_column(:itself, header: 'Version/Remote',
                 styler: -> v, s { v.version < v.remote_version ? s.red : s.green rescue s.yellow },
                 formatter: -> e { "%s %s %s" % [ e.version, ({ 0 => ?=, -1 => ?<, 1 => ?> }[e.version <=> e.remote_version] rescue nil), e.remote_version ] },
                 header_styler: bold_head)
  }.pack
when 'kext'
  path = ARGV.shift or fail 'need kext dir'
  puts Kext.new(path: path)
when 'list'
  disks = Disks.new
  efis = []
  disks.AllDisksAndPartitions.each_with_object([]) { |d, ps|
    uuids = Array(d['Partitions']&.map { |p| p['DiskUUID'] }&.compact)
    efis.concat uuids.
      map { |uuid| DiskInfo.new(disk: uuid) }.
      select { |di| di.FilesystemType == 'msdos' }
  }
  efis.each do |e|
    e.Booted = e.DeviceIdentifier == boot_dev
    e.Volumes =
      ContainerDisk.new(disk: e.ParentWholeDisk, limiter: 'internal').VolumesFromDisks
  end
  puts Tabulo::Table.new(efis, align_header: :left, border: :modern) { |t|
    t.add_column(:Booted, styler: -> v, _ { v == ?☑ ? v.green : v.red }, header_styler: bold_head) { |e| e.Booted ? ?☑ : ?☐ }
    t.add_column(:VolumeName, header_styler: bold_head)
    t.add_column(:DeviceIdentifier, header_styler: bold_head)
    t.add_column(:ParentWholeDisk, header_styler: bold_head)
    t.add_column(:DiskUUID, header_styler: bold_head)
    t.add_column(:Volumes, header_styler: bold_head) { |e| e.Volumes * ?, }
    t.add_column(:MountPoint, styler: -> v, _ { v.empty? ? v : v.green }, header_styler: bold_head)
  }.pack
else
  fail "don't know how to #{command}"
end