# Copyright (c) 2023 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
# frozen_string_literal: true

module Contrast
  module Agent
    module Assess
      module Policy
        module Propagator
          # Propagation that results in all the tags of the source being
          # applied to the target at the point of insertion. The target's
          # preexisting tags are shifted to account for this insertion.
          class Insert < Contrast::Agent::Assess::Policy::Propagator::Base
            class << self
              # For the source, append its tags to the target.
              # Once the tag is applied, shift it to the location of the insert
              # Unlike additive propagation, this currently only supports one source
              # We assume that insert changes the preshift target
              #
              # @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode] the node responsible for the
              # propagation action required by this method.
              # @param preshift [Object] pre call state of the things.
              # @param target [Object] the object to which the source is being appended
              # @return [Object] the target with the tags applied
              def propagate propagation_node, preshift, target
                return unless (properties = Contrast::Agent::Assess::Tracker.properties!(target))

                source = find_source(propagation_node.sources[1], preshift)

                patcher_target = propagation_node.targets[0]
                preshift_target = case patcher_target
                                  when Contrast::Utils::ObjectShare::OBJECT_KEY
                                    preshift.object
                                  else
                                    # this is hardly reached b/c insert supports only one source
                                    # changed to second argument: string.insert[idx, string_to_insert]
                                    # previously was args[int] => produces exception
                                    preshift.args[1]
                                  end

                # Find the first difference between the source to which
                # we inserted and the result. That is the insertion
                # point on which all tags need to be adjusted
                # If the insertion point is the end of the string, preshift length is returned
                # https://stackoverflow.com/questions/31714522/find-the-first-differing-character-between-two-strings-in-ruby
                insert_point = (0...preshift_target.length).find do |i|
                  preshift_target[i] != target[i]
                end || preshift_target.length
                # Depending what's inserted, we might be wrong. For instance, inserting 'foo'
                # into 'asdfasdf' could result in 'asdfoofasdf'. we'd be off by one b/c of the 'f'
                insert_point = target.rindex(source, insert_point)
                overflow = insert_point...(insert_point + source.length)

                # handle shifting the inserted range
                properties.shift_tags([overflow])

                properties.copy_from(source, target, insert_point, propagation_node.untags)
                properties.cleanup_tags
              end
            end
          end
        end
      end
    end
  end
end