# encoding: utf-8

shared_examples 'IceNine.deep_freeze' do
  context 'with an Object' do
    let(:value) { Object.new }

    before do
      value.instance_eval { @a = '1' }
    end

    it 'returns the object' do
      should be(value)
    end

    it 'freezes the object' do
      expect { subject }.to change(value, :frozen?).from(false).to(true)
    end

    it 'freezes the instance variables in the Object' do
      expect(subject.instance_variable_get(:@a)).to be_frozen
    end

    context 'with a circular reference' do
      before do
        value.instance_eval { @self = self }
      end

      it 'returns the object' do
        should be(value)
      end

      it 'freezes the object' do
        expect { subject }.to change(value, :frozen?).from(false).to(true)
      end

      it 'freezes the instance variables in the Object' do
        expect(subject.instance_variable_get(:@a)).to be_frozen
      end
    end
  end

  context 'with an Array' do
    let(:value) { %w[a] }

    it 'returns the object' do
      should be(value)
    end

    it 'freezes the object' do
      expect { subject }.to change(value, :frozen?).from(false).to(true)
    end

    it 'freezes each element in the Array' do
      expect(subject.select(&:frozen?)).to eql(subject)
    end

    context 'with a circular reference' do
      before do
        value << value
      end

      it 'returns the object' do
        should be(value)
      end

      it 'freezes the object' do
        expect { subject }.to change(value, :frozen?).from(false).to(true)
      end

      it 'freezes each element in the Array' do
        expect(subject.select(&:frozen?)).to eql(subject)
      end
    end
  end

  context 'with a Hash' do
    let(:value) { { Object.new => Object.new } }

    it 'returns the object' do
      should be(value)
    end

    it 'freezes the object' do
      expect { subject }.to change(value, :frozen?).from(false).to(true)
    end

    it 'freezes each key in the Hash' do
      expect(subject.keys.select(&:frozen?)).to eql(subject.keys)
    end

    it 'freezes each value in the Hash' do
      expect(subject.values.select(&:frozen?)).to eql(subject.values)
    end

    context 'with a circular reference' do
      before do
        value[value] = value
      end

      it 'returns the object' do
        should be(value)
      end

      it 'freezes the object' do
        expect { subject }.to change(value, :frozen?).from(false).to(true)
      end

      it 'freezes each key in the Hash' do
        expect(subject.keys.select(&:frozen?)).to eql(subject.keys)
      end

      it 'freezes each value in the Hash' do
        expect(subject.values.select(&:frozen?)).to eql(subject.values)
      end
    end
  end

  context 'with a Range' do
    let(:value) { 'a'..'z' }

    it 'returns the object' do
      should be(value)
    end

    it 'freezes the object' do
      expect { subject }.to change(value, :frozen?).from(false).to(true)
    end

    it 'freeze the first object in the Range' do
      expect(subject.begin).to be_frozen
    end

    it 'freeze the last object in the Range' do
      expect(subject.end).to be_frozen
    end
  end

  context 'with a String' do
    let(:value) { '' }

    before do
      value.instance_eval { @a = '1' }
    end

    it 'returns the object' do
      should be(value)
    end

    it 'freezes the object' do
      expect { subject }.to change(value, :frozen?).from(false).to(true)
    end

    it 'freezes the instance variables in the String' do
      expect(subject.instance_variable_get(:@a)).to be_frozen
    end

    context 'with a circular reference' do
      before do
        value.instance_eval { @self = self }
      end

      it 'returns the object' do
        should be(value)
      end

      it 'freezes the object' do
        expect { subject }.to change(value, :frozen?).from(false).to(true)
      end

      it 'freezes the instance variables in the String' do
        expect(subject.instance_variable_get(:@a)).to be_frozen
      end
    end
  end

  context 'with a Struct' do
    let(:value) { klass.new(%w[ 1 2 ]) }
    let(:klass) { Struct.new(:a)       }

    it 'returns the object' do
      should be(value)
    end

    it 'freezes the object' do
      expect { subject }.to change(value, :frozen?).from(false).to(true)
    end

    it 'freezes each value in the Struct' do
      expect(subject.values.select(&:frozen?)).to eql(subject.values)
    end

    context 'with a circular reference' do
      before do
        value.a = value
      end

      it 'returns the object' do
        should be(value)
      end

      it 'freezes the object' do
        expect { subject }.to change(value, :frozen?).from(false).to(true)
      end

      it 'freezes each value in the Struct' do
        expect(subject.values.select(&:frozen?)).to eql(subject.values)
      end
    end
  end

  context 'with an SimpleDelegator' do
    let(:value) { SimpleDelegator.new('foo') }

    before do
      value.instance_eval { @a = '1' }
    end

    it 'returns the object' do
      should be(value)
    end

    it 'freezes the object' do
      expect { subject }.to change(value, :frozen?).from(false).to(true)
    end

    it 'freezes the instance variables in the SimpleDelegator' do
      expect(subject.instance_variable_get(:@a)).to be_frozen
    end

    context 'with a circular reference' do
      before do
        value.instance_eval { @self = self }
      end

      it 'returns the object' do
        should be(value)
      end

      it 'freezes the object' do
        expect { subject }.to change(value, :frozen?).from(false).to(true)
      end

      it 'freezes the instance variables in the SimpleDelegator' do
        expect(subject.instance_variable_get(:@a)).to be_frozen
      end
    end
  end

  [0.0, 0, 0x7fffffffffffffff, true, false, nil, :symbol].each do |value|
    context "with a #{value.class}" do
      let(:value) { value }

      it 'returns the object' do
        should be(value)
      end

      it 'does not freeze the object' do
        expect { subject }.to_not change(value, :frozen?).from(value.frozen?)
      end
    end
  end
end
