require 'puppet/face'

# Implements the 'puppet strings' interface.
Puppet::Face.define(:strings, '0.0.1') do
  summary 'Generate Puppet documentation with YARD.'

  action(:generate) do
    default

    option '--format OUTPUT_FORMAT' do
      summary 'Designate output format, JSON or markdown.'
    end
    option '--out PATH' do
      summary 'Write selected format to PATH. If no path is designated, strings prints to STDOUT.'
    end
    option '--markup FORMAT' do
      summary "The markup format to use for docstring text (defaults to 'markdown')."
    end
    option '--emit-json-stdout' do
      summary 'DEPRECATED: Print JSON representation of the documentation to stdout.'
    end
    option '--emit-json PATH' do
      summary 'DEPRECATED: Write JSON representation of the documentation to the given file.'
    end

    summary 'Generate documentation from files.'
    arguments '[[search_pattern] ...]'

    when_invoked do |*args|
      check_required_features
      require 'puppet-strings'

      PuppetStrings::generate(
        args.count > 1 ? args[0..-2] : PuppetStrings::DEFAULT_SEARCH_PATTERNS,
        build_generate_options(args.last)
      )
      nil
    end
  end

  action(:server) do
    option '--markup FORMAT' do
      summary "The markup format to use for docstring text (defaults to 'markdown')."
    end

    summary 'Runs a local documentation server for the modules in the current Puppet environment.'
    arguments '[[module_name] ...]'

    when_invoked do |*args|
      check_required_features
      require 'puppet-strings'

      modules = args.count > 1 ? args[0..-2] : []

      # Generate documentation for all (or the given) modules
      module_docs = []
      environment = Puppet.lookup(:current_environment)
      environment.modules.each do |mod|
        next unless modules.empty? || modules.include?(mod.name)
        db = File.join(mod.path, '.yardoc')
        patterns = PuppetStrings::DEFAULT_SEARCH_PATTERNS.map do |p|
          File.join(mod.path, p)
        end
        puts "Generating documentation for Puppet module '#{mod.name}'."
        PuppetStrings.generate(patterns, build_generate_options(args.last, '--db', db))

        # Clear the registry so that the next call to generate has a clean database
        YARD::Registry.clear

        module_docs << mod.name
        module_docs << db
      end

      if module_docs.empty?
        puts 'No Puppet modules were found to serve documentation for.'
        return
      end
      puts 'Starting YARD documentation server.'
      PuppetStrings::run_server('-m', *module_docs)
      nil
    end
  end

  # Checks that the required features are installed.
  # @return [void]
  def check_required_features
    raise RuntimeError, "The 'yard' gem must be installed in order to use this face." unless Puppet.features.yard?
    raise RuntimeError, "The 'rgen' gem must be installed in order to use this face." unless Puppet.features.rgen?
    raise RuntimeError, 'This face requires Ruby 1.9 or greater.' if RUBY_VERSION =~ /^1\.8/
  end

  # Builds the options to PuppetStrings.generate.
  # @param [Hash] options The Puppet face options hash.
  # @param [Array] yard_args The additional arguments to pass to YARD.
  # @return [Hash] Returns the PuppetStrings.generate options hash.
  def build_generate_options(options = nil, *yard_args)
    generate_options = {}
    generate_options[:debug] = Puppet[:debug]
    generate_options[:backtrace] = Puppet[:trace]
    generate_options[:yard_args] = yard_args unless yard_args.empty?

    if options
      if options[:emit_json]
        $stderr.puts "WARNING: '--emit-json PATH' is deprecated. Use '--format json --out PATH' instead."
      end
      if options[:emit_json_stdout]
        $stderr.puts "WARNING: '--emit-json-stdout' is deprecated. Use '--format json' instead."
      end
      markup = options[:markup]
      generate_options[:markup] = markup if markup
      generate_options[:path] = options[:out] if options[:out]
      generate_options[:stdout] = options[:stdout]
      format = options[:format]
      if format
        if format.casecmp('markdown').zero?
          generate_options[:markdown] = true
        elsif format.casecmp('json').zero? || options[:emit_json] || options[:emit_json_stdout]
          generate_options[:json] = true
          generate_options[:path] ||= options[:emit_json] if options[:emit_json]
        else
          raise RuntimeError, "Invalid format #{options[:format]}. Please select 'json' or 'markdown'."
        end
      elsif options[:emit_json] || options[:emit_json_stdout]
        generate_options[:json] = true
        generate_options[:path] ||= options[:emit_json] if options[:emit_json]
      end
    end
    generate_options
  end
end
