require 'escape'
require 'active_support/core_ext/array/conversions'

module Pod
  module Generator

    # Generates the documentation for a Pod with the appledoc tool.
    #
    class Documentation

      extend Executable
      executable :appledoc

      attr_reader :sandbox
      attr_reader :specification
      attr_reader :path_list

      def initialize(sandbox, specification, path_list)
        @sandbox = sandbox
        @specification = specification.root
        @path_list = path_list
      end

      DOC_SETS_PATH = "~/Library/Developer/Shared/Documentation/DocSets"

      # @return [Bool] Whether the documentation for the current Pod is already
      #         installed in the system.
      #
      def already_installed?
        index = spec_appledoc_options.index('--company-id')
        company_id = index ? spec_appledoc_options[index + 1] : docs_id
        docset_path = DOC_SETS_PATH + "/#{company_id}.#{name.gsub(/ /,'-')}.docset"
        Pathname.new(File.expand_path(docset_path)).exist?
      end

      # Generates and optionally installs the documentation for the current
      # Pod.
      #
      # @param  [Bool] install_docset
      #         Whether the documentation should also be installed in Xcode.
      #
      # @note   As the documentation is once per Pod to speed up the
      #         installation process it is generate for all the specs
      #         (including those not currently used). For this reason it is
      #         important that the documentation is generated before cleaning a
      #         Pod installation.
      #
      # @todo   Passing the files explicitly clutters output and chokes on very
      #         long list (AWSiOSSDK).  It is possible to just pass the dir of
      #         the pod, however this would include other files like demo
      #         projects.
      #
      # @return [void]
      #
      def generate(install_docset)
        if `which appledoc`.strip.empty?
          UI.warn "[!] Skipping documentation generation because appledoc can't be found.",
            actions = [], verbose_only = true
          return
        end

        target_path.mkpath
        Dir.chdir(pod_root) do
          appledoc apple_doc_command_line_arguments(install_docset)
        end

        if $?.exitstatus != 0
          UI.warn "[!] Appledoc encountered an error (exitstatus: #{$?.exitstatus}), an update might be available to solve the issue."
        end

      end

      #-----------------------------------------------------------------------#

      public

      # !@group Docset information.

      # @return [String] The name of the docset
      #
      def name
        specification.name + ' ' + specification.version.to_s
      end

      # @return [String] The company of the docset.
      #
      # @todo   Set to CocoaPods?
      #
      def company
        if specification.authors
          specification.authors.keys.sort.to_sentence
        else
          'no-company'
        end
      end

      # @return [String] The copyright of the docset.
      #
      def copyright
        company
      end

      # @return [String] The description of the docset.
      #
      def description
        specification.summary || specification.description || 'Generated by CocoaPods.'
      end

      # @return [String] The id of the docset.
      #
      def docs_id
        'org.cocoapods'
      end

      #-----------------------------------------------------------------------#

      public

      # !@group Paths.

      # @return [Array<String>] the list of the headers to process
      #         with the appledoc tool.
      #
      def public_headers
        absolute_paths = file_accessors.map(&:public_headers).flatten.uniq
        absolute_paths.map { |f| f.relative_path_from(pod_root).to_s }
      end

      # @return [String] the path of the file to use as the index of
      #         documentation relative to the root of the Pod.
      #
      def index_file
        readme_file = file_accessors.first.readme
        readme_file.relative_path_from(pod_root).to_s if readme_file
      end

      #-----------------------------------------------------------------------#

      public

      # !@group Appledoc options.

      # @return [Array<String>] The list of the appledoc options followed by
      #         their values as defined in the specification.
      #
      def spec_appledoc_options
        return [] unless specification.documentation
        specification.documentation[:appledoc] || []
      end

      # @return [Array<String>] The list of the appledoc options followed by
      #         their values.
      #
      # @note The appledoc tool terminates with an exits status of 1 if a
      #       warning was logged (see `--exit-threshold` option).
      #
      def appledoc_options
        options = [
          '--project-name', name,
          '--docset-desc', description,
          '--project-company', company,
          '--docset-copyright', copyright,
          '--company-id', docs_id,
          '--ignore', '.m',
          '--keep-undocumented-objects',
          '--keep-undocumented-members',
          '--keep-intermediate-files',
          '--exit-threshold', '2'
        ]
        options += ['--index-desc', index_file] if index_file
        options += spec_appledoc_options
      end

      # @return [String] the arguments to pass to the appledoc command line
      #         tool, properly escaped.
      #
      # @param  [Bool] install_docset
      #         Whether the documentation should also be installed in Xcode.
      #
      def apple_doc_command_line_arguments(install_docset)
        arguments = appledoc_options
        arguments += ['--output', target_path.to_s]
        arguments += install_docset ? ['--create-docset'] : ['--no-create-docset']
        arguments += public_headers
        Escape.shell_command(arguments)
      end

      #-----------------------------------------------------------------------#

      private

      # !@group Private Helpers

      def target_path
        sandbox.documentation_dir + specification.name
      end

      def pod_root
        path_list.root
      end

      def file_accessors
        return @file_accessors if @file_accessors

        @file_accessors = []
        all_specs = [specification, *specification.subspecs]
        all_specs.each do |spec|
          spec.available_platforms.each do |platform|
            accessor = Sandbox::FileAccessor.new(path_list, spec.consumer(platform))
            @file_accessors << accessor
          end
        end
        @file_accessors
      end

    end
  end
end
