#! /usr/bin/ruby

#/ Script to get repository data from github and generate RPM spec file with
#/ genspec. Program works in interactive mode, only one required value is URL.
#/ Then you insert URL, the program will ask you to enter another values, and
#/ prints default value in square brakets, for example [name]. If you accept
#/ it then just press Enter.
#/
#/ Usage: github2spec [options] [-- <genspec-options>]
#/
#/ Available options:
#/   -i, --interactive=level set interactive level:
#/                           0 -- no interaction (only ask url)
#/                           1 -- default value
#/                           2 -- full interaction (ask all values)
#/   -u, --url=url           provide url and set interactive to 0
#/   -v, --version           show version
#/   -p, --print-only        only prints genspec execution string
#    -e, --executable        use for executable packages instead of libs
#/   -h, --help              show help

# (c) 2017 Mikhail Gordeev <obirvalger@altlinux.org>
# This program is free software; you can redistribute it and/or modify it
# under the terms of MIT License.

require 'open-uri'
require 'json'
require 'optparse'
require 'shellwords'
require 'pp'
require 'readline'

GITHUB_API_URL = 'https://api.github.com/repos/'

NEEDED_PARAMETRS = [
  "name",
  "summary",
  "license",
  "description",
  "type",
  "url",
  "version",
  "changelog",
  "owner",
]

class Hash
  def needed
    self.select { |key, value| NEEDED_PARAMETRS.include? key }
  end

  def needed!
    self.select! { |key, value| NEEDED_PARAMETRS.include? key }
  end

  def to_args
    self.reduce("") do |acc, (key, value)|
      "#{acc} -#{key[0]} #{value}"
    end
  end
end

# TODO specify github api version v3
def github_parameters(repo)
  result = {}
  begin
    jr = JSON.load(open(GITHUB_API_URL + repo))
    # list of parameters goes as is
    ["name"].each do |v|
      result[v] = jr[v]
    end

    result['type'] = case jr['language']
    when "Ruby"
      "ruby"
    when "Python"
      "python3"
    when "Go"
      "golang-bin"
    else
      "common"
    end

    # Githubs "description" is "summary" in rpms mean
    result["summary"] = jr["description"]
    jl = begin
      JSON.load(open (GITHUB_API_URL + repo + '/license'))
    rescue OpenURI::HTTPError
      nil
    end
    result["license"] = jl["license"]["spdx_id"] if jl
    jt = JSON.load(open (GITHUB_API_URL + repo + '/tags'))
    tags = jt.map { |x| x["name"] }
    tags = nil if tags == []
    if tags
      result["tags"] = tags
      result["tag"] = tags[0]
      result["version"] = result["tag"].gsub(/^v/,'')
    end
    return result
  rescue SocketError
    abort "No internet connection"
  rescue OpenURI::HTTPError
    abort "Not a valid github repository"
  end
end

# Function simulating github_parameters for testing
def github_test_parameters(repo)
  {
    "name" => "pry",
    # "type" => "ruby",
    "summary"=>"An IRB alternative and runtime developer console",
    "license" => "MIT",
    "tag"=>"v0.10.4",
    "version" => "0.10.4",
  }
end

default_parameters = {
  "description" => "%summary",
  "changelog" => "- Initial build for Sisyphus",
}

# Values specified with command line options
cmd_parameters = {}

# Default value for command line options controling this program
options = {
  interactive_level: -1, # To distinguish default value from set in command line
  url: nil,
  print_only: false,
}

OptionParser.new do |opts|
  # Options to control program execution
  opts.on("-i", "--interactive=[level]", Integer) do |level|
    level ||= 1 # default value if no level provided
    abort "Interactive level should be in [0,1,2], get #{level}!" if level<0 or level>2
    options[:interactive_level] = level
  end
  opts.on("-u", "--url=val", String) do |val|
    options[:url] = val
    options[:interactive_level] = 0 if options[:interactive_level] == -1
    cmd_parameters["url"] ||= val
  end
  opts.on("-v", "--version") { puts "1.4.5"; exit }
  opts.on("-p", "--print-only") { options[:print_only] = true }
  opts.on("-e", "--executable") { options[:executable] = true }
  opts.on("--tag=val") { |tag| options[:tag] = tag }
  opts.on("--here") { options[:here] = true }
  opts.on("--force") { options[:force] = true }
  opts.on_tail("-h", "--help") do
    system "grep ^#/<'#{__FILE__}' | cut -c4-"
    puts "\nParameters passed to genspec:"
    NEEDED_PARAMETRS.each do |parameter|
      uc_parameter = parameter.upcase
      puts "  -#{uc_parameter[0]}, --#{uc_parameter}=#{parameter}"
    end
    exit 0
  end

  # Parameters get with command line options
  NEEDED_PARAMETRS.each do |parameter|
    # Use upper case values to distinguish with control options
    uc_parameter = parameter.upcase
    opts.on("-#{uc_parameter[0]}", "--#{uc_parameter}=val", String) do |val|
      cmd_parameters[parameter] = val
    end
  end

  opts.parse!
  options[:interactive_level] = 1 if options[:interactive_level] < 0
end.parse!

if ARGV.length >= 1
  unless options[:url]
    options[:url]=ARGV[0]
  end
end

def input_with_check(prompt, default = nil)
  all_prompt = "#{prompt}: "
  if default
    all_prompt = "#{prompt} [#{default}]: "
  end
  res = Readline.readline(all_prompt)
  # skip empty string and string containing only white spaces
  while res =~ /^\s*$/ and default.nil?
      puts "Parameter should not be empty"
      res = Readline.readline(all_prompt)
  end
  if res =~ /^\s*$/
    default
  else
    res
  end
end

def get_available_types(templates_path)
  available_types = []
  begin
    Dir.foreach(templates_path) do |fname|
      available_types << fname.gsub(/.spec$/, '') if fname =~ /.spec$/
    end

  rescue Errno::ENOENT
    puts "Directory #{templates_path} does not exist or does not contain any spec templates"
  end

  available_types.sort
end

available_types = get_available_types(ENV["GENSPEC_TEMPLATES"] ||
                                      "/usr/share/spectemplates/")
def available_types.to_str
  " (#{self.join(',')})"
end

url = options[:url] || input_with_check('URL')
cmd_parameters["url"] = url
repo = url.sub(/^.*github.com\//,'').sub(/(\.git|\/)$/,'')

# Priority of parameter sources in ascending order: default, github, cmd
parameters = default_parameters.merge(github_parameters(repo)).merge(cmd_parameters)
# parameters = default_parameters.merge(github_test_parameters(repo)).merge(cmd_parameters)
parameters["owner"] = repo.sub(/\/.*/,'')

NEEDED_PARAMETRS.each do |parameter|
  default = parameters[parameter]
  # TODO available types
  prompt = "Package #{parameter}"
  if parameter == "type"
    prompt += available_types
  end
  ilevel = options[:interactive_level]
  if ilevel == 2 or (ilevel == 1 and default.nil?)
    parameters[parameter] = input_with_check(prompt, default)
  elsif ilevel == 0 and default.nil?
    abort("Can't get value for <#{parameter}>.")
  end
end

if options[:tag]
  parameters["tag"] = options[:tag]
  if not cmd_parameters["version"]
    parameters["version"] = parameters["tag"].gsub(/^v/,'')
  end
else
  parameters["tags"].select { |tag| /v?#{parameters["version"]}/ =~ tag }.each do |tag|
    parameters["tag"] = tag
  end
end

if options[:executable]
  exec_spec_type = available_types.find { |t| t == parameters["type"] + "-bin" }
  parameters["type"] = exec_spec_type if exec_spec_type
end

exec_options = %W[genspec --git --tag #{parameters["tag"]}]
[:here, :force].each do |genspec_flag|
  if options[genspec_flag]
    exec_options.push("--#{genspec_flag.to_s}")
  end
end
parameters.needed.each do |key, value|
  exec_options.push("-#{key[0]}", value)
end

if options[:print_only]
  puts exec_options.shelljoin
else
  # NOTE do not forget to check return code (true is 0)
  exec(*exec_options)
end
