# 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:

  # Symbolic mapping for directory layout.  Used for both the default and custom layouts.
  #
  # For example, the default layout maps [:source, :main, :java] to 'src/main/java', and
  # [:target, :main, :classes] to 'target/classes'.  You can use this to change the layout
  # of your projects.
  #
  # To map [:source, :main] into the 'sources' directory:
  #   my_layout = Layout.new
  #   my_layout[:source, :main] = 'sources'
  #
  #   define 'foo', :layout=>my_layout do
  #     ...
  #   end
  #
  # To map [:source, :main, :java] to 'java/main':
  #   class MainLast < Layout
  #     def expand(*args)
  #       if args[0..1] == [:source, :main]
  #         super args[2], :main, *args[3,]
  #       else
  #         super
  #       end
  #     end
  #   end
  #
  #   define 'foo', :layout=>MainLast do
  #     ...
  #   end
  class Layout

    class << self

      # Default layout used by new projects.
      attr_accessor :default

    end

    def initialize #:nodoc:
      @mapping = {}
    end

    # Expands list of symbols and path names into a full path, for example:
    #   puts default.expand(:source, :main, :java)
    #   => "src/main/java"
    def expand(*args)
      args = args.compact.reject { |s| s.to_s.empty? }.map(&:to_sym)
      return '' if args.empty?
      @mapping[args] ||= File.join(*[expand(*args[0..-2]), args.last.to_s].reject(&:empty?)) if args.size > 1
      return @mapping[args] || args.first.to_s
    end

    # Resolves a list of symbols into a path.
    def [](*args)
      @mapping[args.map(&:to_sym)]
    end

    # Specifies the path resolved from a list of symbols.
    def []=(*args)
      @mapping[args[0...-1].map(&:to_sym)] = args.last
    end

    def initialize_copy(copy)
      copy.instance_variable_set :@mapping, @mapping.clone
    end

    # Default layout has the following properties:
    # * :source maps to the 'src' directory.
    # * Anything under :source maps verbatim (e.g. :source, :main becomes 'src/main')
    # * :target maps to the 'target' directory.
    # * :target, :main maps to the 'target' directory as well.
    # * Anything under :target, :main maps verbatim (e.g. :target, :main, :classes becomes 'target/classes')
    # * Anything else under :target also maps verbatim (e.g. :target, :test becomes 'target/test')
    class Default < Layout

      def initialize
        super
        self[:source] = 'src'
        self[:target, :main] = 'target'
      end

    end

    self.default = Default.new

  end


  # A project definition is where you define all the tasks associated with
  # the project you're building.
  #
  # The project itself will define several life cycle tasks for you. For example,
  # it automatically creates a compile task that will compile all the source files
  # found in src/main/java into target/classes, a test task that will compile source
  # files from src/test/java and run all the JUnit tests found there, and a build
  # task to compile and then run the tests.
  #
  # You use the project definition to enhance these tasks, for example, telling the
  # compile task which class path dependencies to use. Or telling the project how
  # to package an artifact, e.g. creating a JAR using <tt>package :jar</tt>.
  #
  # You can also define additional tasks that are executed by project tasks,
  # or invoked from rake.
  #
  # Tasks created by the project are all prefixed with the project name, e.g.
  # the project foo creates the task foo:compile. If foo contains a sub-project bar,
  # the later will define the task foo:bar:compile. Since the compile task is
  # recursive, compiling foo will also compile foo:bar.
  #
  # If you run:
  #   buildr compile
  # from the command line, it will execute the compile task of the current project.
  #
  # Projects and sub-projects follow a directory hierarchy. The Buildfile is assumed to
  # reside in the same directory as the top-level project, and each sub-project is
  # contained in a sub-directory in the same name. For example:
  #   /home/foo
  #   |__ Buildfile
  #   |__ src/main/java
  #   |__ foo
  #       |__ src/main/java
  #
  # The default structure of each project is assumed to be:
  #   src
  #   |__main
  #   |  |__java           <-- Source files to compile
  #   |  |__resources      <-- Resources to copy
  #   |  |__webapp         <-- For WARs
  #   |__test
  #   |  |__java           <-- Source files to compile (tests)
  #   |  |__resources      <-- Resources to copy (tests)
  #   |__target            <-- Packages created here
  #   |  |__classes        <-- Generated when compiling
  #   |  |__resources      <-- Copied (and filtered) from resources
  #   |  |__test/classes   <-- Generated when compiling tests
  #   |  |__test/resources <-- Copied (and filtered) from resources
  #   |__reports           <-- Test, coverage and other reports
  #
  # You can change the project layout by passing a new Layout to the project definition.
  #
  # You can only define a project once using #define. Afterwards, you can obtain the project
  # definition using #project. The order in which you define projects is not important,
  # project definitions are evaluated when you ask for them. Circular dependencies will not
  # work. Rake tasks are only created after the project is evaluated, so if you need to access
  # a task (e.g. compile) use <code>project('foo').compile</code> instead of <code>task('foo:compile')</code>.
  #
  # For example:
  #   define 'myapp', :version=>'1.1' do
  #
  #     define 'wepapp' do
  #       compile.with project('myapp:beans')
  #       package :war
  #     end
  #
  #     define 'beans' do
  #       compile.with DEPENDS
  #       package :jar
  #     end
  #   end
  #
  #   puts projects.map(&:name)
  #   => [ 'myapp', 'myapp:beans', 'myapp:webapp' ]
  #   puts project('myapp:webapp').parent.name
  #   => 'myapp'
  #   puts project('myapp:webapp').compile.classpath.map(&:to_spec)
  #   => 'myapp:myapp-beans:jar:1.1'
  class Project < Rake::Task

    class << self

      # :call-seq:
      #   define(name, properties?) { |project| ... } => project
      #
      # See Buildr#define.
      def define(name, properties, &block) #:nodoc:
        # Make sure a sub-project is only defined within the parent project,
        # to prevent silly mistakes that lead to inconsistencies (e.g.
        # namespaces will be all out of whack).
        Buildr.application.current_scope == name.split(':')[0...-1] or
          raise "You can only define a sub project (#{name}) within the definition of its parent project"

        @projects ||= {}
        raise "You cannot define the same project (#{name}) more than once" if @projects[name]
        # Projects with names like: compile, test, build are invalid, so we have
        # to make sure the project has not the name of an already defined task
        raise "Invalid project name: #{name.inspect} is already used for a task" if Buildr.application.lookup(name)

        Project.define_task(name).tap do |project|
          # Define the project to prevent duplicate definition.
          @projects[name] = project
          # Set the project properties first, actions may use them.
          properties.each { |name, value| project.send "#{name}=", value } if properties
          # Setup to call before/after define extension callbacks
          # Don't cache list of extensions, since project may add new extensions.
          project.enhance do |project|
            project.send :call_callbacks, :before_define
            project.enhance do |project|
              project.send :call_callbacks, :after_define
            end
          end
          # Enhance the project using the definition block.
          project.enhance { project.instance_exec project, &block } if block

          # Mark the project as defined
          project.enhance do |project|
            project.send :define!
          end

          # Top-level project? Invoke the project definition. Sub-project? We don't invoke
          # the project definition yet (allow project calls to establish order of evaluation),
          # but must do so before the parent project's definition is done.
          project.parent.enhance { project.invoke } if project.parent
        end
      end

      # :call-seq:
      #   project(name) => project
      #
      # See Buildr#project.
      def project(*args, &block) #:nodoc:
        options = args.pop if Hash === args.last
        return define(args.first, options, &block) if block
        rake_check_options options, :scope, :no_invoke if options
        no_invoke = options && options[:no_invoke]

        raise ArgumentError, 'Only one project name at a time' unless args.size == 1
        @projects ||= {}
        name = args.first.to_s
        # Make sure parent project is evaluated (e.g. if looking for foo:bar, find foo first)
        unless @projects[name]
          parts = name.split(':')
          project(parts.first, options || {}) if parts.size > 1
        end
        if options && options[:scope]
          # We assume parent project is evaluated.
          project = options[:scope].split(':').inject([[]]) { |scopes, scope| scopes << (scopes.last + [scope]) }.
            map { |scope| @projects[(scope + [name]).join(':')] }.
            select { |project| project }.last
        end
        project ||= @projects[name] # Not found in scope.
        raise "No such project #{name}" unless project
        project.invoke unless  project.defined? || no_invoke || Buildr.application.current_scope.join(":").to_s == project.name.to_s
        project
      end

      # :call-seq:
      #   projects(*names) => projects
      #
      # See Buildr#projects.
      def projects(*names) #:nodoc:
        options = names.pop if Hash === names.last
        rake_check_options options, :scope, :no_invoke if options

        no_invoke = options && options[:no_invoke]

        @projects ||= {}
        names = names.flatten
        if options && options[:scope]
          # We assume parent project is evaluated.
          if names.empty?
            parent = @projects[options[:scope].to_s] or raise "No such project #{options[:scope]}"
            @projects.values.select { |project| project.parent == parent }.each { |project| project.invoke unless no_invoke }.
              map { |project| [project] + projects(:scope => project, :no_invoke => no_invoke) }.flatten.sort_by(&:name)
          else
            names.uniq.map { |name| project(name, :scope => options[:scope], :no_invoke => no_invoke) }
          end
        elsif names.empty?
          # Parent project(s) not evaluated so we don't know all the projects yet.
          @projects.values.each { |project| project.invoke unless no_invoke }
          @projects.keys.map { |name| project(name, :no_invoke => no_invoke) or raise "No such project #{name}" }.sort_by(&:name)
        else
          # Parent project(s) not evaluated, for the sub-projects we may need to find.
          names.map { |name| name.split(':') }.select { |name| name.size > 1 }.map(&:first).uniq.each { |name| project(name, :no_invoke => no_invoke) }
          names.uniq.map { |name| project(name, :no_invoke => no_invoke) or raise "No such project #{name}" }.sort_by(&:name)
        end
      end

      # :call-seq:
      #   clear
      #
      # Discard all project definitions.
      def clear
        @projects.clear if @projects
      end

      # :call-seq:
      #   local_task(name)
      #   local_task(name) { |name| ... }
      #
      # Defines a local task with an optional execution message.
      #
      # A local task is a task that executes a task with the same name, defined in the
      # current project, the project's with a base directory that is the same as the
      # current directory.
      #
      # Complicated? Try this:
      #   buildr build
      # is the same as:
      #   buildr foo:build
      # But:
      #   cd bar
      #   buildr build
      # is the same as:
      #   buildr foo:bar:build
      #
      # The optional block is called with the project name when the task executes
      # and returns a message that, for example "Building project #{name}".
      def local_task(*args, &block)
        task *args do |task, args|
          args = task.arg_names.map {|n| args[n]}
          local_projects do |project|
            info block.call(project.name) if block
            task("#{project.name}:#{task.name}").invoke *args
          end
        end
      end

      # *Deprecated* Check the Extension module to see how extensions are handled.
      def on_define(&block)
        Buildr.application.deprecated 'This method is deprecated, see Extension'
        (@on_define ||= []) << block if block
      end

      def scope_name(scope, task_name) #:nodoc:
        task_name
      end

      def local_projects(dir = nil, &block) #:nodoc:
        dir = File.expand_path(dir || Buildr.application.original_dir)
        projects = @projects ? @projects.values : []
        projects = projects.select { |project| project.base_dir == dir }
        if projects.empty? && dir != Dir.pwd && File.dirname(dir) != dir
          local_projects(File.dirname(dir), &block)
        elsif block
          if projects.empty?
            warn "No projects defined for directory #{Buildr.application.original_dir}"
          else
            projects.each { |project| block[project] }
          end
        else
          projects
        end
      end

      # :call-seq:
      #   parent_task(task_name) => task_name or nil
      #
      # Returns a parent task, basically a task in a higher namespace.  For example, the parent
      # of 'foo:test:compile' is 'foo:compile' and the parent of 'foo:compile' is 'compile'.
      def parent_task(task_name) #:nodoc:
        namespace = task_name.split(':')
        last_name = namespace.pop
        namespace.pop
        Buildr.application.lookup((namespace + [last_name]).join(':'), []) unless namespace.empty?
      end

      # :call-seq:
      #   project_from_task(task) => project
      #
      # Figure out project associated to this task and return it.
      def project_from_task(task) #:nodoc:
        project = Buildr.application.lookup('rake:' + task.to_s.gsub(/:[^:]*$/, ''))
        project if Project === project
      end

      # Loaded extension modules.
      def extension_modules #:nodoc:
        @extension_modules ||= []
      end

      # Extension callbacks that apply to all projects
      def global_callbacks #:nodoc:
        @global_callbacks ||= []
      end
    end


    # Project has visibility to everything in the Buildr namespace.
    include Buildr

    # The project name. For example, 'foo' for the top-level project, and 'foo:bar'
    # for its sub-project.
    attr_reader :name

    # The parent project if this is a sub-project.
    attr_reader :parent

    def initialize(*args) #:nodoc:
      super
      split = name.split(':')
      if split.size > 1
        # Get parent project, but do not invoke it's definition to prevent circular
        # dependencies (it's being invoked right now, so calling project will fail).
        @parent = task(split[0...-1].join(':'))
        raise "No parent project #{split[0...-1].join(':')}" unless @parent && Project === parent
      end
      # Inherit all global callbacks
      @callbacks = Project.global_callbacks.dup
    end

    #
    # Returns the root project for this project.
    #
    # If this project is a subproject it will find the top
    # level project and return it, else it will return itself.
    #
    def root_project
      p = project
      while p.parent
        p = p.parent
      end
      p
    end

    # :call-seq:
    #   base_dir => path
    #
    # Returns the project's base directory.
    #
    # The Buildfile defines top-level project, so it's logical that the top-level project's
    # base directory is the one in which we find the Buildfile. And each sub-project has
    # a base directory that is one level down, with the same name as the sub-project.
    #
    # For example:
    #   /home/foo/          <-- base_directory of project 'foo'
    #   /home/foo/Buildfile <-- builds 'foo'
    #   /home/foo/bar       <-- sub-project 'foo:bar'
    def base_dir
      if @base_dir.nil?
        if parent
          # For sub-project, a good default is a directory in the parent's base_dir,
          # using the same name as the project.
          @base_dir = File.expand_path(name.split(':').last, parent.base_dir)
        else
          # For top-level project, a good default is the directory where we found the Buildfile.
          @base_dir = Dir.pwd
        end
      end
      @base_dir
    end

    # Returns the layout associated with this project.
    def layout
      @layout ||= (parent ? parent.layout : Layout.default).clone
    end

    # :call-seq:
    #   path_to(*names) => path
    #
    # Returns a path from a combination of name, relative to the project's base directory.
    # Essentially, joins all the supplied names and expands the path relative to #base_dir.
    # Symbol arguments are converted to paths based on the layout, so whenever possible stick
    # to these.  For example:
    #   path_to(:source, :main, :java)
    #   => 'src/main/java'
    #
    # Keep in mind that all tasks are defined and executed relative to the Buildfile directory,
    # so you want to use #path_to to get the actual path within the project as a matter of practice.
    #
    # For example:
    #   path_to('foo', 'bar')
    #   => foo/bar
    #   path_to('/tmp')
    #   => /tmp
    #   path_to(:base_dir, 'foo') # same as path_to('foo")
    #   => /home/project1/foo
    def path_to(*names)
      File.expand_path(layout.expand(*names), base_dir)
    end
    alias :_ :path_to

    # :call-seq:
    #   file(path) => Task
    #   file(path=>prereqs) => Task
    #   file(path) { |task| ... } => Task
    #
    # Creates and returns a new file task in the project. Similar to calling Rake's
    # file method, but the path is expanded relative to the project's base directory,
    # and the task executes in the project's base directory.
    #
    # For example:
    #   define 'foo' do
    #     define 'bar' do
    #       file('src') { ... }
    #     end
    #   end
    #
    #   puts project('foo:bar').file('src').to_s
    #   => '/home/foo/bar/src'
    def file(*args, &block)
      task_name, arg_names, deps = Buildr.application.resolve_args(args)
      task = Rake::FileTask.define_task(path_to(task_name))
      task.set_arg_names(arg_names) unless arg_names.empty?
      task.enhance Array(deps), &block
    end

    # :call-seq:
    #   task(name) => Task
    #   task(name=>prereqs) => Task
    #   task(name) { |task| ... } => Task
    #
    # Creates and returns a new task in the project. Similar to calling Rake's task
    # method, but prefixes the task name with the project name and executes the task
    # in the project's base directory.
    #
    # For example:
    #   define 'foo' do
    #     task 'doda'
    #   end
    #
    #   puts project('foo').task('doda').name
    #   => 'foo:doda'
    #
    # When called from within the project definition, creates a new task if the task
    # does not already exist. If called from outside the project definition, returns
    # the named task and raises an exception if the task is not defined.
    #
    # As with Rake's task method, calling this method enhances the task with the
    # prerequisites and optional block.
    def task(*args, &block)
      task_name, arg_names, deps = Buildr.application.resolve_args(args)
      if task_name =~ /^:/
        task = Buildr.application.switch_to_namespace [] do
          Rake::Task.define_task(task_name[1..-1])
        end
      elsif Buildr.application.current_scope == name.split(':')
        task = Rake::Task.define_task(task_name)
      else
        unless task = Buildr.application.lookup(task_name, name.split(':'))
          raise "You cannot define a project task outside the project definition, and no task #{name}:#{task_name} defined in the project"
        end
      end
      task.set_arg_names(arg_names) unless arg_names.empty?
      task.enhance Array(deps), &block
    end

    # :call-seq:
    #   recursive_task(name=>prereqs) { |task| ... }
    #
    # Define a recursive task. A recursive task executes itself and the same task
    # in all the sub-projects.
    def recursive_task(*args, &block)
      task_name, arg_names, deps = Buildr.application.resolve_args(args)
      task = Buildr.options.parallel ? multitask(task_name) : task(task_name)
      parent.task(task_name).enhance [task] if parent
      task.set_arg_names(arg_names) unless arg_names.empty?
      task.enhance Array(deps), &block
    end

    # :call-seq:
    #   project(name) => project
    #   project => self
    #
    # Same as Buildr#project. This method is called on a project, so a relative name is
    # sufficient to find a sub-project.
    #
    # When called on a project without a name, returns the project itself. You can use that when
    # setting project properties, for example:
    #   define 'foo' do
    #     project.version = '1.0'
    #   end
    def project(*args, &block)
      if Hash === args.last
        options = args.pop
      else
        options = {}
      end
      if args.empty?
        self
      else
        Project.project *(args + [{ :scope=>self.name }.merge(options)]), &block
      end
    end

    # :call-seq:
    #   projects(*names) => projects
    #
    # Same as Buildr#projects. This method is called on a project, so relative names are
    # sufficient to find sub-projects.
    def projects(*args)
      if Hash === args.last
        options = args.pop
      else
        options = {}
      end
      Project.projects *(args + [{ :scope=>self.name }.merge(options)])
    end

    def inspect #:nodoc:
      %Q{project(#{name.inspect})}
    end

    def callbacks #:nodoc:
      # global + project_local callbacks for this project
      @callbacks ||= []
    end

    def calledback #:nodoc:
      # project-local callbacks that have been called
      @calledback ||= {}
    end

    def defined?
      @defined
    end

  protected
    def define!
      @defined = true
    end


    # :call-seq:
    #   base_dir = dir
    #
    # Sets the project's base directory. Allows you to specify a base directory by calling
    # this accessor, or with the :base_dir property when calling #define.
    #
    # You can only set the base directory once for a given project, and only before accessing
    # the base directory (for example, by calling #file or #path_to).
    # Set the base directory. Note: you can only do this once for a project,
    # and only before accessing the base directory. If you try reading the
    # value with #base_dir, the base directory cannot be set again.
    def base_dir=(dir)
      raise 'Cannot set base directory twice, or after reading its value' if @base_dir
      @base_dir = File.expand_path(dir)
    end

    # Sets the project layout.  Accepts Layout object or class (or for that matter, anything
    # that can expand).
    def layout=(layout)
      raise 'Cannot set directory layout twice, or after reading its value' if @layout
      @layout = layout.is_a?(Class) ? layout.new : layout
    end

    # :call-seq:
    #   define(name, properties?) { |project| ... } => project
    #
    # Define a new sub-project within this project. See Buildr#define.
    def define(name, properties = nil, &block)
      Project.define "#{self.name}:#{name}", properties, &block
    end

    def execute(args) #:nodoc:
      Buildr.application.switch_to_namespace name.split(':') do
        super
      end
    end

    # Call all extension callbacks for a particular phase, e.g. :before_define, :after_define.
    def call_callbacks(phase) #:nodoc:
      remaining = @callbacks.select { |cb| cb.phase == phase }
      known_callbacks = remaining.map { |cb| cb.name }

      # call each extension in order
      until remaining.empty?
        callback = first_satisfied(remaining, known_callbacks)
        if callback.nil?
          hash = remaining.map { |cb| { cb.name => cb.dependencies} }
          fail "Unsatisfied dependencies in extensions for #{phase}: #{hash.inspect}"
        end
        callback.blocks.each { |b| b.call(self) }
      end
    end

    private

    # find first callback with satisfied dependencies
    def first_satisfied(r, known_callbacks)
      remaining_names = r.map { |cb| cb.name }
      res = r.find do |cb|
        cb.dependencies.each do |dep|
          fail "Unknown #{phase.inspect} extension dependency: #{dep.inspect}" unless known_callbacks.index(dep)
        end
        satisfied = cb.dependencies.find { |dep| remaining_names.index(dep) } == nil
        cb if satisfied
      end
      r.delete res
    end

  end


  # The basic mechanism for extending projects in Buildr are Ruby modules.  In fact,
  # base features like compiling and testing are all developed in the form of modules,
  # and then added to the core Project class.
  #
  # A module defines instance methods that are then mixed into the project and become
  # instance methods of the project.  There are two general ways for extending projects.
  # You can extend all projects by including the module in Project:
  #    class Project
  #      include MyExtension
  #    end
  # You can also extend a given project instance and only that instance by extending
  # it with the module:
  #   define 'foo' do
  #     extend MyExtension
  #   end
  #
  # Some extensions require tighter integration with the project, specifically for
  # setting up tasks and properties, or for configuring tasks based on the project
  # definition.  You can do that by adding callbacks to the process.
  #
  # The easiest way to add callbacks is by incorporating the Extension module in your
  # own extension, and using the various class methods to define callback behavior:
  # * first_time -- This block will be called once for any particular extension.
  #     You can use this to setup top-level and local tasks.
  # * before_define -- This block is called once for the project with the project
  #     instance, right before running the project definition.  You can use this
  #     to add tasks and set properties that will be used in the project definition.
  # * after_define -- This block is called once for the project with the project
  #     instance, right after running the project definition.  You can use this to
  #     do any post-processing that depends on the project definition.
  #
  # This example illustrates how to write a simple extension:
  #   module LinesOfCode
  #     include Extension
  #
  #     first_time do
  #       # Define task not specific to any projet.
  #       desc 'Count lines of code in current project'
  #       Project.local_task('loc')
  #     end
  #
  #     before_define do |project|
  #       # Define the loc task for this particular project.
  #       Rake::Task.define_task 'loc' do |task|
  #         lines = task.prerequisites.map { |path| Dir['#{path}/**/*'] }.flatten.uniq.
  #           inject(0) { |total, file| total + File.readlines(file).count }
  #         puts "Project #{project.name} has #{lines} lines of code"
  #       end
  #     end
  #
  #     after_define do |project|
  #       # Now that we know all the source directories, add them.
  #       task('loc'=>compile.sources + compile.test.sources)
  #     end
  #
  #     # To use this method in your project:
  #     #   loc path_1, path_2
  #     def loc(*paths)
  #       task('loc'=>paths)
  #     end
  #
  #   end
  #
  #   class Buildr::Project
  #     include LinesOfCode
  #   end
  module Extension

    # Extension callback details
    class Callback #:nodoc:
      attr_accessor :phase, :name, :dependencies, :blocks

      def initialize(phase, name, dependencies, blocks)
        @phase = phase
        @name = name
        @dependencies = dependencies
        @blocks = (blocks ? (Array === blocks ? blocks : [blocks]) : [])
      end

      def merge(callback)
        Callback.new(phase, name, @dependencies + callback.dependencies, @blocks + callback.blocks)
      end
    end

    def self.included(base) #:nodoc:
      base.extend ClassMethods
    end

    # Methods added to the extension module when including Extension.
    module ClassMethods

      def included(base) #:nodoc:
        # When included in Project, add module instance, merge callbacks and call first_time.
        if Project == base && !base.extension_modules.include?(module_callbacks)
          base.extension_modules << module_callbacks
          merge_callbacks(base.global_callbacks, module_callbacks)
          first_time = module_callbacks.select { |c| c.phase == :first_time }
          first_time.each do |c|
            c.blocks.each { |b| b.call }
          end
        end
      end

      def extended(base) #:nodoc:
        # When extending project, merge after_define callbacks and call before_define callback(s)
        # immediately
        if Project === base
          merge_callbacks(base.callbacks, module_callbacks.select { |cb| cb.phase == :after_define })
          calls = module_callbacks.select { |cb| cb.phase == :before_define }
          calls.each do |cb|
            cb.blocks.each { |b| b.call(base) } unless base.calledback[cb]
            base.calledback[cb] = cb
          end
        end
      end

      # This block will be called once for any particular extension included in Project.
      # You can use this to setup top-level and local tasks.
      def first_time(&block)
        module_callbacks << Callback.new(:first_time, self.name, [], block)
      end

      # This block is called once for the project with the project instance,
      # right before running the project definition.  You can use this to add
      # tasks and set properties that will be used in the project definition.
      #
      # The block may be named and dependencies may be declared similar to Rake
      # task dependencies:
      #
      #   before_define(:my_setup) do |project|
      #     # do stuff on project
      #   end
      #
      #   # my_setup code must run before :compile
      #   before_define(:compile => :my_setup)
      #
      def before_define(*args, &block)
        if args.empty?
          name = self.name
          deps = []
        else
          name, args, deps = Buildr.application.resolve_args(args)
        end
        module_callbacks << Callback.new(:before_define, name, deps, block)
      end

      # This block is called once for the project with the project instance,
      # right after running the project definition.  You can use this to do
      # any post-processing that depends on the project definition.
      #
      # The block may be named and dependencies may be declared similar to Rake
      # task dependencies:
      #
      #   after_define(:my_setup) do |project|
      #     # do stuff on project
      #   end
      #
      #   # my_setup code must run before :compile (but only after project is defined)
      #   after_define(:compile => :my_setup)
      #
      def after_define(*args, &block)
        if args.empty?
          name = self.name
          deps = []
        else
          name, args, deps = Buildr.application.resolve_args(args)
        end
        module_callbacks << Callback.new(:after_define, name, deps, block)
      end

    private

      def module_callbacks
        begin
          const_get('Callbacks')
        rescue
          callbacks = []
          const_set('Callbacks', callbacks)
        end
      end

      def merge_callbacks(base, merge)
        # index by phase and name
        index = base.inject({}) { |hash,cb| { [cb.phase, cb.name] => cb } }
        merge.each do |cb|
          existing = index[[cb.phase, cb.name]]
          if existing
            base[base.index(existing)] = existing.merge(cb)
          else
            base << cb
          end
          index[[cb.phase, cb.name]] = cb
        end
        base
      end
    end

  end


  # :call-seq:
  #   define(name, properties?) { |project| ... } => project
  #
  # Defines a new project.
  #
  # The first argument is the project name. Each project must have a unique name.
  # For a sub-project, the actual project name is created by prefixing the parent
  # project's name.
  #
  # The second argument is optional and contains a hash or properties that are set
  # on the project. You can only use properties that are supported by the project
  # definition, e.g. :group and :version. You can also set these properties from the
  # project definition.
  #
  # You pass a block that is executed in the context of the project definition.
  # This block is used to define the project and tasks that are part of the project.
  # Do not perform any work inside the project itself, as it will execute each time
  # the Buildfile is loaded. Instead, use it to create and extend tasks that are
  # related to the project.
  #
  # For example:
  #   define 'foo', :version=>'1.0' do
  #
  #     define 'bar' do
  #       compile.with 'org.apache.axis2:axis2:jar:1.1'
  #     end
  #   end
  #
  #   puts project('foo').version
  #   => '1.0'
  #   puts project('foo:bar').compile.classpath.map(&:to_spec)
  #   => 'org.apache.axis2:axis2:jar:1.1'
  #   % buildr build
  #   => Compiling 14 source files in foo:bar
  def define(name, properties = nil, &block) #:yields:project
    Project.define(name, properties, &block)
  end

  # :call-seq:
  #   project(name) => project
  #
  # Returns a project definition.
  #
  # When called from outside a project definition, must reference the project by its
  # full name, e.g. 'foo:bar' to access the sub-project 'bar' in 'foo'. When called
  # from inside a project, relative names are sufficient, e.g. <code>project('foo').project('bar')</code>
  # will find the sub-project 'bar' in 'foo'.
  #
  # You cannot reference a project before the project is defined. When working with
  # sub-projects, the project definition is stored by calling #define, and evaluated
  # before a call to the parent project's #define method returns.
  #
  # However, if you call #project with the name of another sub-project, its definition
  # is evaluated immediately. So the returned project definition is always complete,
  # and you can access its definition (e.g. to find files relative to the base directory,
  # or packages created by that project).
  #
  # For example:
  #   define 'myapp' do
  #     self.version = '1.1'
  #
  #     define 'webapp' do
  #       # webapp is defined first, but beans is evaluated first
  #       compile.with project('beans')
  #       package :war
  #     end
  #
  #     define 'beans' do
  #       package :jar
  #     end
  #   end
  #
  #   puts project('myapp:beans').version
  def project(*args, &block)
    Project.project *args, &block
  end

  # :call-seq:
  #   projects(*names) => projects
  #
  # With no arguments, returns a list of all projects defined so far. When called on a project,
  # returns all its sub-projects (direct descendants).
  #
  # With arguments, returns a list of named projects, fails on any name that does not exist.
  # As with #project, you can use relative names when calling this method on a project.
  #
  # Like #project, this method evaluates the definition of each project before returning it.
  # Be advised of circular dependencies.
  #
  # For example:
  #   files = projects.map { |prj| FileList[prj.path_to('src/**/*.java') }.flatten
  #   puts "There are #{files.size} source files in #{projects.size} projects"
  #
  #   puts projects('myapp:beans', 'myapp:webapp').map(&:name)
  # Same as:
  #   puts project('myapp').projects.map(&:name)
  def projects(*args)
    Project.projects *args
  end

end
