module Search

  class Dbbase
    def initialize(operands, table_name, field_name, operator)
      @operands = operands
      @table_name = table_name
      @field_name = field_name
      @operator = operator
    end

    def like_operator
      'LIKE'
    end

    def sql_field_name
      "#{@table_name}.#{@field_name}"
    end

    def operands
      @operands
    end
  end

  class PostgresqlSpecific < Dbbase
    def like_operator
      'ILIKE'
    end
  end

  class OracleSpecific < Dbbase
    def sql_field_name
      result = super
      %w(contains is_exactly does_not_contain).include?(@operator) ?  "upper(#{result})" : result
    end

    def operands
      result = super
      %w(contains is_exactly does_not_contain).include?(@operator) ?  result.upcase : result
    end
  end

  class Term

    attr_accessor :error, :table_name, :field, :operator, :operands, :dbbase

    def initialize(klass, value, search_type)
      @table_name = klass.table_name
      compute_search_fields(value)

      adapter = AdminData::Config.setting[:adapter_name].downcase
      if adapter =~ /postgresql/
        self.dbbase = PostgresqlSpecific.new(@operands, table_name, field, operator)
      elsif adapter =~ /oracle/
        self.dbbase = OracleSpecific.new(@operands, table_name, field, operator)
      else
        self.dbbase = Dbbase.new(@operands, table_name, field, operator)
      end
    end

    def attribute_condition
      return if valid? && operand_required? && operands.blank?
      case operator
      when 'contains'
        ["#{sql_field_name} #{like_operator} ?","%#{operands}%"]

      when 'is_exactly'
        ["#{sql_field_name} = ?", operands]

      when 'does_not_contain'
        ["#{sql_field_name} IS NULL OR #{sql_field_name} NOT #{like_operator} ?","%#{operands}%"]

      when 'is_false'
        ["#{sql_field_name} = ?",false]

      when 'is_true'
        ["#{sql_field_name} = ?",true]

      when 'is_null'
        ["#{sql_field_name} IS NULL"]

      when 'is_not_null'
        ["#{sql_field_name} IS NOT NULL"]

      when 'is_on'
        ["#{sql_field_name} >= ? AND #{sql_field_name} < ?",   values_after_cast.beginning_of_day,
        values_after_cast.end_of_day]

      when 'is_on_or_before_date'
        ["#{sql_field_name} <= ?",values_after_cast.end_of_day]

      when 'is_on_or_after_date'
        ["#{sql_field_name} >= ?",values_after_cast.beginning_of_day]

      when 'is_equal_to'
        ["#{sql_field_name} = ?",values_after_cast]

      when 'greater_than'
        ["#{sql_field_name} > ?",values_after_cast]

      when 'less_than'
        ["#{sql_field_name} < ?",values_after_cast]

      else
        # it means user did not select anything in operator. Ignore it.
      end
    end

    def valid?
      @error = nil
      @error = validate
      @error.blank?
    end

    private

    def like_operator
      dbbase.like_operator
    end

    def sql_field_name
      dbbase.sql_field_name
    end

    def operands
      dbbase.operands
    end

    def operand_required?
      operator =~ /(contains|is_exactly|does_not_contain|is_on |is_on_or_before_date|is_on_or_after_date |greater_than|less_than|is_equal_to)/
    end

    def compute_search_fields(value)
      @field, @operator, @operands = value.values_at(:col1, :col2, :col3)
      # field value is directly used in the sql statement. So it is important to sanitize it
      @field      = @field.gsub(/\W/,'')
      @operands   = (@operands.blank? ? @operands : @operands.strip)
    end

    def values_after_cast
      case operator
      when /(is_on|is_on_or_before_date|is_on_or_after_date)/
        AdminDataDateValidation.validate(operands)
      when /(is_equal_to|greater_than|less_than)/
        operands.to_i
      else
        operands
      end
    end

    def validate
      case operator
      when /(is_on|is_on_or_before_date|is_on_or_after_date)/
        "#{operands} is not a valid date" unless AdminDataDateValidation.validate(operands)
      when /(is_equal_to|greater_than|less_than)/
        unless operands.blank?
          "#{operands} is not a valid integer" unless operands =~ /^\d+$/
        end
      end
    end

  end # end of Term


  def build_quick_search_conditions( klass, search_term )
    return nil if search_term.blank?
    str_columns = klass.columns.select { |column| column.type.to_s =~ /(string|text)/i }
    conditions = str_columns.collect do |column|
      t = Term.new(klass, {:col1 => column.name, :col2 => 'contains', :col3 => search_term}, 'quick_search')
      t.attribute_condition
    end
    AdminData::Util.or_merge_conditions(klass, *conditions)
  end

  def build_advance_search_conditions(klass, options)
    values        = ( options.blank? ? [] : options.values )
    terms         = values.collect {|value| Term.new(klass, value, 'advance_search') }
    valid_terms   = terms.select{ |t| t.valid? }
    errors        = (terms - valid_terms).collect { |t| t.error }
    return {:errors => errors} if errors.any?

    r = klass.unscoped
    valid_terms.each { |t|  r = r.where(t.attribute_condition) }
    { :cond => r }
  end

end