module Jekyll
  module Hooks
    DEFAULT_PRIORITY = 20

    # compatibility layer for octopress-hooks users
    PRIORITY_MAP = {
      :low    => 10,
      :normal => 20,
      :high   => 30
    }.freeze

    # initial empty hooks
    @registry = {
      :site      => {
        :after_init  => [],
        :after_reset => [],
        :post_read   => [],
        :pre_render  => [],
        :post_render => [],
        :post_write  => []
      },
      :pages     => {
        :post_init   => [],
        :pre_render  => [],
        :post_render => [],
        :post_write  => []
      },
      :posts     => {
        :post_init   => [],
        :pre_render  => [],
        :post_render => [],
        :post_write  => []
      },
      :documents => {
        :post_init   => [],
        :pre_render  => [],
        :post_render => [],
        :post_write  => []
      }
    }

    # map of all hooks and their priorities
    @hook_priority = {}

    NotAvailable = Class.new(RuntimeError)
    Uncallable = Class.new(RuntimeError)

    # register hook(s) to be called later, public API
    def self.register(owners, event, priority: DEFAULT_PRIORITY, &block)
      Array(owners).each do |owner|
        register_one(owner, event, priority_value(priority), &block)
      end
    end

    # Ensure the priority is a Fixnum
    def self.priority_value(priority)
      return priority if priority.is_a?(Integer)
      PRIORITY_MAP[priority] || DEFAULT_PRIORITY
    end

    # register a single hook to be called later, internal API
    def self.register_one(owner, event, priority, &block)
      @registry[owner] ||={
        :post_init   => [],
        :pre_render  => [],
        :post_render => [],
        :post_write  => []
      }

      unless @registry[owner][event]
        raise NotAvailable, "Invalid hook. #{owner} supports only the " \
          "following hooks #{@registry[owner].keys.inspect}"
      end

      unless block.respond_to? :call
        raise Uncallable, "Hooks must respond to :call"
      end

      insert_hook owner, event, priority, &block
    end

    def self.insert_hook(owner, event, priority, &block)
      @hook_priority[block] = [-priority, @hook_priority.size]
      @registry[owner][event] << block
    end

    # interface for Jekyll core components to trigger hooks
    def self.trigger(owner, event, *args)
      # proceed only if there are hooks to call
      return unless @registry[owner]
      return unless @registry[owner][event]

      # hooks to call for this owner and event
      hooks = @registry[owner][event]

      # sort and call hooks according to priority and load order
      hooks.sort_by { |h| @hook_priority[h] }.each do |hook|
        hook.call(*args)
      end
    end
  end
end
