# encoding: UTF-8
module MongoMapper
  module Plugins
    module EmbeddedCallbacks
      extend ActiveSupport::Concern

      included do
        extend  ::ActiveModel::Callbacks

        define_model_callbacks :save, :create, :update, :destroy, :only => [:before, :after]
        define_model_callbacks :initialize, :find, :touch, :only => [:after]

        proxy_callbacks(
          :before => [:save, :create, :update, :destroy],
          :after  => [:save, :create, :update, :destroy, :touch]
        )

        @embedded_callbacks_status = nil
      end

      module ClassMethods
        def define_callbacks(*args)
          embedded_callbacks_on if @embedded_callbacks_status.nil?
          super
        end

        def embedded_callbacks_on
          @embedded_callbacks_status = true
        end

        def embedded_callbacks_off
          @embedded_callbacks_status = false
        end

        def embedded_callbacks_on?
          !!@embedded_callbacks_status
        end

        def embedded_callbacks_off?
          !@embedded_callbacks_status
        end

        def proxy_callbacks(definition)
          definition.each do |prefix, suffixes|
            suffixes.each do |suffix|
              callback = "%s_%s" % [prefix, suffix]

              class_eval <<-CALLBACK, __FILE__, __LINE__ + 1
                class << self
                  alias_method :__original_#{callback}, :#{callback}

                  def #{callback}(*args, **options, &block)
                    embedded_callbacks_on if @embedded_callbacks_status.nil?
                    __original_#{callback}(*args, **options, &block)
                  end
                end
              CALLBACK
            end
          end
        end
      end

      def run_callbacks(callback, *args, &block)
        if self.class.embedded_callbacks_on? and embedded_associations.length > 0
          embedded_docs = embedded_associations.map do |association|
            Array(get_proxy(association).send(:load_target))
          end.flatten(1)

          block = embedded_docs.inject(block) do |chain, doc|
            if doc.class.respond_to?("_#{callback}_callbacks")
              lambda { doc.run_callbacks(callback, *args, &chain) }
            else
              chain
            end
          end
        end

        super callback, *args, &block
      end
    end
  end
end
