#!/usr/bin/env ruby
#
# Generate a changelog entry file in the correct location.
#
# Automatically stages the file and amends the previous commit if the `--amend`
# argument is used.

require 'optparse'
require 'yaml'

Options = Struct.new(
  :amend,
  :author,
  :dry_run,
  :force,
  :merge_request,
  :title
)

class ChangelogOptionParser
  def self.parse(argv)
    options = Options.new

    parser = OptionParser.new do |opts|
      opts.banner = "Usage: #{__FILE__} [options] [title]\n\n"

      # Note: We do not provide a shorthand for this in order to match the `git
      # commit` interface
      opts.on('--amend', 'Amend the previous commit') do |value|
        options.amend = value
      end

      opts.on('-f', '--force', 'Overwrite an existing entry') do |value|
        options.force = value
      end

      opts.on('-m', '--merge-request [integer]', Integer, 'Merge Request ID') do |value|
        options.merge_request = value
      end

      opts.on('-n', '--dry-run', "Don't actually write anything, just print") do |value|
        options.dry_run = value
      end

      opts.on('-u', '--git-username', 'Use Git user.name configuration as the author') do |value|
        options.author = git_user_name if value
      end

      opts.on('-h', '--help', 'Print help message') do
        $stdout.puts opts
        exit
      end
    end

    parser.parse!(argv)

    # Title is everything that remains, but let's clean it up a bit
    options.title = argv.join(' ').strip.squeeze(' ').tr("\r\n", '')

    options
  end

  def self.git_user_name
    %x{git config user.name}.strip
  end
end

class ChangelogEntry
  attr_reader :options

  def initialize(options)
    @options = options

    assert_feature_branch!
    assert_new_file!
    assert_title!

    $stdout.puts "\e[32mcreate\e[0m #{file_path}"
    $stdout.puts contents

    unless options.dry_run
      write
      amend_commit if options.amend
    end
  end

  def contents
    YAML.dump(
      'title'         => title,
      'merge_request' => options.merge_request,
      'author'        => options.author
    )
  end

  def write
    File.write(file_path, contents)
  end

  def amend_commit
    %x{git add #{file_path}}
    exec("git commit --amend")
  end

  private

  def fail_with(message)
    $stderr.puts "\e[31merror\e[0m #{message}"
    exit 1
  end

  def assert_feature_branch!
    return unless branch_name == 'master'

    fail_with "Create a branch first!"
  end

  def assert_new_file!
    return unless File.exist?(file_path)
    return if options.force

    fail_with "#{file_path} already exists! Use `--force` to overwrite."
  end

  def assert_title!
    return if options.title.length > 0 || options.amend

    fail_with "Provide a title for the changelog entry or use `--amend`" \
      " to use the title from the previous commit."
  end

  def title
    if options.title.empty?
      last_commit_subject
    else
      options.title
    end
  end

  def last_commit_subject
    %x{git log --format="%s" -1}.strip
  end

  def file_path
    File.join(
      unreleased_path,
      branch_name.gsub(/[^\w-]/, '-') << '.yml'
    )
  end

  def unreleased_path
    File.join('changelogs', 'unreleased').tap do |path|
      path << '-ee' if ee?
    end
  end

  def ee?
    @ee ||= File.exist?(File.expand_path('../CHANGELOG-EE.md', __dir__))
  end

  def branch_name
    @branch_name ||= %x{git symbolic-ref HEAD}.strip.sub(%r{\Arefs/heads/}, '')
  end
end

if $0 == __FILE__
  options = ChangelogOptionParser.parse(ARGV)
  ChangelogEntry.new(options)
end

# vim: ft=ruby
