require 'test_helper'

module Vedeu

  module Repositories

    class RepositoriesTestClass < Vedeu::Repositories::Repository

      attr_accessor :storage
      alias_method :in_memory, :storage

      def initialize(storage = {})
        @storage = storage
      end

      def add(model)
        if storage.is_a?(Hash)
          @storage = in_memory.merge!(model)

        else
          @storage << model

        end
      end

    end # RepositoriesTestClass

    class TestRepository < Vedeu::Repositories::Repository
    end

    class TestModel
      include Vedeu::Repositories::Model

      attr_accessor :name

      def initialize(name = nil)
        @name = name
        @repository = Vedeu::Repositories::TestRepository.new(self.class)
      end
    end

    describe Repository do

      let(:described)      { Vedeu::Repositories::Repository }
      let(:instance)       { described.new(model, storage) }
      let(:model)          { Vedeu::Repositories::TestModel }
      let(:model_instance) { model.new(model_name) }
      let(:model_name)     { 'terbium' }
      let(:storage)        { {} }

      describe '#initialize' do
        it { instance.must_be_instance_of(described) }
        it { instance.instance_variable_get('@model').must_equal(model) }
        it { instance.instance_variable_get('@storage').must_equal(storage) }
      end

      describe 'accessors' do
        it { instance.must_respond_to(:model) }
        it { instance.must_respond_to(:storage) }
      end

      describe '#all' do
        subject { instance.all }

        context 'when there are no models stored in the repository' do
          before { instance.reset }

          it { subject.must_equal([]) }
        end

        context 'when there are models stored in the repository' do
          context 'when the repository is a glorified Hash' do
            let(:storage) {
              {
                oxygen:    :life,
                plutonium: :death,
              }
            }
            it { subject.must_equal([:life, :death]) }
          end

          context 'when the repository is a glorified Set' do
            let(:storage) { Set[:oxygen, :hydrogen] }

            it { subject.must_equal([:oxygen, :hydrogen]) }
          end

          context 'when the repository is unknown' do
            let(:storage) { [:hydrogen, :helium] }

            it { subject.must_equal([:hydrogen, :helium]) }
          end
        end
      end

      describe '#by_name' do
        let(:_name) { 'carbon' }

        subject { instance.by_name(_name) }

        context 'when the model exists' do
          # it { skip }
        end

        context 'when the model does not exist' do
          # it { skip }
        end
      end

      describe '#current' do
        subject { instance.current }

        context 'when there is nothing in focus' do
          before { Vedeu.stubs(:focus) }

          it { subject.must_be_instance_of(NilClass) }
        end

        context 'when there something in focus' do
          before { Vedeu.stubs(:focus).returns('zinc') }

          it { subject.must_be_instance_of(Vedeu::Repositories::TestModel) }
        end
      end

      describe '#find' do
        subject { instance.find(model_name) }

        context 'when the model cannot be found' do
          let(:model_name) { 'not_found' }

          it { subject.must_be_instance_of(NilClass) }
        end

        context 'when the model is found' do
          let(:model_instance) { model.new('terbium') }

          before { instance.store(model_instance) }

          it 'returns the stored model' do
            subject.must_equal(model_instance)
          end
        end
      end

      describe '#find!' do
        subject { instance.find!(model_name) }

        context 'when the model cannot be found' do
          let(:model_name) { 'not_found' }

          it { proc { subject }.must_raise(Vedeu::Error::ModelNotFound) }
        end

        context 'when the model is found' do
          let(:model_instance) { model.new('terbium') }

          before { instance.store(model_instance) }

          it 'returns the stored model' do
            subject.must_equal(model_instance)
          end
        end
      end

      describe '#find_or_create' do
        let(:model_instance) { Vedeu::Repositories::TestModel.new('niobium') }

        before { Vedeu.stubs(:log) }

        subject { instance.find_or_create(model_name) }

        context 'when the model exists' do
          let(:model_name) { 'niobium' }

          before { instance.store(model_instance) }

          it { subject.must_equal(model_instance) }
        end

        context 'when the model does not exist' do
          let(:model_name) { 'zinc'}

          it 'creates and stores a new instance of the model' do
            subject.must_be_instance_of(Vedeu::Repositories::TestModel)
          end
        end
      end

      describe '#inspect' do
        subject { instance.inspect }

        it { subject.must_be_instance_of(String) }
        it { subject.must_equal('<Vedeu::Repositories::Repository>') }
      end

      describe '#registered?' do
        it 'returns false with no name' do
          RepositoriesTestClass.new.registered?('').must_equal(false)
        end

        it 'returns false with no name' do
          RepositoriesTestClass.new.registered?(nil).must_equal(false)
        end

        it 'returns false when the storage is empty' do
          RepositoriesTestClass.new.registered?('terbium').must_equal(false)
        end

        it 'returns false when the model is not registered' do
          repo = RepositoriesTestClass.new
          repo.add(name: 'samarium')

          repo.registered?('terbium').must_equal(false)
        end
      end

      describe '#remove' do
        subject { instance.remove('francium') }

        it { instance.must_respond_to(:destroy) }
        it { instance.must_respond_to(:delete) }
        it { instance.must_respond_to(:deregister) }

        context 'when the storage is empty' do
          before { instance.reset }

          it { subject.must_be_instance_of(FalseClass) }
        end

        context 'when the model is not registered' do
          before {
            instance.reset
            instance.store(Vedeu::Repositories::TestModel.new('zinc'))
          }

          it { subject.must_be_instance_of(FalseClass) }
        end

        context 'when the model is registered' do
          before {
            instance.reset
            instance.store(Vedeu::Repositories::TestModel.new('gadolinium'))
            instance.store(Vedeu::Repositories::TestModel.new('francium'))
          }

          it 'returns the storage with the model removed' do
            subject.size.must_equal(1)
          end
        end
      end

      describe '#store' do
        subject { instance.store(model_instance) }

        before { Vedeu.stubs(:log) }

        it { instance.must_respond_to(:register) }

        context 'when a name attribute is empty or nil' do
          before { model_instance.name = '' }

          it { proc { subject }.must_raise(Vedeu::Error::MissingRequired) }
        end

        context 'when a name attributes is provided' do
          let(:model_name) { 'hydrogen' }

          it { subject.must_be_instance_of(Vedeu::Repositories::TestModel) }
        end
      end

    end # Repository

  end # Repositories

end # Vedeu