local split = require("pl.utils").split
local pretty = require("pl.pretty").write
local strip = require("pl.stringx").strip
local lines = require("pl.stringx").splitlines
local _execex = require("pl.utils").executeex
local _exec = require("pl.utils").execute
local directories = require("pl.dir").getdirectories
local writefile = require("pl.utils").writefile
local readfile = require("pl.utils").readfile
local is_dir = require("pl.path").isdir
local is_file = require("pl.path").isfile

local CUSTOM_TEMPLATE="/custom_nginx.conf"

io.stdout:setvbuf("no")
io.stderr:setvbuf("no")


local function stderr(...)
  io.stderr:write(...)
  io.stderr:write("\n")
end


local function stdout(...)
  io.stdout:write(...)
  io.stdout:write("\n")
end


local function fail(msg)
  stderr(msg)
  os.exit(1)
end


local function header(msg)
  local fill1 = math.floor((80 - 2 - #msg)/2)
  local fill2 = 80 - 2 - #msg - fill1
  stdout(
    ("*"):rep(80).."\n"..
    "*"..(" "):rep(fill1)..msg..(" "):rep(fill2).."*\n"..
    ("*"):rep(80)
  )
end


local platforms = {
  {
    check = "apk -V",         -- check for alpine
    commands = {              -- run before anything else in build container
      "apk update",
      "apk add git",
      "apk add wget",
      "apk add zip",
      "apk add gcc",
      "apk add musl-dev",
    },
  }, {
    check = "yum --version",  -- check for rhel
    commands = {              -- run before anything else in build container
      "yum -y install git",
      "yum -y install unzip",
      "yum -y install zip",
      "yum -y install gcc gcc-c++ make",
    },
  }, {
    check = "apt -v",         -- check for Ubuntu
    commands = {              -- run before anything else in build container
      "apt update",
      "apt install -y zip",
      "apt install -y wget",
      "apt install -y build-essential",
    },
  },
}


local execex = function(cmd, ...)
  stdout("\027[32m", "[packer exec] ", cmd, "\027[0m")
  return _execex(cmd, ...)
end


local exec = function(cmd, ...)
  stdout("\027[32m", "[packer exec] ", cmd, "\027[0m")
  return _exec(cmd, ...)
end


local function prep_platform()
  for _, platform in ipairs(platforms) do
    local ok = exec(platform.check)
    if not ok then
      stdout(("platform test '%s' was negative"):format(platform.check))
    else
      stdout(("platform test '%s' was positive"):format(platform.check))
      for _, cmd in ipairs(platform.commands) do
        stdout(cmd)
        ok = exec(cmd)
        if not ok then
          fail(("failed executing '%s'"):format(cmd))
        end
      end
      return true
    end
  end
  stderr("WARNING: no platform match!")
end


local function is_empty_file(filename)
  local t = readfile(filename)
  if t then
    if t:gsub("\n", ""):gsub("\t", ""):gsub(" ","") == "" then
      return true
    end
  end
  return false
end


local function get_args()
  if not arg or
     not arg[1] or
     arg[1] == "--" and not arg[2] then
    -- no args, but maybe a custom config file?

    if is_empty_file(CUSTOM_TEMPLATE) then
      fail("no arguments to parse, commandline: " .. pretty(arg or {}))
    else
      stdout("no plugins specified, but a custom template exists")
      return
    end
  end

  local list = {}
  for i = 1, #arg do
    if arg[i] and arg[i] ~= "--" then
      local sp = split(arg[i], ",")
      for n = 1, #sp do
        local rock = strip(sp[n])
        if rock ~= "" then
          table.insert(list, rock)
        end
      end
    end
  end

  if #list == 0 then
    if is_empty_file(CUSTOM_TEMPLATE) then
      fail("no arguments to parse, commandline: " .. pretty(arg))
    else
      stdout("no plugins specified, but a custom template exists")
    end
  end

  stdout("rocks to install: " .. pretty(list))
  return list
end


local function get_plugins()
  local plugins = {}
  local cnt = 0

  for i = 1, 2 do
    local pattern, paths, extension
    if i == 1 then
      pattern = "%?%.lua$"
      extension = ".lua"
      paths = split(package.path, ";")
    else
      pattern = "%?%.so$"
      extension = ".so"
      paths = split(package.cpath, ";")
    end

    for _, path in ipairs(paths) do
      path = path:gsub(pattern, "kong/plugins/")
      if is_dir(path) then
        for _, dir in ipairs(directories(path)) do
          local plugin_name = dir:sub(#path + 1, -1)
          if is_file(dir .. "/handler" .. extension) then
            plugins[plugin_name] = true
            cnt = cnt + 1
          end
        end
      end
    end
  end

  stdout("Found ", cnt, " plugins installed")
  return plugins
end


local function get_rocks()
  local cmd = "luarocks list --tree=system --porcelain"
  local ok, _, sout, serr = execex(cmd)
  if not ok then
    fail(("failed to retrieve list of installed rocks: '%s' failed with\n%s\n%s"):format(
        cmd, sout, serr))
  end

  local rocks = {}
  local cnt = 0
  for _, rock in ipairs(lines(sout)) do
    cnt = cnt + 1
    local name, spec = rock:match("^(.-)\t(.-)\t")
    local rock_id = name.."-"..spec
    rocks[rock_id] = { name = name, spec = spec }
  end
  stdout("Found ", cnt, " rocks installed")
  return rocks
end


local function install_plugins(plugins, lr_flag)
  local cmd = "luarocks install --tree=system %s " .. lr_flag
  for _, rock in ipairs(plugins) do
    stdout(cmd:format(rock))

    local ok = exec(cmd:format(rock))
    if not ok then
      fail(("failed installing rock: '%s' failed"):format(cmd:format(rock)))
    end

    stdout("installed: "..rock)
    exec("luarocks show "..rock)
  end
end


local function check_custom_template()
  if is_empty_file(CUSTOM_TEMPLATE) then
    -- it's the empty_file, delete it
    os.remove(CUSTOM_TEMPLATE)
    stdout("No custom template found")
    return
  end
  stdout("Found a custom template")
end


local function start_rocks_server()
  if is_empty_file("/rocks-server") then
    stdout("No custom rocks found, using public luarocks.org as server")
    return ""
  end
  assert(exec("luarocks-admin make_manifest /rocks-server"))
  stdout("Local LuaRocks server manifest created")
  assert(exec("mkdir /nginx"))
  assert(exec("mkdir /nginx/logs"))
  assert(writefile("/nginx/nginx.conf", [[
events {
}

http {
    server {
        listen 127.0.0.1:8080;

        location / {
            root /rocks-server;
        }
    }
}
]]))
  assert(exec("touch /nginx/logs/error.log"))
  assert(exec("/usr/local/openresty/nginx/sbin/nginx " ..
              "-c /nginx/nginx.conf " ..
              "-p /nginx"))
  stdout("Nginx started as local LuaRocks server")
  stdout("List of locally available rocks:")
  assert(exec("luarocks search --all --porcelain --only-server=http://localhost:8080"))
  return " --only-server=http://localhost:8080 "
end


-- **********************************************************
-- Do the actual work
-- **********************************************************
header("Set up platform")
prep_platform()

header("Set up LuaRocks server")
local lr_flag = start_rocks_server()

header("Get arguments")
local rocks = get_args()


header("Get existing rocks")
local pre_installed_rocks = get_rocks()


header("Get existing plugin list")
local pre_installed_plugins = get_plugins()


header("Getting custom template")
check_custom_template()


header("Install the requested plugins")
install_plugins(rocks, lr_flag)


header("Get post-install rocks list and get the delta")
local added_rocks
do
  local post_installed_rocks = get_rocks()
  for k in pairs(pre_installed_rocks) do
    if post_installed_rocks[k] then
      post_installed_rocks[k] = nil  -- remove the ones we already had
    end
  end
  added_rocks = post_installed_rocks
end
if (not next(added_rocks)) then
  if is_empty_file(CUSTOM_TEMPLATE) then
    fail("no additional rocks were added, nor a custom template specified")
  end
  stdout("No rocks were added")
else
  for k in pairs(added_rocks) do
    stdout("added rock: "..k)
  end
end


header("Get post-install plugin list and get the delta")
local plugins = {}
for plugin_name in pairs(get_plugins()) do
  if not pre_installed_plugins[plugin_name] then
    table.insert(plugins, plugin_name)
    stdout("added plugin: "..plugin_name)
  end
end
if not next(plugins) then
  stdout("No plugins were added")
end


header("Write new entry-point script")
assert(exec("mv /docker-entrypoint.sh /old-entrypoint.sh"))
local entrypoint = [=[
#!/bin/sh
set -e

if [ "$KONG_PLUGINS" = "" ]; then
  KONG_PLUGINS="bundled"
fi
# replace 'bundled' with the new set, including the custom ones
export KONG_PLUGINS=$(echo ",$KONG_PLUGINS," | sed "s/,bundled,/,bundled,%s,/" | sed 's/^,//' | sed 's/,$//')

# prefix the custom template option, since the last one on the command line
# wins, so the user can still override this template
INITIAL="$1 $2"
if [ -f /custom_nginx.conf ]; then
  # only for these commands support "--nginx-conf"
  echo 1: $INITIAL
  if [ "$INITIAL" = "kong prepare" ] || \
     [ "$INITIAL" = "kong reload"  ] || \
     [ "$INITIAL" = "kong restart" ] || \
     [ "$INITIAL" = "kong start"   ] ; then
    INITIAL="$1 $2 --nginx-conf=/custom_nginx.conf"
  fi
fi
# shift 1 by 1; if there is only 1 arg, then "shift 2" fails
if [ ! "$1" = "" ]; then
  shift
fi
if [ ! "$1" = "" ]; then
  shift
fi

exec /old-entrypoint.sh $INITIAL "$@"
]=]
entrypoint = entrypoint:format(table.concat(plugins, ","))
assert(writefile("/docker-entrypoint.sh", entrypoint))
assert(exec("chmod +x /docker-entrypoint.sh"))
stdout(entrypoint)


header("Completed building plugins, rocks and/or template")

