class Card
  module Query
    class AbstractQuery
      # The "Tie" methods support tying two queries (CardQuery, ReferenceQuery, etc)
      # together.  The "subquery_type" variable determines which tying strategy is used.
      #
      # We currently support three values for "subquery_type": :join, :exist, and :in
      #
      # In concept, here's how the different strategies would tie table A to table B
      # in SQL assuming A.id = B.a_id
      #
      # - :join  ...  FROM A JOIN B ON A.id = B.a_id
      # - :exist ...  FROM A WHERE EXISTS (SELECT * FROM B WHERE A.id = B.a_id ...)
      # - :in    ...  FROM A WHERE A.id IN (SELECT B.a_id FROM B WHERE ...)
      #
      # The different strategies will return the same values but the relative speed is
      # context dependent.
      module Tie
        def tie subquery_type, val, fields={}, subquery_args={}
          subquery = tie_subquery subquery_type, subquery_args
          subquery.interpret val
          fields = { from: :id, to: :id }.merge fields
          fasten_tie subquery, fields
        end

        def tie_subquery subquery_type, subquery_args
          subquery_args[:class] = Query.class_for subquery_type
          subquery(subquery_args)
        end

        def fasten_tie subquery, fields={}
          method = "tie_with_#{subquery.fasten}"
          send method, subquery, fields
          subquery
        end

        def tie_with_join subquery, fields={}
          join = Join.new tie_with_join_args(subquery, fields)
          negate_join(subquery, join, fields) if subquery.negate
          joins << join
        end

        def tie_with_in subquery, fields
          subquery.mods[:return] = fields[:to]
          subquery.mods[:in_field] = fld(fields[:from])
        end

        def tie_with_exist subquery, fields
          subquery.super_conditions fields if fields.present?
        end

        def fasten
          @fasten ||= root? ? :join : inherit_fasten
        end

        def tie_with_join_args subquery, fields
          args = { from: self, from_field: fields[:from],
                   to: subquery, to_field: fields[:to] }
          args[:side] = :left if left_join? subquery
          args
        end

        def left_join? subquery
          current_conjunction == "or" || subquery.negate
          # reverse conjunction if negated?
        end

        def negate_join subquery, join, fields
          subquery.conditions_on_join = join
          add_condition "#{subquery.fld fields[:to]} is null"
        end

        def inherit_fasten
          superfasten = superquery.fasten
          superfasten == :direct ? superquery.inherit_fasten : superfasten
        end

        def super_conditions fields
          superfield fields[:to], fields[:from]
        end

        def superfield myfield, superfield
          add_condition "#{fld myfield} = #{superquery.fld superfield}"
        end

        def restrict id_field, val
          if (id = id_from_val val)
            interpret id_field => id
          else
            tie :card, val, from: id_field
          end
        end

        def id_from_val val
          case val
          when Integer         then val
          when String, Symbol  then val.card_id || -999
          end
        end

        def op_and_id_or_ids_from_val val
          if (single_id = id_from_val val)
            "= #{single_id}"
          elsif list_of_ids? val
            "in (#{val.map { |v| id_from_val v }.join ', '})"
          end
        end

        def list_of_ids? val
          return false unless val.is_a? Array

          !val.find { |v| !id_from_val v }
        end
      end
    end
  end
end
