#!/usr/bin/env ruby

require "rubygems"
require "bundler"

Bundler.with_clean_env do
  require_relative "../gemfile_util"

  options = {}
  opts = OptionParser.new do |opts|
    opts.banner = "Usage: create-override-gemfile [OPTIONS]"

    opts.on("--gemfile GEMFILE", "The Gemfile to read (default: Gemfile).") { |path| options[:gemfile_path] = path }
    opts.on("--lockfile GEMFILE", "The lockfile to read (default: <gemfile>.lock).") { |path| options[:lockfile_path] = path }

    opts.on("--group GROUP", "Groups to include (whitelist).") do |group|
      options[:groups] ||= []
      options[:groups] << group.to_sym
    end

    opts.on("--without GROUP", "Groups to exclude.") do |group|
      options[:without_groups] ||= []
      options[:without_groups] << group.to_sym
    end

    opts.on("--gem GEM", "Gems to include regardless of groups.") do |name|
      options[:gems] ||= []
      options[:gems] << name
    end

    opts.on("--relative-to PATH", "A path to prepend to any relative paths in the Gemfile.") do |path|
      unless Pathname.new(path).absolute?
        puts opts
        raise "--relative-to #{path} was not an absolute path!"
      end
      options[:relative_to] = path
    end

    opts.on("--[no-]copy-groups", "Whether to copy groups over from the original Gemfile or not (default: false).") { |val| options[:copy_groups] = val }

    opts.on("--[no-]override", "Whether to emit override: true on each gem line (default: false).") { |val| options[:override] = val }

    opts.on("-h", "--help", "Print this message.") do
      puts opts
      exit(0)
    end
  end

  args = opts.parse(ARGV)

  if args.size > 0
    puts opts
    raise "Invalid arguments #{args}"
  end

  def create_override_gemfile(gemfile_path: "Gemfile", lockfile_path: "#{gemfile_path}.lock", groups: nil, without_groups: nil, gems: [], copy_groups: false, relative_to: ".", override: false)
    relative_to = Pathname.new(relative_to).realpath
    # Select the gems we want
    bundle = GemfileUtil::Bundle.parse(gemfile_path, lockfile_path)
    gems_to_include = bundle.select_gems(groups: groups, without_groups: without_groups)
    gems.each do |name|
      raise "Requested gem #{name} is not in #{gemfile_path}.lock!" if !bundle.gems[name]
      gems_to_include[name] ||= bundle.gems[name]
      gems_to_include[name][:dependencies].each do |dep|
        gems_to_include[name] ||= bundle.gems[dep]
      end
    end

    # Add the gems to the Gemfile
    gem_root = Pathname.new(gemfile_path).dirname.realpath
    gems_to_include.sort_by { |name, options| options[:declared_groups].empty? ? 1 : 0 }.each do |name, options|
      comment = nil
      options = options.dup
      version = options.delete(:version)
      if copy_groups
        # Some dependencies have no groups (are not in the Gemfile--just runtime
        # dependencies). If we actually record that they have no groups, they
        # will *always* be installed (or perhaps never). We only want them to
        # install if their other deps do, so we mark them with the groups of the
        # things that brought them in (the gems that depended on them). To do
        # this, we just leave :groups intact.
        if options[:declared_groups].empty?
          options.delete(:declared_groups)
          comment = " # Transitive dependency, not actually in original Gemfile"
        else
          # For other things, we want to copy the actual declared_groups--the
          # ones that were in the Gemfile. We want the same --with and --without
          # options to include and exclude them as worked with the original
          # Gemfile.
          options[:groups] = options.delete(:declared_groups)
        end
      else
        options.delete(:groups)
        options.delete(:declared_groups)
      end
      options.delete(:dependencies)
      options.delete(:development_dependencies)
      options[:override] = true if override
      options[:path] = Pathname.new(options[:path]).expand_path(gem_root).relative_path_from(relative_to).to_s if options[:path]
      line = "gem #{name.inspect}, #{version.inspect}"
      options.each do |name, value|
        line << ", #{name}: #{value.inspect}"
      end
      line << comment if comment
      puts line
    end
  end

  create_override_gemfile(options)
end
