# gem install xcodeproj claide hashdiff

require 'xcodeproj'
require 'claide'
require 'pp'

argv = CLAide::ARGV.new(ARGV)
project_path = argv.shift_argument
target_name = argv.option("target")
target_type = argv.option("target-type", "PBXNativeTarget")
config_name = argv.option("config")
diff = argv.flag?("diff")
intersection = argv.flag?("intersection")

if project_path.nil? || argv.flag?('help')
  puts "Usage:"
  puts "\truby #{__FILE__} path/to/the.xcodeproj [OPTIONS]"
  puts
  puts "Options:"
  puts "\t--target-type=type\tThe types of targets to operate on, defaults to PBXNativeTarget"
  puts "\t--target=name\t\tThe target to limit to"
  puts "\t--config=name\t\tThe build configuration to operate on/limit to"
  puts "\t--intersection\t\tFinds the intersection of all configurations"
  puts "\t--diff\t\t\tWhen combined with `--intersection` target configurations,\n\t\t\t\tare diffed against the intersection; otherwise\n\t\t\t\tconfigurations in `target` are diffed against `config`"
  puts
  exit 1
end

def print_header(content)
  puts "=" * 80
  puts content
  puts "=" * 80
end

def print_target_header(target, config)
  print_header "#{target.name}: #{config.name}"
end

def dump_target(target, config_name = nil)
  target.build_configurations.each do |config|
    next if config_name && config.name != config_name
    print_target_header(target, config)
    pp config.build_settings
    puts
  end
end

def diff_target_config(target, base_config, skip_config_name = nil)
  require 'hashdiff'
  target.build_configurations.each do |config|
    next if config.name == skip_config_name
    print_target_header(target, config)
    pp Hashdiff.diff(base_config, config.build_settings)
    puts
  end
end

def diff_target_config_to_others(target, config_name)
  base_config = target.build_configurations.find { |c| c.name == config_name }
  diff_target_config(target, base_config.build_settings, config_name)
end

def intersect(targets, config_name)
  require 'hashdiff'
  result = nil
  targets.each do |target|
    target.build_configurations.each do |config|
      next if config_name && config.name != config_name
      if result.nil?
        result = config.build_settings
      else
        Hashdiff.diff(result, config.build_settings)
          .reverse # Remove elements from back to front, otherwise indices will no longer be correct.
          .each do |type, key, *val|
            case type
            when "-"
              if key.include?("[")
                _, key, index = key.match(/^(\w+)\[(\d+)\]$/).to_a
                result[key].delete_at(index.to_i)
              else
                result.delete(key)
              end
            when "~"
              before, after = Array(val[0]), Array(val[1])
              before.delete_if { |x| !after.include?(x) }
              case before.size
              when 0
                result.delete(key)
              when 1
                result[key] = before[0]
              else
                result[key] = before
              end
            else
            end
          end
      end
    end
  end
  result
end

project = Xcodeproj::Project.open(project_path)
targets = target_type ? project.targets.select { |t| t.class.name.split("::").last == target_type } : project.targets

if intersection
  result = intersect(targets, config_name)
  print_header("Intersection")
  pp result
  if diff
    targets.each do |target|
      next if target_name && target.name != target_name
      diff_target_config(target, result)
    end
  end
else
  if target_name
    target = targets.find { |t| t.name == target_name }
    if diff
      diff_target_config_to_others(target, config_name)
    else
      dump_target(target, config_name)
    end
  else
    targets.each do |target|
      dump_target(target, config_name)
    end
  end
end