require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))

if ADAPTER
  module QuerySpec
    class SailBoat
      include DataMapper::Resource

      property :id,      Serial
      property :name,    String
      property :port,    String
      property :captain, String

      def self.default_repository_name
        ADAPTER
      end
    end

    class Permission
      include DataMapper::Resource

      property :id,            Serial
      property :user_id,       Integer
      property :resource_id,   Integer
      property :resource_type, String
      property :token,         String

      def self.default_repository_name
        ADAPTER
      end
    end

    class Region
      include DataMapper::Resource

      property :id,   Serial
      property :name, String
   property :type, String

      def self.default_repository_name
        ADAPTER
      end
    end

    class Factory
      include DataMapper::Resource

      property :id,        Serial
      property :region_id, Integer
      property :name,      String

      repository(:mock) do
        property :land, String
      end

      belongs_to :region

      def self.default_repository_name
        ADAPTER
      end
    end

    class Vehicle
      include DataMapper::Resource

      property :id,         Serial
      property :factory_id, Integer
      property :name,       String

      belongs_to :factory

      def self.default_repository_name
        ADAPTER
      end
    end

    class Group
      include DataMapper::Resource
      property :id, Serial
      property :name, String
    end
  end

  module Namespace
    class Region
      include DataMapper::Resource

      property :id,   Serial
      property :name, String

      def self.default_repository_name
        ADAPTER
      end
    end

    class Factory
      include DataMapper::Resource

      property :id,        Serial
      property :region_id, Integer
      property :name,      String

      repository(:mock) do
        property :land, String
      end

      belongs_to :region

      def self.default_repository_name
        ADAPTER
      end
    end

    class Vehicle
      include DataMapper::Resource
      property :id,         Serial
      property :factory_id, Integer
      property :name,       String

      belongs_to :factory

      def self.default_repository_name
        ADAPTER
      end
    end
  end

  describe DataMapper::Query, "with #{ADAPTER}" do
    describe '#unique' do
      include LoggingHelper

      before do
        QuerySpec::SailBoat.auto_migrate!

        QuerySpec::SailBoat.create(:name => 'A', :port => 'C')
        QuerySpec::SailBoat.create(:name => 'B', :port => 'B')
        QuerySpec::SailBoat.create(:name => 'C', :port => 'A')
      end

      def parse_statement(log)
        log.readlines.join.chomp.split(' ~ ').last
      end

      describe 'when true' do
        if [ :postgres, :sqlite3, :mysql ].include?(ADAPTER)
          it 'should add a GROUP BY to the SQL query' do
            logger do |log|
              QuerySpec::SailBoat.all(:unique => true, :fields => [ :id ]).to_a

              case ADAPTER
                when :postgres, :sqlite3
                  parse_statement(log).should == 'SELECT "id" FROM "query_spec_sail_boats" GROUP BY "id" ORDER BY "id"'
                when :mysql
                  parse_statement(log).should == 'SELECT `id` FROM `query_spec_sail_boats` GROUP BY `id` ORDER BY `id`'
              end
            end
          end

          it 'should not add a GROUP BY to the SQL query if no field is a Property' do
            operator = DataMapper::Query::Operator.new(:thing, :test)

            # make the operator act like a Property
            class << operator
              property = QuerySpec::SailBoat.properties[:id]
              (property.methods - (public_instance_methods - %w[ type ])).each do |method|
                define_method(method) do |*args|
                  property.send(method, *args)
                end
              end
            end

            operator.should_not be_kind_of(DataMapper::Property)

            logger do |log|
              QuerySpec::SailBoat.all(:unique => true, :fields => [ operator ]).to_a

              case ADAPTER
                when :postgres, :sqlite3
                  parse_statement(log).should == 'SELECT "id" FROM "query_spec_sail_boats" ORDER BY "id"'
                when :mysql
                  parse_statement(log).should == 'SELECT `id` FROM `query_spec_sail_boats` ORDER BY `id`'
              end
            end
          end
        end
      end

      describe 'when false' do
        if [ :postgres, :sqlite3, :mysql ].include?(ADAPTER)
          it 'should not add a GROUP BY to the SQL query' do
            logger do |log|
              QuerySpec::SailBoat.all(:unique => false, :fields => [ :id ]).to_a

              case ADAPTER
                when :postgres, :sqlite3
                  parse_statement(log).should == 'SELECT "id" FROM "query_spec_sail_boats" ORDER BY "id"'
                when :mysql
                  parse_statement(log).should == 'SELECT `id` FROM `query_spec_sail_boats` ORDER BY `id`'
              end
            end
          end
        end
      end
    end

    describe 'when ordering' do
      before do
        QuerySpec::SailBoat.auto_migrate!

        QuerySpec::SailBoat.create(:name => 'A', :port => 'C')
        QuerySpec::SailBoat.create(:name => 'B', :port => 'B')
        QuerySpec::SailBoat.create(:name => 'C', :port => 'A')
      end

      it "should find by conditions" do
        lambda do
          repository(ADAPTER) do
            QuerySpec::SailBoat.first(:conditions => [ 'name = ?', 'B' ])
          end
        end.should_not raise_error

        lambda do
          repository(ADAPTER) do
            QuerySpec::SailBoat.first(:conditions => [ 'name = ?', 'A' ])
          end
        end.should_not raise_error
      end

      it "should find by conditions passed in as hash" do
        repository(ADAPTER) do
          QuerySpec::SailBoat.create(:name => "couldbe@email.com", :port => 'wee')

          find = QuerySpec::SailBoat.first(:name => 'couldbe@email.com')
          find.name.should == 'couldbe@email.com'

          find = QuerySpec::SailBoat.first(:name => 'couldbe@email.com', :port.not => nil)
          find.should_not be_nil
          find.port.should_not be_nil
          find.name.should == 'couldbe@email.com'
        end
      end

      it "should find by conditions passed in a range" do
        repository(ADAPTER) do
          find = QuerySpec::SailBoat.all(:id => 0..2)
          find.should_not be_nil
          find.should have(2).entries

          find = QuerySpec::SailBoat.all(:id.not => 0..2)
          find.should have(1).entries
        end
      end

      it "should find by conditions passed in as an array" do
        repository(ADAPTER) do
          find = QuerySpec::SailBoat.all(:id => [1,2])
          find.should_not be_nil
          find.should have(2).entries

          find = QuerySpec::SailBoat.all(:id.not => [1,2])
          find.should have(1).entries

          find = QuerySpec::SailBoat.all(:id => [])
          find.should have(0).entries

          find = QuerySpec::SailBoat.all(:id.not => [])
          find.should have(3).entries
        end
      end

      it "should order results" do
        repository(ADAPTER) do
          result = QuerySpec::SailBoat.all(:order => [
            DataMapper::Query::Direction.new(QuerySpec::SailBoat.properties[:name], :asc)
          ])
          result[0].id.should == 1

          result = QuerySpec::SailBoat.all(:order => [
            DataMapper::Query::Direction.new(QuerySpec::SailBoat.properties[:port], :asc)
          ])
          result[0].id.should == 3

          result = QuerySpec::SailBoat.all(:order => [
            DataMapper::Query::Direction.new(QuerySpec::SailBoat.properties[:name], :asc),
            DataMapper::Query::Direction.new(QuerySpec::SailBoat.properties[:port], :asc)
          ])
          result[0].id.should == 1

          result = QuerySpec::SailBoat.all(:order => [
            QuerySpec::SailBoat.properties[:name],
            DataMapper::Query::Direction.new(QuerySpec::SailBoat.properties[:port], :asc)
          ])
          result[0].id.should == 1

          result = QuerySpec::SailBoat.all(:order => [ :name ])
          result[0].id.should == 1

          result = QuerySpec::SailBoat.all(:order => [ :name.desc ])
          result[0].id.should == 3
        end
      end
    end

    describe 'when sub-selecting' do
      before do
        [ QuerySpec::SailBoat, QuerySpec::Permission ].each { |m| m.auto_migrate! }

        QuerySpec::SailBoat.create(:id => 1, :name => "Fantasy I",      :port => "Cape Town", :captain => 'Joe')
        QuerySpec::SailBoat.create(:id => 2, :name => "Royal Flush II", :port => "Cape Town", :captain => 'James')
        QuerySpec::SailBoat.create(:id => 3, :name => "Infringer III",  :port => "Cape Town", :captain => 'Jason')

        #User 1 permission -- read boat 1 & 2
        QuerySpec::Permission.create(:id => 1, :user_id => 1, :resource_id => 1, :resource_type => 'SailBoat', :token => 'READ')
        QuerySpec::Permission.create(:id => 2, :user_id => 1, :resource_id => 2, :resource_type => 'SailBoat', :token => 'READ')

        #User 2 permission  -- read boat 2 & 3
        QuerySpec::Permission.create(:id => 3, :user_id => 2, :resource_id => 2, :resource_type => 'SailBoat', :token => 'READ')
        QuerySpec::Permission.create(:id => 4, :user_id => 2, :resource_id => 3, :resource_type => 'SailBoat', :token => 'READ')
      end

      it 'should accept a DM::Query as a value of a condition' do
        # User 1
        acl = DataMapper::Query.new(repository(ADAPTER), QuerySpec::Permission, :user_id => 1, :resource_type => 'SailBoat', :token => 'READ', :fields => [ :resource_id ])
        query = { :port => 'Cape Town', :id => acl, :captain.like => 'J%', :order => [ :id ] }
        boats = repository(ADAPTER) { QuerySpec::SailBoat.all(query) }
        boats.should have(2).entries
        boats.entries[0].id.should == 1
        boats.entries[1].id.should == 2

        # User 2
        acl = DataMapper::Query.new(repository(ADAPTER), QuerySpec::Permission, :user_id => 2, :resource_type => 'SailBoat', :token => 'READ', :fields => [ :resource_id ])
        query = { :port => 'Cape Town', :id => acl, :captain.like => 'J%', :order => [ :id ] }
        boats = repository(ADAPTER) { QuerySpec::SailBoat.all(query) }

        boats.should have(2).entries
        boats.entries[0].id.should == 2
        boats.entries[1].id.should == 3
      end

      it 'when value is NOT IN another query' do
        # Boats that User 1 Cannot see
        acl = DataMapper::Query.new(repository(ADAPTER), QuerySpec::Permission, :user_id => 1, :resource_type => 'SailBoat', :token => 'READ', :fields => [ :resource_id ])
        query = { :port => 'Cape Town', :id.not => acl, :captain.like => 'J%' }
        boats = repository(ADAPTER) { QuerySpec::SailBoat.all(query) }
        boats.should have(1).entries
        boats.entries[0].id.should == 3
      end
    end  # describe sub-selecting

    describe 'when linking associated objects' do
      before do
        [ QuerySpec::Region, QuerySpec::Factory, QuerySpec::Vehicle ].each { |m| m.auto_migrate! }

        QuerySpec::Region.create(:id => 1, :name => 'North West', :type => 'commercial')
        QuerySpec::Factory.create(:id => 2000, :region_id => 1, :name => 'North West Plant')
        QuerySpec::Vehicle.create(:id => 1, :factory_id => 2000, :name => '10 ton delivery truck')

        Namespace::Region.auto_migrate!
        Namespace::Factory.auto_migrate!
        Namespace::Vehicle.auto_migrate!

        Namespace::Region.create(:id => 1, :name => 'North West')
        Namespace::Factory.create(:id => 2000, :region_id => 1, :name => 'North West Plant')
        Namespace::Vehicle.create(:id => 1, :factory_id => 2000, :name => '10 ton delivery truck')
      end

      it 'should require that all properties in :fields and all :links come from the same repository' #do
      #  land = QuerySpec::Factory.properties(:mock)[:land]
      #  fields = []
      #  QuerySpec::Vehicle.properties(ADAPTER).map do |property|
      #    fields << property
      #  end
      #  fields << land
      #
      #  lambda{
      #    begin
      #      results = repository(ADAPTER) { QuerySpec::Vehicle.all(:links => [ :factory ], :fields => fields) }
      #    rescue RuntimeError
      #      $!.message.should == "Property QuerySpec::Factory.land not available in repository #{ADAPTER}"
      #      raise $!
      #    end
      #  }.should raise_error(RuntimeError)
      #end

      it 'should accept a DM::Assoc::Relationship as a link' do
        factory = DataMapper::Associations::Relationship.new(
          :factory,
          ADAPTER,
          'QuerySpec::Vehicle',
          'QuerySpec::Factory',
          { :child_key => [ :factory_id ], :parent_key => [ :id ] }
        )
        results = repository(ADAPTER) { QuerySpec::Vehicle.all(:links => [ factory ]) }
        results.should have(1).entries
      end

      it 'should accept a symbol of an association name as a link' do
        results = repository(ADAPTER) { QuerySpec::Vehicle.all(:links => [ :factory ]) }
        results.should have(1).entries
      end

      it 'should accept a string of an association name as a link' do
        results = repository(ADAPTER) { QuerySpec::Vehicle.all(:links => [ 'factory' ]) }
        results.should have(1).entries
      end

      it 'should accept a mixture of items as a set of links' do
        region = DataMapper::Associations::Relationship.new(
          :region,
          ADAPTER,
          'QuerySpec::Factory',
          'QuerySpec::Region',
          { :child_key => [ :region_id ], :parent_key => [ :id ] }
        )
        results = repository(ADAPTER) { QuerySpec::Vehicle.all(:links => [ 'factory', region ]) }
        results.should have(1).entries
      end

      it 'should only accept a DM::Assoc::Relationship, String & Symbol as a link' do
        lambda{
          DataMapper::Query.new(repository(ADAPTER), QuerySpec::Vehicle, :links => [1])
        }.should raise_error(ArgumentError)
      end

      it 'should have a association by the name of the Symbol or String' do
        lambda{
          DataMapper::Query.new(repository(ADAPTER), QuerySpec::Vehicle, :links => [ 'Sailing' ])
        }.should raise_error(ArgumentError)

        lambda{
          DataMapper::Query.new(repository(ADAPTER), QuerySpec::Vehicle, :links => [ :sailing ])
        }.should raise_error(ArgumentError)
      end

      it 'should create an n-level query path' do
        QuerySpec::Vehicle.factory.region.model.should == QuerySpec::Region
        QuerySpec::Vehicle.factory.region.name.property.should == QuerySpec::Region.properties(QuerySpec::Region.repository.name)[ :name ]
      end

      it 'should accept a DM::QueryPath as the key to a condition' do
        vehicle = QuerySpec::Vehicle.first(QuerySpec::Vehicle.factory.region.name => 'North West')
        vehicle.name.should == '10 ton delivery truck'

        vehicle = Namespace::Vehicle.first(Namespace::Vehicle.factory.region.name => 'North West')
        vehicle.name.should == '10 ton delivery truck'
      end

      it "should accept a string representing a DM::QueryPath as they key to a condition" do
        vehicle = QuerySpec::Vehicle.first("factory.region.name" => 'North West')
        vehicle.name.should == '10 ton delivery truck'
      end

   it "should accept 'id' and 'type' as endpoints on ah DM::QueryPath" do
    vehicle = QuerySpec::Vehicle.first( QuerySpec::Vehicle.factory.region.type => 'commercial' )
    vehicle.name.should == '10 ton delivery truck'
    vehicle = QuerySpec::Vehicle.first( QuerySpec::Vehicle.factory.region.id => 1 )
    vehicle.name.should == '10 ton delivery truck'
   end

      it 'should auto generate the link if a DM::Property from a different resource is in the :fields option'

      it 'should create links with composite keys'

      it 'should eager load associations' do
        repository(ADAPTER) do
          vehicle = QuerySpec::Vehicle.first(:includes => [ QuerySpec::Vehicle.factory ])
        end
      end

      it "should behave when using mocks" do
        QuerySpec::Group.should_receive(:all).with(:order => [ :id.asc ])
        QuerySpec::Group.all(:order => [ :id.asc ])
      end
    end   # describe links
  end # DM::Query
end
