require 'json_diff/version'

# Provides helper methods to compare object trees (like those generated by JSON.parse)
# and generate a list of their differences.
module JSONDiff
  # Generates an Array of differences between the two supplied object trees with Hash roots.
  #
  # @param a [Hash] the left hand side of the comparison.
  # @param b [Hash] the right hand side of the comparison.
  # @param path [String] the JSON path at which `a` and `b` are found in a larger object tree.
  # @return [Array<String>] the differences found between `a` and `b`.
  def self.objects(a, b, path='')
    differences = []

    a.each do |k, v|
      if b.has_key? k
        if v.class != b[k].class
          differences << "type mismatch:  #{path}/#{k} '#{v.class}' != '#{b[k].class}'"
        else
          if v.is_a? Hash
            differences += objects(v, b[k], "#{path}/#{k}")
          elsif v.is_a? Array
            differences += arrays(v, b[k], "#{path}/#{k}")
          elsif v != b[k] # String, TrueClass, FalseClass, NilClass, Float, Fixnum
            differences << "value mismatch: #{path}/#{k} '#{v}' != '#{b[k]}'"
          end
        end
      else
        differences << "b is missing:   #{path}/#{k}"
      end
    end

    (b.keys - a.keys).each do |k, v|
      differences << "a is missing:   #{path}/#{k}"
    end

    differences
  end

  # Generates an Array of differences between the two supplied object trees with Array roots.
  #
  # @param a [Array] the left hand side of the comparison.
  # @param b [Array] the right hand side of the comparison.
  # @param path [String] the JSON path at which `a` and `b` are found in a larger object tree.
  # @return [Array<String>] the differences found between `a` and `b`.
  def self.arrays(a, b, path='/')
    differences = []

    if a.size != b.size
      differences << "size mismatch:  #{path}"
    else
      a.zip(b).each_with_index do |pair, index|
        if pair[0].class != pair[1].class
          differences << "type mismatch:  #{path}[#{index}] '#{pair[0].class}' != '#{pair[1].class}'"
        else
          if pair[0].is_a? Hash
            differences += objects(pair[0], pair[1], "#{path}[#{index}]")
          elsif pair[0].is_a? Array
            differences += arrays(pair[0], pair[1], "#{path}[#{index}]")
          elsif pair[0] != pair[1] # String, TrueClass, FalseClass, NilClass, Float, Fixnum
            differences << "value mismatch: #{path}[#{index}] '#{pair[0]}' != '#{pair[1]}'"
          end
        end
      end
    end

    differences
  end
end
