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

# Hijacker class
#
# This class is used by RoleRequirementTestHelper to temporarily hijack a controller action for testing
#
# It can be used for other tests as well.
#
# You can contract the author with questions
#   Tim C. Harper - irb(main):001:0> ( 'tim_see_harperATgmail._see_om'.gsub('_see_', 'c').gsub('AT', '@') )
#
#
# Example usage:
#   hijacker = Hijacker.new(ListingsController)
#   hijacker.hijack_instance_method("index", "render :text => 'hello world!'" )
#   get :index        # will return "hello world"
#   hijacker.restore  # put things back the way you found it

class Hijacker
  def initialize(klass)
    @target_klass = klass
    @method_stores = {}
  end

  def hijack_class_method(method_name, eval_string = nil, arg_names = [], &block)
    hijack_method(class_self_instance, method_name, eval_string, arg_names, &block )
  end

  def hijack_instance_method(method_name, eval_string = nil, arg_names = [], &block)
    hijack_method(@target_klass, method_name, eval_string, arg_names, &block )
  end

  # restore all
  def restore
    @method_stores.each_pair{|klass, method_stores|
      method_stores.reverse_each{ |method_name, method|
        klass.send :undef_method, method_name
        klass.send :define_method, method_name, method if method
      }
    }
    @method_stores.clear
    true
  rescue
    false
  end

  protected

  def class_self_instance
    @target_klass.send :eval, "class << self; self; end;"
  end

  def hijack_method(klass, method_name, eval_string = nil, arg_names = [], &block)
    method_name = method_name.to_s
    # You have got love ruby!  What other language allows you to pillage and plunder a class like this?

    (@method_stores[klass]||=[]) << [
      method_name,
      klass.instance_methods.include?(method_name) && klass.instance_method(method_name)
    ]

    klass.send :undef_method, method_name
    if Symbol === eval_string
      klass.send :define_method, method_name, klass.instance_methods(eval_string)
    elsif String === eval_string
      klass.class_eval <<-EOF
      def #{method_name}(#{arg_names * ','})
        #{eval_string}
      end
      EOF
    elsif block_given?
      klass.send :define_method, method_name, block
    end

    true
  rescue
    false
  end

end
