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

require 'contrast/agent/patching/policy/method_policy_extend'

module Contrast
  module Agent
    module Patching
      module Policy
        # This class is used to map each method to the trigger node that applies to it
        class MethodPolicy
          extend Contrast::Agent::Patching::Policy::MethodPolicyExtend
          attr_reader   :deadzone_node, :inventory_node, :propagation_node, :protect_node, :trigger_node
          attr_accessor :source_node, :method_name, :method_visibility, :instance_method

          # Initialize new method policy with:
          #
          # @param method_policy [Hash]
          #  {
          #   source_node       [ Contrast::Agent::Assess::Policy::SourceNode      ]
          #   propagation_node  [ Contrast::Agent::Assess::Policy::PropagationNode ]
          #   trigger_node      [ Contrast::Agent::Assess::Policy::TriggerNode     ]
          #   inventory_node    [ Contrast::Agent::Inventory::Policy::TriggerNode   ]
          #   protect_node      [ Contrast::Agent::Protect::Policy::TriggerNode     ]
          #   deadzone_node     [ Contrast::Agent::Deadzone::Policy::DeadzoneNode   ]
          #   method_name       [ Symbol                                            ]
          #                       the name of the method for this policy
          #   method_visibility [ Symbol                                            ]
          #                       Public, Private Protected
          #   instance_method   [ Boolean                                           ]
          #                       true if this method is an instance method
          #  }
          def initialize method_policy
            @method_name = method_policy[:method_name]
            @method_visibility = method_policy[:method_visibility]
            @instance_method = method_policy[:instance_method]
            @source_node = method_policy[:source_node]
            @propagation_node = method_policy[:propagation_node]
            @trigger_node = method_policy[:trigger_node]
            @inventory_node = method_policy[:inventory_node]
            @protect_node = method_policy[:protect_node]
            @deadzone_node = method_policy[:deadzone_node]
          end

          def private_method?
            method_visibility == :private
          end

          def empty?
            nodes.none?
          end

          def scopes_to_enter
            @_scopes_to_enter ||= method_scopes
          end

          def scopes_to_exit
            @_scopes_to_exit ||= method_scopes.reverse
          end

          def requires_custom_patch?
            !!@trigger_node&.custom_patch?
          end

          private

          def nodes
            @_nodes ||= [
              source_node, propagation_node, trigger_node, inventory_node, protect_node,
              deadzone_node
            ].compact
          end

          def method_scopes
            # Implicitly, the scope precedence is the node order
            # defined by #nodes.
            @_method_scopes ||= nodes.flat_map(&:method_scope).tap(&:compact!).tap(&:uniq!)
          end
        end
      end
    end
  end
end