local proc_mgmt = require "kong.runloop.plugin_servers.process"
local plugin = require "kong.runloop.plugin_servers.plugin"

local pairs = pairs
local kong = kong

-- module cache of loaded external plugins
-- XXX historically, this list of plugins has not been invalidated;
-- however, as plugin servers can be managed externally, users may also
-- change and restart the plugin server, potentially with new configurations
-- this needs to be improved -- docs and code hardening
local loaded_plugins

local function load_external_plugins()
  if loaded_plugins then
    return true
  end

  loaded_plugins = {}

  local kong_config = kong.configuration

  local plugins_info, err = proc_mgmt.load_external_plugins_info(kong_config)
  if not plugins_info then
    return nil, "failed loading external plugins: " .. err
  end

  for plugin_name, plugin_info in pairs(plugins_info) do
    local plugin = plugin.new(plugin_info)
    loaded_plugins[plugin_name] = plugin
  end

  return loaded_plugins
end

local function get_plugin(plugin_name)
  assert(load_external_plugins())

  return loaded_plugins[plugin_name]
end

local function load_plugin(plugin_name)
  local plugin = get_plugin(plugin_name)
  if plugin and plugin.PRIORITY then
    return true, plugin
  end

  return false, "no plugin found"
end

local function load_schema(plugin_name)
  local plugin = get_plugin(plugin_name)
  if plugin and plugin.PRIORITY then
    return true, plugin.schema
  end

  return false, "no plugin found"
end

local function start()
  -- in case plugin server restarts, all workers need to update their defs
  kong.worker_events.register(function (data)
    plugin.reset_instances_for_plugin(data.plugin_name)
  end, "plugin_server", "reset_instances")

  return proc_mgmt.start_pluginservers()
end

local function stop()
  return proc_mgmt.stop_pluginservers()
end


--
-- This modules sole responsibility is to
-- manage external plugins: starting/stopping plugins servers,
-- and return plugins info (such as schema and their loaded representations)
--
-- The general initialization flow is:
-- - kong.init: calls start and stop to start/stop external plugins servers
-- - kong.db.schema.plugin_loader: calls load_schema to get an external plugin schema
-- - kong.db.dao.plugins: calls load_plugin to get the expected representation of a plugin
--                        (phase handlers, priority, etc)
--
-- Internal flow:
-- .plugin_servers.init: loads all external plugins, by calling .plugin_servers.process and .plugin_servers.plugin
--   .plugin_servers.process: queries external plugins info with the command specified in _query_cmd properties
--   .plugin_servers.plugin: with info obtained as described above, .plugin:new returns a kong-compatible representation
--                           of an external plugin, with phase handlers, PRIORITY, and wrappers to the PDK. Calls
--                           .plugin_servers.rpc to create an RPC through which Kong communicates with the plugin process
--     .plugin_servers.rpc: based on info contained in the plugin (protocol field), creates the correct RPC for the
--                           given external plugin
--       .plugin_servers.rpc.pb_rpc: protobuf rpc implementation - used by Golang
--       .plugin_servers.rpc.mp.rpc: messagepack rpc implementation - used by JS and Python
-- .plugin_servers.init: calls .plugin_servers.process to start external plugin servers
--   .plugin_servers.process: optionally starts all external plugin servers (if a _start_cmd is found)
--      uses the resty pipe API to manage the external plugin process
--

return {
  start = start,
  stop = stop,
  load_schema = load_schema,
  load_plugin = load_plugin,
}
