# Copyright 2023 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

require "google/cloud/datastore/v1"

module Google
  module Cloud
    module Datastore
      ##
      # Filter
      #
      # Represents the filter criteria for a datastore query.
      #
      # @example Run a query with a simple property filter.
      #   require "google/cloud/datastore"
      #
      #   datastore = Google::Cloud::Datastore.new
      #
      #   filter = Google::Cloud::Datastore::Filter.new("done", "=", "false")
      #
      #   query = Google::Cloud::Datastore::Query.new
      #   query.kind("Task")
      #        .where(filter)
      #
      #   tasks = datastore.run query
      #
      # @example Construct a composite filter with a logical OR.
      #   require "google/cloud/datastore"
      #
      #   datastore = Google::Cloud::Datastore.new
      #
      #   filter = Google::Cloud::Datastore::Filter.new("done", "=", "false")
      #                                            .or("priority", ">=", "4")
      #
      #   query = Google::Cloud::Datastore::Query.new
      #   query.kind("Task")
      #        .where(filter)
      #
      #   tasks = datastore.run query
      #
      # @example Construct a composite filter by combining multiple filters.
      #   require "google/cloud/datastore"
      #
      #   datastore = Google::Cloud::Datastore.new
      #
      #   filter_1 = Google::Cloud::Datastore::Filter.new("done", "=", "false")
      #   filter_2 = Google::Cloud::Datastore::Filter.new("priority", ">=", "4")
      #   filter = filter_1.or(filter_2)
      #
      #   query = Google::Cloud::Datastore::Query.new
      #   query.kind("Task")
      #        .where(filter)
      #
      #   tasks = datastore.run query
      #
      class Filter
        ##
        # @private Object of type
        # Google::Cloud::Datastore::V1::Filter
        attr_accessor :grpc

        ##
        # Creates a new Filter.
        #
        # @example
        #   require "google/cloud/datastore"
        #
        #   filter = Google::Cloud::Datastore::Filter.new("done", "=", "false")
        #
        def initialize name, operator, value
          @grpc = create_property_filter name, operator, value
        end

        ##
        # Joins two filters with an AND operator.
        #
        # @overload and(name, operator, value)
        #   Joins the filter with a property filter
        #   @param name [String]
        #   @param operator [String]
        #   @param value
        #
        # @overload and(filter)
        #   Joins the filter with a Filter object
        #   @param filter [Filter]
        #
        # @example Join the filter with a property filter
        #   require "google/cloud/datastore"
        #
        #   datastore = Google::Cloud::Datastore.new
        #
        #   filter = Google::Cloud::Filter.new("done", "=", false)
        #                                 .and("priority", ">=", 4)
        #
        # @example Join the filter with a filter object
        #   require "google/cloud/datastore"
        #
        #   datastore = Google::Cloud::Datastore.new
        #
        #   filter_1 = Google::Cloud::Filter.new("done", "=", false)
        #   filter_2 = Google::Cloud::Filter.new("priority", ">=", 4)
        #
        #   filter = filter_1.and(filter_2)
        #
        def and name_or_filter, operator = nil, value = nil
          combine_filters composite_filter_and, name_or_filter, operator, value
        end

        ##
        # Joins two filters with an OR operator.
        #
        # @overload or(name, operator, value)
        #   Joins the filter with a property filter
        #   @param name [String] The property to filter by.
        #   @param operator [String] The operator to filter by. Defaults to nil.
        #   @param value [Object] The value to compare the property to. Defaults to nil.
        #       Possible values are:
        #         - Integer
        #         - Float/BigDecimal
        #         - String
        #         - Boolean
        #         - Array
        #         - Date/Time
        #         - StringIO
        #         - Google::Cloud::Datastore::Key
        #         - Google::Cloud::Datastore::Entity
        #         - nil
        #
        # @overload or(filter)
        #   Joins the filter with a Filter object
        #   @param flter [Filter]
        #
        # @example Join the filter with a property filter
        #   require "google/cloud/datastore"
        #
        #   datastore = Google::Cloud::Datastore.new
        #
        #   filter = Google::Cloud::Filter.new("done", "=", false)
        #                                 .or("priority", ">=", 4)
        #
        # @example Join the filter with a filter object
        #   require "google/cloud/datastore"
        #
        #   datastore = Google::Cloud::Datastore.new
        #
        #   filter_1 = Google::Cloud::Filter.new("done", "=", false)
        #   filter_2 = Google::Cloud::Filter.new("priority", ">=", 4)
        #
        #   filter = filter_1.or(filter_2)
        #
        def or name_or_filter, operator = nil, value = nil
          combine_filters composite_filter_or, name_or_filter, operator, value
        end

        # @private
        def to_grpc
          @grpc
        end

        protected

        ##
        # @private
        #
        # Combines self.grpc and (name_or_filter, operator, value)
        # into the new_filter object with the specified AND/OR operator
        def combine_filters new_filter, name_or_filter, operator, value
          new_filter.composite_filter.filters << to_grpc
          new_filter.composite_filter.filters << if name_or_filter.is_a? Google::Cloud::Datastore::Filter
                                                   name_or_filter.to_grpc
                                                 else
                                                   create_property_filter name_or_filter, operator, value
                                                 end
          dup.tap do |f|
            f.grpc = new_filter
          end
        end

        ##
        # @private
        def composite_filter_and
          Google::Cloud::Datastore::V1::Filter.new(
            composite_filter: Google::Cloud::Datastore::V1::CompositeFilter.new(op: :AND)
          )
        end

        ##
        # @private
        def composite_filter_or
          Google::Cloud::Datastore::V1::Filter.new(
            composite_filter: Google::Cloud::Datastore::V1::CompositeFilter.new(op: :OR)
          )
        end

        ##
        # @private
        def create_property_filter name, operator, value
          Google::Cloud::Datastore::V1::Filter.new(
            property_filter: Google::Cloud::Datastore::V1::PropertyFilter.new(
              property: Google::Cloud::Datastore::V1::PropertyReference.new(
                name: name
              ),
              op: Convert.to_prop_filter_op(operator),
              value: Convert.to_value(value)
            )
          )
        end
      end
    end
  end
end
