require "devops-client/version"
require "devops-client/options/helpers/extendable"
require "devops-client/options/helpers/devops_options_parser"

class CommonOptions
  extend Options::Helpers::Extendable

  attr_accessor :header, :args, :default_options
  attr_writer :banner_header

  TABLE_FORMAT = "table"
  JSON_FORMAT = "json"
  CSV_FORMAT = "csv"
  OUTPUT_FROMATS = [TABLE_FORMAT, JSON_FORMAT, CSV_FORMAT]

  # skip command name and subcommand
  def initialize args, def_options
    self.args = args
    self.default_options = def_options
    self.banner_header = self.class::COMMAND_NAME
  end

  def self.commands *cmds
    acommands = []
    cmds.each do |cmd|
      if cmd.is_a?(Hash)
        key = cmd.keys[0]
        acommands.push key.to_sym
        cmd[key].each do |subcmd|
          create_command key.to_s, subcmd.to_s
        end

        define_method "#{key}_subcommands" do
          cmd[key]
        end

        invalid_command_method = "invalid_#{key}_command"
        banner_method = "#{key}_banner"

        define_method invalid_command_method do
          puts "#{self.header}:\n#{self.send(banner_method)}"
        end

        define_method banner_method do
          cmd[key].map{|sc| self.send("#{key}_#{sc}_banner")}.join("") + "\n"
        end
      else
        acommands.push cmd.to_sym
        create_command cmd.to_s

        define_method "#{cmd}_subcommands" do
          []
        end
      end
    end

    define_method :available_commands do
      acommands
    end

    define_method "banners" do
      r = []
      cmds.each do |cmd|
        if cmd.is_a?(Hash)
          key = cmd.keys[0]
          cmd[key].each do |subcmd|
            r.push self.send("#{key}_#{subcmd}_banner")
          end
        else
          r.push self.send("#{cmd.to_s}_banner")
        end
      end
      r
    end

  end

  def self.create_command cmd, subcmd=nil
    name = (subcmd.nil? ? cmd : "#{cmd}_#{subcmd}")
    banner = (subcmd.nil? ? cmd : "#{cmd} #{subcmd}")

    invalid_command_method = "invalid_#{name}_command"
    banner_method = "#{name}_banner"

    define_method invalid_command_method do
      puts "#{self.header}:\n#{self.send(banner_method)}"
    end

    params_method = "#{name}_params"
    define_method banner_method do
      self.banner_header + " #{banner} #{(self.send(params_method) || []).join(" ")}\n"
    end

    options_method = "#{name}_options"
    define_method options_method do
      self.options do |opts, options|
        opts.banner << self.send(banner_method)
      end
    end

    attr_accessor params_method

  end

  # You could use block to extend default functionality:
  #   self.options do |parser, options|
  #     parser.banner << 'banner'
  #     options[:any_option] = {a: 123}
  #   end
  def options(&block)
    parser = Options::Helpers::DevopsOptionsParser.new(default_options, &block)
    parser.parse!(self.args)
    parser.parsed_options
  end

  def invalid_command
    options do |opts, options|
      opts.banner << self.error_banner
      puts opts.banner
      exit(2)
    end
  end

  # returns [command, options, args] because they are always needed together
  # will exit if operation is unsupported
  def parse_options_for!
    if !ARGV[1] || ARGV[1].start_with?("-")
      invalid_command
    end
    command = ARGV[1].to_sym
    # available_commands method is defined dinamically in .commands method
    if available_commands.include?(command)
      subcommands = send("#{command}_subcommands")
      n = 2
      unless subcommands.empty?
        n = 3
        return invalid_command if !ARGV[2] || !subcommands.include?(ARGV[2].to_sym)
        command = "#{command}_#{ARGV[2]}".to_sym
      end

      options = send("#{command}_options")
      [command, options, args[n..-1]]
    else
      invalid_command
    end
  end

  def error_banner
    "\t#{self.header}:\n\t#{self.banners.join("\t")}\n"
  end

  def banner_header
    "\t" + @banner_header
  end

end
