#!/usr/bin/ruby.ruby2.1
#
# A simple script to update sources in rails app in order to bundle
# required gems
#
# (C) 2018 SUSE LLC
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
# See http://www.gnu.org/licenses/gpl-2.0.html for full license text.
#
require 'bundler'
require 'rubygems/package'
require 'zlib'
require 'tempfile'
require 'logger'
require 'fileutils'
require 'optparse'
require 'fileutils'
require 'open3'

logger = Logger.new(STDOUT)
logger.level = Logger::INFO
logger.progname = File.basename($PROGRAM_NAME)
logger.formatter = proc do |severity, datetime, progname, msg|
  date_format = datetime.strftime('%Y-%m-%d %H:%M:%S')
  "[#{date_format}] #{severity.ljust(5)} (#{progname}): #{msg}\n"
end

options = { strategy: :spec }
OptionParser.new do |opts|
  opts.banner = "Usage: #{ARGV[0]} [options]"
  opts.on('-oDIR', '--outdir=DIR', 'Output Directory') do |v|
    options[:outdir] = v
  end
  strategies = %i[cpio spec]
  msg = "Choose the strategy the service runs in. Options: #{strategies.join(', ')}"
  opts.on('--strategy=STRING', strategies, msg) do |v|
    options[:strategy] = v
  end
end.parse!

outdir = options[:outdir] || Dir.pwd
bundled_gems = []

# to match _service:bundle_gems:Gemfile and Gemfile
gem_file = Dir.glob('*Gemfile').first.to_s
if gem_file.empty?
  logger.fatal 'No Gemfile found'
  exit(1)
end
logger.info "Using #{gem_file}"

# to match _service:bundle_gems:Gemfile.lock and Gemfile.lock
gem_file_lock = Dir.glob('*Gemfile.lock').first.to_s
if gem_file_lock.empty?
  logger.fatal 'No Gemfile.lock found'
  exit(1)
end
logger.info "Using #{gem_file_lock}"

if options[:strategy] == :cpio
  FileUtils.cp gem_file, File.join(outdir, 'Gemfile')
  FileUtils.cp gem_file_lock, File.join(outdir, 'Gemfile.lock')
  vendor = Dir.glob('*vendor.obscpio').first.to_s
  unless vendor.empty?
    logger.info 'Extracting vendor.obscpio...'
    FileUtils.cp vendor, options[:outdir]
    Dir.chdir(options[:outdir]) do
      stdout_and_stderr_str, status = Open3.capture2e(
        "cpio -i --make-directories --format=newc < #{vendor}"
      )
      logger.info stdout_and_stderr_str
      abort(stdout_and_stderr_str) unless status.success?
    end
  end

  if ENV['OBS_SERVICE_BUNDLE_GEMS_MIRROR_URL']
    Dir.chdir(options[:outdir]) do
      mirror_url = ENV['OBS_SERVICE_BUNDLE_GEMS_MIRROR_URL']
      stdout_and_stderr_str, status = Open3.capture2e("
        bundle config --local mirror.http://rubygems.org #{mirror_url}")
      logger.info stdout_and_stderr_str
      abort(stdout_and_stderr_str) unless status.success?
      stdout_and_stderr_str, status = Open3.capture2e(
        "bundle config --local mirror.https://rubygems.org #{mirror_url}"
      )
      logger.info stdout_and_stderr_str
      abort(stdout_and_stderr_str) unless status.success?
    end
  end

  Dir.chdir(options[:outdir]) do
    stdout_and_stderr_str, status = Open3.capture2e(
      'bundle package --no-install --all --path . --verbose'
    )
    logger.info stdout_and_stderr_str
    abort(stdout_and_stderr_str) unless status.success?
    stdout_and_stderr_str, status = Open3.capture2e(
      'find vendor/cache/ -depth -type f -print |' \
      ' cpio --format=newc -o > vendor.obscpio'
    )
    abort(stdout_and_stderr_str) unless status.success?
    logger.info stdout_and_stderr_str
    FileUtils.rm ['Gemfile', 'Gemfile.lock']
    FileUtils.remove_dir 'vendor'
    FileUtils.remove_dir 'ruby'
    FileUtils.remove_dir '.bundle'
  end

  exit 0
end

spec = Dir['*.spec'].first
unless spec
  logger.fatal 'No spec found'
  exit(1)
end

logger.info 'Resolving...'
definition = Bundler::Definition.build(gem_file, gem_file_lock, nil)
bundled_gems.concat definition.resolve.to_a.sort_by(&:to_s)
logger.info "  #{bundled_gems.size} gems..."

logger.info "Updating #{spec}"
# Now parse the spec file
gems_start = false
new_spec_lines = []
File.open(spec, 'r').each_line do |line|
  if line =~ /^### GEMS START/
    gems_start = true
    new_spec_lines.push(line)
    i = 100
    bundled_gems.each do |s|
      new_spec_lines.push("Source#{i}: https://rubygems.org/downloads/#{s.name}-#{s.version}.gem\n")
      i += 1
    end
    new_spec_lines.push("### GEMS END\n")
    next
  end

  if line =~ /^### GEMS END/
    gems_start = false
    next
  end

  if line =~ /^Source(.*):/
    # drop this one
    next if gems_start
  end

  new_spec_lines.push(line)
end

File.open(File.join(outdir, spec), 'w') do |f|
  f.write(new_spec_lines.join(''))
end
logger.info 'DONE'
