# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements.  See the NOTICE file distributed with this
# work for additional information regarding copyright ownership.  The ASF
# licenses this file to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
# License for the specific language governing permissions and limitations under
# the License.

module Buildr #:nodoc:

  # Provides the <code>emma:html</code> and <code>emma:xml</code> tasks.
  # Require explicitly using <code>require "buildr/emma"</code>.
  #
  # You can generate emma reports for a single project
  # using the project name as prefix:
  #
  #   project_name:emma:html
  #
  # You can also specify which classes to include/exclude from instrumentation by
  # passing a class name regexp to the <code>emma.include</code> or
  # <code>emma.exclude</code> methods.
  #
  #   define 'someModule' do
  #      emma.include 'some.package.*'
  #      emma.exclude 'some.foo.util.SimpleUtil'
  #   end
  module Emma

    VERSION = '2.0.5312'

    class << self

      def version
        Buildr.settings.build['emma'] || VERSION
      end

      def dependencies
        @dependencies ||= ["emma:emma_ant:jar:#{version}", "emma:emma:jar:#{version}"]
      end

      def report_to format=nil
        File.expand_path('reports/emma')
      end

      def data_file()
        File.join(report_to, 'coverage.es')
      end

      def ant

        Buildr.ant 'emma' do |ant|
          ant.taskdef :resource=>'emma_ant.properties',
            :classpath=>Buildr.artifacts(dependencies).each(&:invoke).map(&:to_s).join(File::PATH_SEPARATOR)
          ant.emma :verbosity=>(trace?(:emma) ? 'verbose' : 'warning') do
            yield ant
          end
        end
      end
    end

    class EmmaConfig # :nodoc:

      def initialize(project)
        @project = project
      end

      attr_reader :project
      private :project

      attr_writer :metadata_file, :coverage_file, :instrumented_dir, :report_dir

      def coverage_file
        @coverage_file ||= File.join(report_dir, 'coverage.ec')
      end

      def metadata_file
        @metadata_file ||= File.join(report_dir, 'coverage.em')
      end

      def instrumented_dir
        @instrumented_dir ||= project.path_to(:target, :instrumented, :classes)
      end

      def report_dir
        @report_dir ||= project.path_to(:reports, :emma)
      end

      def report_to format
        report_dir
      end

      # :call-seq:
      #   project.emma.include(*classPatterns)
      #
      def include(*classPatterns)
        includes.push(*classPatterns)
        self
      end

      def includes
        @includeClasses ||= []
      end

      # :call-seq:
      #   project.emma.exclude(*classPatterns)
      #
      def exclude(*classPatterns)
        excludes.push(*classPatterns)
        self
      end

      def excludes
        @excludeClasses ||= []
      end

      def sources
        project.compile.sources
      end
    end

    module EmmaExtension # :nodoc:
      include Buildr::Extension

      def emma
        @emma_config ||= EmmaConfig.new(self)
      end

      after_define do |project|
        emma = project.emma

        namespace 'emma' do
          unless project.compile.target.nil?
            # Instrumented bytecode goes in a different directory. This task creates before running the test
            # cases and monitors for changes in the generate bytecode.
            instrumented = project.file(emma.instrumented_dir => project.compile.target) do |task|
              unless project.compile.sources.empty?
                info "Instrumenting classes with emma metadata file #{emma.metadata_file}"
                Emma.ant do |ant|
                  ant.instr :instrpath=>project.compile.target.to_s, :destdir=>task.to_s, :metadatafile=>emma.metadata_file do
                    ant.filter :includes=>emma.includes.join(', ') unless emma.includes.empty?
                    ant.filter :excludes=>emma.excludes.join(', ') unless emma.excludes.empty?
                  end
                end
                touch task.to_s
              end
            end

            task 'instrument' => instrumented

            # We now have two target directories with bytecode.
            project.test.dependencies.unshift emma.instrumented_dir
            project.test.with Emma.dependencies
            project.test.options[:properties]["emma.coverage.out.file"] = emma.coverage_file

            [:xml, :html].each do |format|
              task format => ['instrument', 'test'] do
                missing_required_files = [emma.metadata_file, emma.coverage_file].reject { |f| File.exist?(f) }
                if missing_required_files.empty?
                  info "Creating test coverage reports in #{emma.report_dir}"
                  mkdir_p emma.report_dir
                  Emma.ant do |ant|
                    ant.report do
                      ant.infileset :file=>emma.metadata_file
                      ant.infileset :file=>emma.coverage_file
                      ant.send format, :outfile=>File.join(emma.report_to(format),"coverage.#{format}")
                      ant.sourcepath do
                        emma.sources.flatten.each do |src|
                          ant.dirset(:dir=>src.to_s) if File.exist?(src.to_s)
                        end
                      end
                    end
                  end
                else
                  info "No test coverage report for #{project}. Missing: #{missing_required_files.join(', ')}"
                end
              end
            end
          end
        end

        project.clean do
          rm_rf [emma.report_dir, emma.coverage_file, emma.metadata_file, emma.instrumented_dir]
        end

      end

    end

    class Buildr::Project
      include EmmaExtension
    end

    namespace "emma" do

      Project.local_task('instrument') { |name| "Instrumenting #{name}" }

      [:xml, :html].each do |format|
        desc "Run the test cases and produce code coverage reports in #{format}"
        task format => ['instrument', 'test'] do
          info "Creating test coverage reports in #{format}"
          mkdir_p report_to(format)
          Emma.ant do |ant|
            ant.merge :outfile=>data_file do
              Buildr.projects.each do |project|
                [project.emma.metadata_file, project.emma.coverage_file].each do |data_file|
                  ant.fileset :file=>data_file if File.exist?(data_file)
                end
              end
            end
            ant.report do
              ant.infileset :file=>data_file
              ant.send format, :outfile=>File.join(report_to(format), "coverage.#{format}")
              ant.sourcepath do
                Buildr.projects.map(&:emma).map(&:sources).flatten.map(&:to_s).each do |src|
                  ant.dirset :dir=>src if File.exist?(src)
                end
              end
            end
          end
        end
      end

      task :clean do
        rm_rf [report_to, data_file]
      end
      end

    task :clean do
      task('emma:clean').invoke if Dir.pwd == Rake.application.original_dir
    end

  end
end
