module Rasti
  module DB
    class Query

      DATASET_CHAINED_METHODS = [:where, :exclude, :or, :order, :reverse_order, :limit, :offset].freeze

      include Enumerable
      include Helpers::WithSchema

      def initialize(collection_class, dataset, relations=[], schema=nil)
        @collection_class = collection_class
        @dataset = dataset
        @relations = relations
        @schema = schema
      end

      def raw
        dataset.all
      end

      def pluck(*attributes)
        ds = dataset.select(*attributes.map { |attr| Sequel.qualify(collection_class.collection_name, attr) })
        attributes.count == 1 ? ds.map { |r| r[attributes.first] } : ds.map(&:values)
      end

      def primary_keys
        pluck collection_class.primary_key
      end

      def all
        with_relations(dataset.all).map do |row| 
          collection_class.model.new row
        end
      end
      alias_method :to_a, :all

      def each(&block)
        all.each &block
      end

      DATASET_CHAINED_METHODS.each do |method|
        define_method method do |*args, &block|
          Query.new collection_class, 
                    dataset.send(method, *args, &block), 
                    relations, 
                    schema
        end
      end

      def graph(*rels)
        Query.new collection_class, 
                  dataset, 
                  (relations | rels), 
                  schema
      end

      def count
        dataset.count
      end

      def any?
        count > 0
      end

      def empty?
        !any?
      end

      def first
        row = dataset.first
        row ? collection_class.model.new(with_relations(row)) : nil
      end

      def last
        row = dataset.last
        row ? collection_class.model.new(with_relations(row)) : nil
      end

      def to_s
        "#<#{self.class.name}: \"#{dataset.sql}\">"
      end
      alias_method :inspect, :to_s

      private

      def chainable(&block)
        ds = instance_eval &block
        Query.new collection_class, ds, relations, schema
      end

      def with_relations(data)
        rows = data.is_a?(Array) ? data : [data]
        Relations.graph_to rows, relations, collection_class, dataset.db, schema
        data
      end

      def method_missing(method, *args, &block)
        if collection_class.queries.key?(method)
          instance_exec *args, &collection_class.queries[method]
        else
          super
        end
      end

      def respond_to_missing?(method, include_private=false)
        collection_class.queries.key?(method) || super
      end

      attr_reader :collection_class, :dataset, :relations, :schema

    end
  end
end