local fio = require('fio')
local t = require('luatest')
local treegen = require('test.treegen')
local justrun = require('test.justrun')

local g = t.group()

-- Find an item in an array table and return its index.
--
-- array_find('b', {'a', 'b', 'c'}) -> 2
-- array_find('x', {'a', 'b', 'c'}) -> nil
local function array_find(needle, array)
    for i, item in ipairs(array) do
        if item == needle then
            return i
        end
    end
    return nil
end

-- All Lua files produced by the test write a JSON object to
-- stdout. This object contains everything we want to know
-- about the context of the script execution.
--
-- First of all, we should ensure that given script was executed.
-- So we output a script file name.
--
-- Next, we should define what is passed to the script as `...`
-- (dots).
--
-- We should also inspect `arg` content to freeze certain
-- behavior.
--
-- Note: In order to serialize `arg` into JSON it is split into
-- `arg[-1]`, `arg[0]` and `arg[]`. The latter means an array of
-- all items from 1 to the end.
local SCRIPT_TEMPLATE = [[
print(require('json').encode({
    ['script'] = '<script>',
    ['...'] = {...},
    ['arg[-1]'] = arg[-1],
    ['arg[0]'] = arg[0],
    ['arg[]'] = setmetatable(arg, {__serialize = 'seq'}),
}))
]]

g.before_all(function(g)
    treegen.init(g)
    treegen.add_template(g, '^.*$', SCRIPT_TEMPLATE)
end)

g.after_all(function(g)
    treegen.clean(g)
end)

-- Generate output expected from tarantool running in given
-- directory with given scripts (generated by the SCRIPT_TEMPLATE
-- template) with given environment and given command line
-- arguments.
--
-- It is the test oracle.
local function expected_output(scripts, env, args)
    assert(type(scripts) == 'table')
    assert(type(env) == 'table')
    assert(type(args) == 'table')

    local script_args = table.copy(args)
    table.remove(script_args, 1)

    local res = {}

    -- TT_PRELOAD entries.
    for _, entry in ipairs(env['TT_PRELOAD']:split(';')) do
        local script
        if #entry == 0 then
            -- Ignore empty entries.
            goto continue
        elseif entry:endswith('.lua') then
            -- The entry is a script file.
            script = entry
        else
            -- The entry is a module to `require`.
            script = entry:gsub('%.', '/') .. '.lua'
            -- Handle the special case, when LUA_PATH has one
            -- entry. Adjust script name to one written into
            -- the script itself.
            if env['LUA_PATH'] ~= nil then
                local module_dir = env['LUA_PATH']:match('^(.-)/%?%.lua;;$')
                assert(module_dir ~= nil)
                assert(module_dir:find(';') == nil)
                script = fio.pathjoin(module_dir, script)
            end
        end

        -- A negative scenario: there is no file that corresponds
        -- the preload entry.
        --
        -- Don't check stdout/stderr (extra logic, more fragile
        -- test), just ensure that tarantool exits with an error.
        if not array_find(script, scripts) then
            return {
                exit_code = 1,
            }
        end

        table.insert(res, {
            ['script'] = script,
            ['...'] = {entry},
            ['arg[-1]'] = arg[-1],
            ['arg[0]'] = args[1],
            ['arg[]'] = script_args,
        })

        ::continue::
    end

    -- The main script.
    table.insert(res, {
        ['script'] = args[1],
        ['...'] = script_args,
        ['arg[-1]'] = arg[-1],
        ['arg[0]'] = args[1],
        ['arg[]'] = script_args,
    })

    return {
        exit_code = 0,
        stdout = res,
    }
end

-- Define a couple of situations to verify. Each situation is
-- defined as a working directory description (a list of scripts)
-- and parameters to run tarantool in this working directory
-- (environment and command line arguments).
--
-- The test oracle is defined by the `expected_output()`
-- function.
for _, case in ipairs({
    -- Successful cases.
    {
        'test_script',
        scripts = {'foo.lua', 'main.lua'},
        env = {TT_PRELOAD = 'foo.lua'},
        args = {'main.lua'},
    },
    {
        'test_script_nested',
        scripts = {'foo/bar.lua', 'main.lua'},
        env = {TT_PRELOAD = 'foo/bar.lua'},
        args = {'main.lua'},
    },
    {
        'test_args',
        scripts = {'foo.lua', 'main.lua'},
        env = {TT_PRELOAD = 'foo.lua'},
        args = {'main.lua', 'a', 'b', 'c'},
    },
    {
        'test_module',
        scripts = {'foo.lua', 'main.lua'},
        env = {TT_PRELOAD = 'foo'},
        args = {'main.lua'},
    },
    {
        'test_module_nested',
        scripts = {'foo/bar.lua', 'main.lua'},
        env = {TT_PRELOAD = 'foo.bar'},
        args = {'main.lua'},
    },
    {
        'test_module_args',
        scripts = {'foo.lua', 'main.lua'},
        env = {TT_PRELOAD = 'foo'},
        args = {'main.lua', 'a', 'b', 'c'},
    },
    {
        'test_lua_path',
        scripts = {'foo/bar.lua', 'main.lua'},
        env = {
            LUA_PATH = 'foo/?.lua;;',
            TT_PRELOAD = 'bar',
        },
        args = {'main.lua'},
    },
    {
        'test_semicolon_separated',
        scripts = {'foo.lua', 'bar.lua', 'main.lua'},
        env = {TT_PRELOAD = 'foo.lua;bar'},
        args = {'main.lua'},
    },
    {
        'test_leading_semicolon',
        scripts = {'foo.lua', 'bar.lua', 'main.lua'},
        env = {TT_PRELOAD = ';foo.lua;bar'},
        args = {'main.lua'},
    },
    {
        'test_trailing_semicolon',
        scripts = {'foo.lua', 'bar.lua', 'main.lua'},
        env = {TT_PRELOAD = 'foo.lua;bar;'},
        args = {'main.lua'},
    },
    {
        'test_duplicated_semicolon',
        scripts = {'foo.lua', 'bar.lua', 'main.lua'},
        env = {TT_PRELOAD = 'foo.lua;;bar'},
        args = {'main.lua'},
    },
    {
        'test_only_semicolons',
        scripts = {'main.lua'},
        env = {TT_PRELOAD = ';;;'},
        args = {'main.lua'},
    },
    -- Negative cases.
    {
        'test_nonexisting_script',
        scripts = {'foo.lua', 'main.lua'},
        env = {TT_PRELOAD = 'foo.lua;pigeons_milk.lua'},
        args = {'main.lua'},
    },
    {
        'test_nonexisting_module',
        scripts = {'foo.lua', 'main.lua'},
        env = {TT_PRELOAD = 'foo;pigeons_milk'},
        args = {'main.lua'},
    },
    {
        'test_hyphen_is_not_accepted',
        scripts = {'main.lua'},
        env = {TT_PRELOAD = '-'},
        args = {'main.lua'},
    },
}) do
    g[case[1]] = function(g)
        local dir = treegen.prepare_directory(g, case.scripts)
        local res = justrun.tarantool(dir, case.env, case.args)
        local exp = expected_output(case.scripts, case.env, case.args)
        t.assert_equals(res, exp)
    end
end
