require 'minitest/spec'
require 'minitest/autorun'
require 'set'

class Schemas
  def initialize
    @schemas = Set.new
  end

  def <<(hash)
    @schemas << hash
  end

  def to_a
    @schemas.to_a
  end
end

class HashSchema
  def schema(hash)
    {}.tap do |result|
      hash.keys.sort.each do |key|
        result[key] = classify(hash, key)
      end
    end
  end

  private

  def classify(hash, key)
    case hash[key]
    when Hash
      schema(hash[key])
    else
      hash[key].class.name.downcase
    end
  end
end

describe HashSchema do
  subject { HashSchema.new }

  describe 'when given unsorted hash keys' do
    let(:hash) { {'z' => 42, 'y' => 43, 'x' => '44'} }
    let(:expected) { {'x' => 'string', 'y' => 'fixnum', 'z' => 'fixnum'} }

    it 'sorts and classifies value types' do
      subject.schema(hash).must_equal expected
    end
  end

  describe 'when given unsorted nested hashes' do
    let(:hash) { {'z' => 42, 'y' => {'c' => true, 'b' => 12.1}} }
    let(:expected) { {'y' => {'b' => 'float', 'c' => 'trueclass'}, 'z' => 'fixnum'} }

    it 'recursively sorts and classifies value types' do
      subject.schema(hash).must_equal expected
    end
  end
end

describe Schemas do
  subject { Schemas.new }

  describe 'when given duplicate hashes' do
    let(:expected) { [{'a' => 42}, {'b' => 12}] }

    it 'only tracks distinct entries' do
      subject << {'a' => 42}
      subject << {'b' => 12}
      subject << {'a' => 42}
      subject.to_a.size.must_equal 2
      subject.to_a.must_equal expected
    end
  end
end