#
# Author:: Tyler Ball (<tball@getchef.com>)
# Copyright:: Copyright (c) 2014 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

require 'chef/resource'
require 'chef/resource_collection/stepable_iterator'
require 'chef/resource_collection/resource_collection_serialization'
require 'forwardable'

# This class keeps the list of all known Resources in the order they are to be executed in.  It also keeps a pointer
# to the most recently executed resource so we can add resources-to-execute after this point.
class Chef
  class ResourceCollection
    class ResourceList
      include ResourceCollection::ResourceCollectionSerialization
      include Enumerable
      extend Forwardable

      attr_reader :iterator

      attr_reader :resources
      private :resources
      # Delegate direct access methods to the @resources array
      # 4 extra methods here are not included in the Enumerable's instance methods
      direct_access_methods = Enumerable.instance_methods + [ :[], :each, :each_index, :empty? ]
      def_delegators :resources, *(direct_access_methods)

      def initialize
        @resources = Array.new
        @insert_after_idx = nil
      end

      # @param resource [Chef::Resource] The resource to insert
      # If @insert_after_idx is nil, we are not currently executing a converge so the Resource is appended to the
      # end of the list.  If @insert_after_idx is NOT nil, we ARE currently executing a converge so the resource
      # is inserted into the middle of the list after the last resource that was converged.  If it is called multiple
      # times (when an LWRP contains multiple resources) it keeps track of that.  See this example ResourceList:
      # [File1, LWRP1, File2] # The iterator starts and points to File1.  It is executed and @insert_after_idx=0
      # [File1, LWRP1, File2] # The iterator moves to LWRP1.  It is executed and @insert_after_idx=1
      # [File1, LWRP1, Service1, File2] # The LWRP execution inserts Service1 and @insert_after_idx=2
      # [File1, LWRP1, Service1, Service2, File2] # The LWRP inserts Service2 and @insert_after_idx=3.  The LWRP
      #     finishes executing
      # [File1, LWRP1, Service1, Service2, File2] # The iterator moves to Service1 since it is the next non-executed
      #     resource.  The execute_each_resource call below resets @insert_after_idx=2
      # If Service1 was another LWRP, it would insert its resources between Service1 and Service2.  The iterator keeps
      # track of executed resources and @insert_after_idx keeps track of where the next resource to insert should be.
      def insert(resource)
        is_chef_resource!(resource)
        if @insert_after_idx
          @resources.insert(@insert_after_idx += 1, resource)
        else
          @resources << resource
        end
      end

      # @deprecated - can be removed when it is removed from resource_collection.rb
      def []=(index, resource)
        @resources[index] = resource
      end

      def all_resources
        @resources
      end

      def execute_each_resource(&resource_exec_block)
        @iterator = ResourceCollection::StepableIterator.for_collection(@resources)
        @iterator.each_with_index do |resource, idx|
          @insert_after_idx = idx
          yield resource
        end
      end

    end
  end
end
