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


require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helpers'))


describe Extension do

  before do
    @saved_modules   = Project.class_eval { @extension_modules }.dup
    @saved_callbacks = Project.class_eval { @global_callbacks }.dup
  end

  after do
    modules   = @saved_modules
    callbacks = @saved_callbacks
    Project.class_eval do
      @global_callbacks  = callbacks
      @extension_modules = modules
    end
  end

  it 'should call Extension.first_time during include' do
    TestExtension.should_receive(:first_time_called).once
    class Buildr::Project
      include TestExtension
    end
  end

  it 'should call before_define and after_define in order when project is defined' do
    begin
      TestExtension.callback do |extension|
        extension.should_receive(:before_define_called).once.ordered
        extension.should_receive(:after_define_called).once.ordered
      end
      class Buildr::Project
        include TestExtension
      end
      define('foo')
    ensure
      TestExtension.callback { |ignore| }
    end
  end

  it 'should call before_define and after_define for each project defined' do
    begin
      extensions = 0
      TestExtension.callback do |extension|
        extensions += 1
        extension.should_receive(:before_define_called).once.ordered
        extension.should_receive(:after_define_called).once.ordered
      end
      class Buildr::Project
        include TestExtension
      end
      define('foo')
      define('bar')
      extensions.should equal(2)
    ensure
      TestExtension.callback { |ignore| }
    end
  end

  it 'should call before_define callbacks in dependency order' do
    class Buildr::Project
      include ExtensionOneTwo
      include ExtensionThreeFour
    end
    define('foo')
    project('foo').before_order.should == [ :one, :two, :three, :four ]
    project('foo').after_order.should  == [ :four, :three, :two, :one ]
  end

  it 'should call before_define callbacks when extending project' do
    define('foo') do
      extend ExtensionOneTwo
      extend ExtensionThreeFour
    end
    project('foo').before_order.should == [ :two, :one, :four, :three ]
    project('foo').after_order.should  == [ :four, :three, :two, :one ]
  end

  it 'should raise error when including if callback dependencies cannot be satisfied' do
    class Buildr::Project
      include ExtensionOneTwo # missing ExtensionThreeFour
    end
    lambda { define('foo') }.should raise_error
  end

  it 'should raise error when extending if callback dependencies cannot be satisfied' do
    lambda {
      define('foo') do |project|
        extend ExtensionOneTwo # missing ExtensionThreeFour
      end
    }.should raise_error
  end

  it 'should ignore dependencies when extending project' do
    define('bar') do |project|
      extend ExtensionThreeFour # missing ExtensionOneTwo
    end
    project('bar').before_order.should == [:four, :three]
    project('bar').after_order.should == [:four, :three]
  end
end

module TestExtension
  include Extension

  def initialize(*args)
    super
    # callback is used to obtain extension instance created by buildr
    @@callback.call(self) if @@callback
  end

  def self.callback(&block)
    @@callback = block
  end

  first_time do
    self.first_time_called()
  end

  before_define do |project|
    project.before_define_called()
  end

  after_define do |project|
    project.after_define_called()
  end

  def self.first_time_called()
  end

end

module BeforeAfter
  def before_order
    @before_order ||= []
  end

  def after_order
    @after_order ||= []
  end
end

module ExtensionOneTwo
  include Extension, BeforeAfter

  before_define(:two => :one) do |project|
    project.before_order << :two
  end

  before_define(:one) do |project|
    project.before_order << :one
  end

  after_define(:one => :two) do |project|
    project.after_order << :one
  end

  after_define(:two => :three) do |project|
    project.after_order << :two
  end
end

module ExtensionThreeFour
  include Extension, BeforeAfter

  before_define(:three => :two)

  before_define(:four => :three) do |project|
    project.before_order << :four
  end

  before_define(:three) do |project|
    project.before_order << :three
  end

  after_define(:three => :four) do |project|
    project.after_order << :three
  end

  after_define(:four) do |project|
    project.after_order << :four
  end
end
