module RecordFilter
  class Join # :nodoc: all
    attr_reader :left_table, :right_table

    def initialize(left_table, right_table, join_conditions, join_type=nil)
      @left_table, @right_table, @join_conditions, @join_type =
        left_table, right_table, join_conditions, join_type
      @join_type ||= :inner
    end

    def to_sql
      if @join_conditions.blank?
        raise InvalidJoinException.new("Conditions must be provided for explicit joins using the 'on' method");
      end
      if join_type_string.nil?
        raise ArgumentError.new("The provided join type '#{@join_type}' is invalid.")
      end
      predicate_sql = @join_conditions.map do |condition|
        condition_to_predicate_part(condition)
      end * ' AND '
      "#{join_type_string} JOIN #{@right_table.table_name} AS #{@right_table.table_alias} ON #{predicate_sql}"
    end

    def requires_distinct_select?
      [:right, :left].include?(@join_type)
    end

    protected

    def condition_to_predicate_part(condition)
      if condition.is_a?(Hash)
        "#{column_join_alias(@left_table, condition.keys[0])} = #{column_join_alias(@right_table, condition.values[0])}"
      elsif condition.is_a?(RecordFilter::DSL::Restriction)
        restriction_join_alias(condition)
      end
    end

    def column_join_alias(table, column)
      unless table.has_column(column)
        raise ColumnNotFoundException.new("The column #{column} was not found in the table #{table.table_name}")
      end
      "#{table.table_alias}.#{column}"
    end

    def restriction_join_alias(dsl_restriction)
      unless @right_table.has_column(dsl_restriction.column)
        raise ColumnNotFoundException.new("The column #{dsl_restriction.column} was not found in the table #{@right_table.table_name}")
      end
      restriction = RestrictionFactory.build(
        dsl_restriction.operator,
        "#{@right_table.table_alias}.#{dsl_restriction.column}",
        dsl_restriction.value,
        @right_table,
        :negated => dsl_restriction.negated)
      @right_table.model_class.merge_conditions(restriction.to_conditions)
    end

    def join_type_string
      @join_type_string ||= case(@join_type)
        when :inner, 'inner' then 'INNER'
        when :left, 'left' then 'LEFT OUTER'
        when :right, 'right' then 'RIGHT OUTER'
        else nil
      end
    end
  end
end
