module SimpleSearch
  OP_MAP = { :gt=>">", :ge=>">=", :lt=>"<", :le=>"<=", :eq=>"=", :ne=>"<>", :in=>"IN", 
    :bt=>"BETWEEN",    :between => "BETWEEN",
    :sw=>"LIKE",       :startswith => "LIKE",
    :ew=>"LIKE",       :endswith => "LIKE",
    :ct=>"LIKE",       :contains => "LIKE",    :like => "LIKE",
    :nc=>"NOT LIKE",   :notcontains => "NOT LIKE", :notlike=>"NOT LIKE",
    :is=>"IS", 
    :it=> 'IS NOT',    :isnot => "IS NOT"
  }

  class Error < Exception; end

  module ActiveRecord
    
    def self.included(base)
      base.extend ClassMethods
    end

    module ClassMethods
      def simplesearch(params={})

        arel = self.scoped unless self.is_a?(::ActiveRecord::Relation)
				other_params, where_params=filtered_params(params)  # validate params
        arel = with_ss_where(arel,where_params)
        arel = with_ss_group(arel,other_params['group_by'])
        arel = with_ss_order(arel,other_params['order_by'])
        arel = with_ss_limit_offset(arel,other_params)
        arel
      end

      alias_method :ss, :simplesearch unless respond_to?(:ss)
		
			def filtered_params(params)
				where_params = {}
				other_params = {}
        params.each do |key,value|
					if ["group_by","page_by","page", "order_by"].include?(key)
						other_params[key]=value
					else
						matches = /(.*)_([a-z]+)$/.match(key)
						if matches.length==3
							all, col, op = matches.to_a
							operands = SimpleSearch::OP_MAP.keys
							if operands.include?(op.to_sym)
								where_params[key]=value
							else
								logger.warning("SimpleSearch ignored #{key}. operand must be one of #{operands}") unless logger.nil?
							end
						else
							logger.warning("SimpleSearch ignored #{key}. it requires to end with _<operands>") unless logger.nil?
						end
					end
				end
				[other_params, where_params]
			end

      def with_ss_where(arel,params={})          
        params.each do |key,value|
					matches = /(.*)_([a-z]+)$/.match(key)
          all, col, op = matches.to_a
					where_col =  col =~ /\./ ? col : "#{arel.table_name}.#{col}" #if no table name, the add it
          where_value = case op.to_sym
            when :gt,:ge,:lt,:le,:eq,:ne  then value
            when :sw       then "#{value}%"  # LIKE
            when :ew       then "%#{value}"  # LIKE
            when :ct,:contains,:like then "%#{value}%" # LIKE
            when :nc,:notcontains,:notlike then "%#{value}%" # NOT LIKE
            when :is, :isnot, :it
              if    ["NULL","null"].include?(value)   then nil
              elsif ["TRUE","true"].include?(value)   then true
              elsif ["FALSE","false"].include?(value) then false
              end
          end
					if op.to_sym==:in
						arel = arel.where("#{where_col}"=>value.split(','))
					elsif [:bt, :between].include? op.to_sym
            first,second = value.split('..')
						arel = arel.where("#{where_col}"=>Range.new(first,second))
					else
						arel = arel.where("#{where_col} #{SimpleSearch::OP_MAP[op.to_sym]} ?", where_value)
					end
        end
        arel
      end

			def with_ss_group(arel,group_by)          
				arel= arel.group(group_by) if group_by
				arel
			end

			def with_ss_order(arel,order_by)          
				arel= arel.order(order_by) if order_by
				arel
			end
			
			def with_ss_limit_offset(arel,params)          
				page_num = (params['page'] || 1).to_i
				page_by  = (params['page_by'] || 50).to_i
				arel = arel.limit(page_by).offset( (page_num-1)*page_by )
				arel
			end

		end # end class methods
  end # end module ActiveRecord
end  # end module SimpleSearch