module Celluloid
  class Actor
    # Handle high-priority system event messages
    def handle_system_event(event)
      if handler = SystemEvent.handle(event.class)
        send(handler, event)
      else
        Internals::Logger.debug "Discarded message (unhandled): #{message}"
      end
    end
  end
  # High-priority internal system events
  class SystemEvent
    class << self
      @@system_events = {}
      def handle(type)
        @@system_events[type]
      end

      def handler(&block)
        raise ArgumentError, "SystemEvent handlers must be defined with a block." unless block
        method = begin
          handler = name
                    .split("::").last
                    .gsub(/([A-Z]+)([A-Z][a-z])/, "\1_\2")
                    .gsub(/([a-z\d])([A-Z])/, "\1_\2")
                    .tr("-", "_")
                    .downcase
          :"handle_#{handler}"
        end
        Actor.send(:define_method, method, &block)
        @@system_events[self] = method
      end
    end

    class LinkingEvent < SystemEvent
      # Shared initializer for LinkingRequest and LinkingResponse
      def initialize(actor, type)
        @actor = actor
        @type = type.to_sym
        raise ArgumentError, "type must be link or unlink" unless %i[link unlink].include?(@type)
      end
    end
  end

  # Request to link with another actor
  class LinkingRequest < SystemEvent::LinkingEvent
    attr_reader :actor, :type

    handler do |event|
      event.process(links)
    end

    def process(links)
      case type
      when :link   then links << actor
      when :unlink then links.delete actor
      end

      actor.mailbox << LinkingResponse.new(Actor.current, type)
    end
  end

  # Response to a link request
  class LinkingResponse < SystemEvent::LinkingEvent
    attr_reader :actor, :type
  end

  # An actor has exited for the given reason
  class ExitEvent < SystemEvent
    attr_reader :actor, :reason

    handler do |event|
      @links.delete event.actor
      @exit_handler.call(event)
    end

    def initialize(actor, reason = nil)
      @actor = actor
      @reason = reason
    end
  end

  # Name an actor at the time it's registered
  class NamingRequest < SystemEvent
    attr_reader :name

    handler do |event|
      @name = event.name

      # !!! DO NOT INTRODUCE ADDITIONAL GLOBAL VARIABLES !!!
      # rubocop:disable Style/GlobalVars
      Celluloid::Probe.actor_named(self) if $CELLULOID_MONITORING
      # rubocop:enable Style/GlobalVars
    end

    def initialize(name)
      @name = name
    end
  end

  # Request for an actor to terminate
  class TerminationRequest < SystemEvent
    handler do |_event|
      terminate
    end
  end

  # Signal a condition
  class SignalConditionRequest < SystemEvent
    def initialize(task, value)
      @task = task
      @value = value
    end
    attr_reader :task, :value

    handler(&:call)

    def call
      @task.resume(@value)
    end
  end
end
