module RSpec::Rails
  module BaseHelper
    def self.included(base)
      base.extend ClassMethods
      base.class_eval do
        include RSpec::Rails::App
        include Thor::Actions
      end
    end
    
    module ClassMethods
      def aliases_for name
        multi_alias name, 
                    :create => :new, :insert_into => [:inject_into, :update], :read => :X_content, :remove => :delete, 
                    :options => :after
      end
    end

    def type_postfix type
      "_#{type}" if ![:model].include?(type)
    end

    def artifact_path name, type, dir=nil
      dir ||= send :"#{type}_dir"
      File.join(dir, "#{name}#{type_postfix type}.rb")
    end
         
    # CREATE
    def create_artifact name, options={}, &block
      type = get_type options

      file = file_name name, type
      return if File.exist?(file)

      debug "create #{type}: #{name}"
      debug "file: #{file}"

      # make dir        
      dir = File.dirname(file)
      FileUtils.mkdir_p dir if !File.directory?(dir)

      content = options[:content]
      content ||= yield if block

      content_method = :"new_#{type}_content"
      debug "inner content: #{content}, to inject using ##{content_method}"

      raise "Content method #{content_method} not found #{orm_notify}" if !respond_to?(content_method)
      insert_content = send content_method, name, content        
      
      debug "content to insert: #{insert_content}"

      return if insert_content.blank?

      File.open(file, 'w') do |f|  
        f.puts insert_content
      end
      debug "file created and content inserted"
    end          

    # READ
    def read_artifact(name, options, &block)
      type = get_type options
      file_name = existing_file_name(name, type)
      debug "reading from: #{file_name}"
      content = File.new(file_name).try :read
      debug "read content: #{content}"
      yield content if block
      content
    end

    # UPDATE
    def insert_content(name, options={}, &block)
      type = get_type options
      file = existing_file_name(name, type)  
      file_insertion file, marker(name, type, options), options, &block
    end

    # DELETE
    def remove_artifact name, type
      debug "remove artifact: #{name}"            
      file = file_name name, type
      debug "removed" if File.exist?(file) && FileUtils.rm_f(file) 
    end

    def remove_artifacts type,*names
      names.flatten.each{|name| send :"remove_#{type}", name }
    end

    def new_artifact_content name, type, content=nil, &block
      content ||= yield if block
      %Q{class #{marker(name, type)}
  #{content}
end}
    end

    protected

    def get_type options = {}
      raise ArgumentError, "No artifact type specified" if !options[:type]
      options[:type]      
    end

    def debug?
      RSpec::Generator.debug?
    end    

    def debug msg
      puts msg if debug?
    end

    def set options, type
      options.merge!(:type => type)
      options
    end

    def orm_notify
      ''
    end

    def marker name, type, options={}
      send :"#{type}_marker", name, options               
    end

    def file_name name, type, options={}
      send :"#{type}_file_name", name, options
    end

    def existing_file_name name, type 
      # first try finder method
      finder_method = :"find_#{type}"
      found = send finder_method, name if respond_to? finder_method      
      # default
      file_name(name, type) if !found
    end

    def file_insertion(file_name, marker, options={}, &block)
      debug "file insertion: marker = #{marker}"
      return if !marker

      file = File.new(file_name)
      return if !File.exist?(file)

      insert_content = options[:content] || (yield if block)

      # already inserted?
      return if insert_content.blank? || (file.read =~ /#{insert_content}/)

      place = options[:before] ? :before : :after 

      debug "inserted #{place}: '#{marker}'"
      debug "content: #{insert_content}"
      
      mutate_file file.path, marker, place do
         insert_content
       end
     end   

     def mutate_file file, marker, place, &block
       raise ArgumentError, "You must define a replacement marker for a :before or :after key" if !marker 
       replace_in_file file, /(#{Regexp.escape(marker)})/mi do |match|
         place == :after ? "#{match}#{yield}" : "#{yield}#{match}"         
       end
     end  
     
     def replace_in_file(path, regexp, *args, &block)
       content = File.read(path).gsub(regexp, *args, &block)
       File.open(path, 'wb') { |file| file.write(content) }
     end               
   end
 end