require_relative 'internal/curried_method'

module Ramda
  # Relation functions
  module Relation
    extend ::Ramda::Internal::CurriedMethod

    # Counts the elements of a list according to how many match each value
    # of a key generated by the supplied function. Returns an object mapping
    # the keys produced by fn to the number of occurrences in the list.

    # Acts as a transducer if a transformer is given in list position.
    #
    # (a -> String) -> [a] -> {*}
    #
    curried_method(:count_by) do |fn, list|
      list
        .group_by(&fn)
        .each_with_object({}) { |(key, value), acc| acc[key] = value.count }
    end

    # Finds the set (i.e. no duplicates) of all elements in the first list not
    # contained in the second list. Objects and Arrays are compared in terms
    # of value equality, not reference equality.
    #
    # [*] -> [*] -> [*]
    #
    curried_method(:difference) do |list_a, list_b|
      list_a - list_b
    end

    # Finds the set (i.e. no duplicates) of all elements in the first list not
    # contained in the second list. Duplication is determined according to the
    # value returned by applying the supplied predicate to two list elements.
    #
    # ((a, a) -> Boolean) -> [a] -> [a] -> [a]
    #
    # TODO: optimize
    curried_method(:difference_with) do |fn, list_a, list_b|
      list_a.reject do |el_a|
        list_b.find { |el_b| fn.call(el_a, el_b) }
      end
    end

    # Takes a function and two values in its domain and returns true if the
    # values map to the same value in the codomain; false otherwise.
    #
    # (a -> b) -> a -> a -> Boolean
    #
    curried_method(:eq_by) do |fn, a, b|
      fn.call(a) == fn.call(b)
    end

    # Returns true if its arguments are equivalent, false otherwise.
    # Handles cyclical data structures.
    #
    # a -> b -> Boolean
    #
    curried_method(:equals) do |a, b|
      a.eql?(b)
    end

    # Returns true if the first argument is greater than the second; false otherwise.
    #
    # Ord a => a -> a -> Boolean
    #
    curried_method(:gt) do |a, b|
      a > b
    end

    # Returns true if the first argument is greater than or equal to the second;
    # false otherwise.
    #
    # Ord a => a -> a -> Boolean
    #
    curried_method(:gte) do |a, b|
      a >= b
    end

    # Returns true if its arguments are identical, false otherwise.
    # Values are identical if they reference the same memory.
    #
    # a -> a -> Boolean
    #
    curried_method(:identical) do |a, b|
      a.equal?(b)
    end

    # Combines two lists into a set (i.e. no duplicates) composed of those elements
    # common to both lists. The order is preserved from the original array.
    #
    # [*] -> [*] -> [*]
    #
    curried_method(:intersection) do |list_a, list_b|
      list_a & list_b
    end

    # Returns true if the first argument is less than the second; false otherwise.
    #
    # a -> a -> Boolean
    #
    curried_method(:lt) do |a, b|
      a < b
    end

    # Returns true if the first argument is less than or equal to the second;
    # false otherwise.
    #
    # a -> a -> Boolean
    #
    curried_method(:lte) do |a, b|
      a <= b
    end

    # Returns the larger of its two arguments.
    #
    # Ord a => a -> a -> a
    #
    curried_method(:max) do |a, b|
      [a, b].max
    end

    # Takes a function and two values, and returns whichever value produces
    # the larger result when passed to the provided function.
    #
    # Ord b => (a -> b) -> a -> a -> a
    #
    curried_method(:max_by) do |fn, a, b|
      [a, b].max_by(&fn)
    end

    # Returns the smaller of its two arguments.
    #
    # Ord a => a -> a -> a
    #
    curried_method(:min) do |a, b|
      [a, b].min
    end

    # Takes a function and two values, and returns whichever value
    # produces the smaller result when passed to the provided function.
    #
    # Ord b => (a -> b) -> a -> a -> a
    #
    curried_method(:min_by) do |fn, a, b|
      [a, b].min_by(&fn)
    end

    # Determines whether a nested path on an object has a specific value,
    # in R.equals terms. Most likely used to filter a list.
    #
    # [Idx] -> a -> {a} -> Boolean
    # Idx = String | Int
    #
    curried_method(:path_eq) do |path, val, obj|
      path.reduce(obj) do |acc, key|
        acc[key] if acc.is_a?(Hash) || acc.is_a?(Array)
      end == val
    end

    # Returns true if the specified object property is equal, in R.equals terms,
    # to the given value; false otherwise.
    #
    # String -> a -> Object -> Boolean
    #
    curried_method(:prop_eq) do |prop, value, obj|
      obj[prop] == value
    end

    # Sorts the list according to the supplied function.
    #
    # Ord b => (a -> b) -> [a] -> [a]
    #
    curried_method(:sort_by) do |fn, xs|
      xs.sort_by(&fn)
    end

    # Combines two lists into a set (i.e. no duplicates) composed of the
    # elements of each list.
    #
    # [*] -> [*] -> [*]
    #
    curried_method(:union) do |xs1, xs2|
      xs1 | xs2
    end

    # Combines two lists into a set (i.e. no duplicates) composed of the elements
    # of each list. Duplication is determined according to the value returned
    # by applying the supplied predicate to two list elements. Combines two
    # lists into a set (i.e. no duplicates) composed of the elements of each list.
    # Duplication is determined according to the value returned by applying
    # the supplied predicate to two list elements.
    #
    # (a -> a -> Boolean) -> [*] -> [*] -> [*]
    #
    curried_method(:union_with) do |fn, xs1, xs2|
      (xs1 + xs2).uniq(&fn)
    end
  end
end
