# encoding: utf-8
require 'rubygems'
require 'digest/sha2'

# Based on https://github.com/bundler/bundler/blob/ec0621d/lib/bundler/gem_helpers.rb
GEM_TASKS = Class.new(Rake::TaskLib) do
  include Rake::DSL if defined? Rake::DSL
  attr_reader :base
  def initialize
    @base = File.expand_path('../..', __FILE__)
    @gemspec_path = File.join(base, 'metric_fu.gemspec')
    @gemspec = Gem::Specification.load(@gemspec_path)
  end

  def install
    built_gem_path = nil

    desc "Build #{built_gem_name} into the pkg directory."
    task 'build' do
      built_gem_path = build_gem
    end

    desc "Build and install #{built_gem_name} into system gems."
    task 'install' => 'build' do
      install_gem(built_gem_path)
    end

    desc 'Creates and commits a SHA512 checksum of the current version built gem'
    task 'checksum' => 'build' do
      add_checksum(built_gem_path)
    end

    desc "Create tag #{version_tag} and build and push #{built_gem_name} to Rubygems"
    task 'release' => 'checksum' do
      release_gem(built_gem_path)
    end

  end

  def build_gem
     file_name = nil
     sh("gem build -V '#{@gemspec_path}'") { |out, code|
       file_name = File.basename(built_gem_path)
       FileUtils.mkdir_p(File.join(base, 'pkg'))
       FileUtils.mv(built_gem_path, 'pkg')
       STDOUT.puts "#{name} #{version} built to pkg/#{file_name}."
     }
     File.join(base, 'pkg', file_name)
  end

  def install_gem(built_gem_path=nil)
    built_gem_path ||= build_gem
    out, _ = sh_with_code("gem install '#{built_gem_path}' --local")
    raise "Couldn't install gem, run `gem install #{built_gem_path}' for more detailed output" unless out[/Successfully installed/]
    STDOUT.puts "#{name} (#{version}) installed."
  end
  # Based on https://github.com/YorickPeterse/ruby-lint/blob/3e946/task/checksum.rake
  def add_checksum(built_gem_path=nil)
    guard_clean
    built_gem_path ||= build_gem
    checksum_file = File.join(checksums, checksum_name)
    File.open(checksum_file, 'w') do |handle|
      handle.write(gem_checksum(built_gem_path))
    end
    sh_with_code("git add #{checksum_file} && git commit -m 'Add checksum for #{built_gem_name}'")
  end

  def checksums
    File.expand_path('../../checksum', __FILE__)
  end

  def checksum_name
    File.basename(built_gem_name) + '.sha512'
  end

  def gem_checksum(built_gem_path)
    checksum(File.read(built_gem_path))
  end

  def checksum(content)
    Digest::SHA512.new.hexdigest(content)
  end

  def release_gem(built_gem_path=nil)
    guard_clean
    built_gem_path ||= build_gem
    add_checksum(built_gem_path) #unless already hashed?
    tag_version { git_push } unless already_tagged?
    rubygem_push(built_gem_path) if gem_push?
  end

  protected

  def rubygem_push(path)
    if Pathname.new("~/.gem/credentials").expand_path.exist?
      sh("gem push '#{path}'")
      STDOUT.puts "Pushed #{name} #{version} to rubygems.org."
    else
      raise "Your rubygems.org credentials aren't set. Run `gem push` to set them."
    end
  end

  def built_gem_path
    Dir[File.join(base, "#{name}-*.gem")].sort_by{|f| File.mtime(f)}.last
  end

  def git_push
    perform_git_push
    perform_git_push ' --tags'
    STDOUT.puts "Pushed git commits and tags."
  end

  def perform_git_push(options = '')
    cmd = "git push #{options}"
    out, code = sh_with_code(cmd)
    raise "Couldn't git push. `#{cmd}' failed with the following output:\n\n#{out}\n" unless code == 0
  end

  def already_tagged?
    if sh('git tag').split(/\n/).include?(version_tag)
      STDOUT.puts "Tag #{version_tag} has already been created."
      true
    end
  end

  def guard_clean
    clean? && committed? or raise("There are files that need to be committed first.")
  end

  def clean?
    sh_with_code("git diff --exit-code")[1] == 0
  end

  def committed?
    sh_with_code("git diff-index --quiet --cached HEAD")[1] == 0
  end

  def tag_version
    sh "git tag -a -m \"Version #{version}\" #{version_tag}"
    STDOUT.puts "Tagged #{version_tag}."
    yield if block_given?
  rescue
    STDERR.puts "Untagging #{version_tag} due to error."
    sh_with_code "git tag -d #{version_tag}"
    raise
  end

  def version
    @gemspec.version
  end

  def version_tag
    "v#{version}"
  end

  def name
    @gemspec.name
  end

  def built_gem_name
    "#{name}-#{version}.gem"
  end

  def sh(cmd, &block)
    out, code = sh_with_code(cmd, &block)
    code == 0 ? out : raise(out.empty? ? "Running `#{cmd}' failed. Run this command directly for more detailed output." : out)
  end

  def sh_with_code(cmd, &block)
    cmd << " 2>&1"
    outbuf = ''
    p cmd
    Dir.chdir(base) {
      outbuf = `#{cmd}`
      if $? == 0
        block.call(outbuf) if block
      end
    }
    [outbuf, $?]
  end

  def gem_push?
    ! %w{n no nil false off 0}.include?(ENV['gem_push'].to_s.downcase)
  end

end.new.install

# Based on https://github.com/YorickPeterse/ruby-lint/blob/3e946e6/task/todo.rake
desc 'Extracts TODO tags and the likes'
task :todo do
  regex = %w{NOTE: FIXME: TODO: THINK: @todo}.join('|')

  sh "ack '#{regex}' lib"
end