"use strict";

const test     = require('tape');

const lua     = require('../src/lua.js');
const lauxlib = require('../src/lauxlib.js');
const lualib  = require('../src/lualib.js');


test('debug.sethook', function (t) {
    let luaCode = `
        local result = ""

        debug.sethook(function (event)
            result = result .. event .. " "
        end, "crl", 1)

        local l = function() end

        l()
        l()
        l()

        return result
    `, L;

    t.plan(3);

    t.doesNotThrow(function () {

        L = lauxlib.luaL_newstate();

        lualib.luaL_openlibs(L);

        lauxlib.luaL_loadstring(L, lua.to_luastring(luaCode));

    }, "Lua program loaded without error");

    t.doesNotThrow(function () {

        lua.lua_call(L, 0, -1);

    }, "Lua program ran without error");

    t.strictEqual(
        lua.lua_tojsstring(L, -1),
        "return count line count line count line call count line return count line count line call count line return count line count line call count line return count line ",
        "Correct element(s) on the stack"
    );
});


test('debug.gethook', function (t) {
    let luaCode = `
        local result = ""

        debug.sethook(function (event)
            result = result .. event .. " "
        end, "crl", 1)

        local l = function() end

        l()
        l()
        l()

        return debug.gethook()
    `, L;

    t.plan(5);

    t.doesNotThrow(function () {

        L = lauxlib.luaL_newstate();

        lualib.luaL_openlibs(L);

        lauxlib.luaL_loadstring(L, lua.to_luastring(luaCode));

    }, "Lua program loaded without error");

    t.doesNotThrow(function () {

        lua.lua_call(L, 0, -1);

    }, "Lua program ran without error");

    t.deepEqual(
        lua.lua_typename(L, lua.lua_type(L, -3)),
        lua.to_luastring("function"),
        "Correct element(s) on the stack"
    );

    t.deepEqual(
        lua.lua_tojsstring(L, -2),
        "crl",
        "Correct element(s) on the stack"
    );

    t.deepEqual(
        lua.lua_tointeger(L, -1),
        1,
        "Correct element(s) on the stack"
    );
});


test('debug.getlocal', function (t) {
    let luaCode = `
        local alocal = "alocal"
        local another = "another"

        local result = ""

        local l = function()
            local infunction = "infunction"
            local anotherin = "anotherin"
            result = table.concat(table.pack(debug.getlocal(2, 1)), " ")
                .. table.concat(table.pack(debug.getlocal(2, 2)), " ")
                .. table.concat(table.pack(debug.getlocal(1, 1)), " ")
                .. table.concat(table.pack(debug.getlocal(1, 2)), " ")
        end

        l()

        return result
    `, L;

    t.plan(3);

    t.doesNotThrow(function () {

        L = lauxlib.luaL_newstate();

        lualib.luaL_openlibs(L);

        lauxlib.luaL_loadstring(L, lua.to_luastring(luaCode));

    }, "Lua program loaded without error");

    t.doesNotThrow(function () {

        lua.lua_call(L, 0, -1);

    }, "Lua program ran without error");

    t.strictEqual(
        lua.lua_tojsstring(L, -1),
        "alocal alocalanother anotherinfunction infunctionanotherin anotherin",
        "Correct element(s) on the stack"
    );
});

test('debug.setlocal', function (t) {
    let luaCode = `
        local alocal = "alocal"
        local another = "another"

        local l = function()
            local infunction = "infunction"
            local anotherin = "anotherin"

            debug.setlocal(2, 1, 1)
            debug.setlocal(2, 2, 2)
            debug.setlocal(1, 1, 3)
            debug.setlocal(1, 2, 4)

            return infunction, anotherin
        end

        local a, b = l()

        return alocal, another, a, b
    `, L;

    t.plan(6);

    t.doesNotThrow(function () {

        L = lauxlib.luaL_newstate();

        lualib.luaL_openlibs(L);

        lauxlib.luaL_loadstring(L, lua.to_luastring(luaCode));

    }, "Lua program loaded without error");

    t.doesNotThrow(function () {

        lua.lua_call(L, 0, -1);

    }, "Lua program ran without error");

    t.strictEqual(
        lua.lua_tointeger(L, -4),
        1,
        "Correct element(s) on the stack"
    );

    t.strictEqual(
        lua.lua_tointeger(L, -3),
        2,
        "Correct element(s) on the stack"
    );

    t.strictEqual(
        lua.lua_tointeger(L, -2),
        3,
        "Correct element(s) on the stack"
    );

    t.strictEqual(
        lua.lua_tointeger(L, -1),
        4,
        "Correct element(s) on the stack"
    );
});

test('debug.upvalueid', function (t) {
    let luaCode = `
        local upvalue = "upvalue"

        local l = function()
            return upvalue
        end

        return debug.upvalueid(l, 1)
    `, L;

    t.plan(3);

    t.doesNotThrow(function () {

        L = lauxlib.luaL_newstate();

        lualib.luaL_openlibs(L);

        lauxlib.luaL_loadstring(L, lua.to_luastring(luaCode));

    }, "Lua program loaded without error");

    t.doesNotThrow(function () {

        lua.lua_call(L, 0, -1);

    }, "Lua program ran without error");

    t.ok(
        lua.lua_touserdata(L, -1),
        "Correct element(s) on the stack"
    );
});


test('debug.upvaluejoin', function (t) {
    let luaCode = `
        local upvalue1 = "upvalue1"
        local upvalue2 = "upvalue2"

        local l1 = function()
            return upvalue1
        end

        local l2 = function()
            return upvalue2
        end

        debug.upvaluejoin(l1, 1, l2, 1)

        return l1()
    `, L;

    t.plan(3);

    t.doesNotThrow(function () {

        L = lauxlib.luaL_newstate();

        lualib.luaL_openlibs(L);

        lauxlib.luaL_loadstring(L, lua.to_luastring(luaCode));

    }, "Lua program loaded without error");

    t.doesNotThrow(function () {

        lua.lua_call(L, 0, -1);

    }, "Lua program ran without error");

    t.strictEqual(
        lua.lua_tojsstring(L, -1),
        "upvalue2",
        "Correct element(s) on the stack"
    );
});


test('debug.traceback (with a global)', function (t) {
    let luaCode = `
        local trace

        rec = function(n)
            n = n or 0
            if n < 10 then
                rec(n + 1)
            else
                trace = debug.traceback()
            end
        end

        rec()

        return trace
    `, L;

    t.plan(3);

    t.doesNotThrow(function () {

        L = lauxlib.luaL_newstate();

        lualib.luaL_openlibs(L);

        luaCode = lua.to_luastring(luaCode);
        lauxlib.luaL_loadbuffer(L, luaCode, luaCode.length, lua.to_luastring("traceback-test"));

    }, "Lua program loaded without error");

    t.doesNotThrow(function () {

        lua.lua_call(L, 0, -1);

    }, "Lua program ran without error");

    t.strictEqual(
        lua.lua_tojsstring(L, -1), `stack traceback:
\t[string "traceback-test"]:9: in function 'rec'
\t[string "traceback-test"]:7: in function 'rec'
\t[string "traceback-test"]:7: in function 'rec'
\t[string "traceback-test"]:7: in function 'rec'
\t[string "traceback-test"]:7: in function 'rec'
\t[string "traceback-test"]:7: in function 'rec'
\t[string "traceback-test"]:7: in function 'rec'
\t[string "traceback-test"]:7: in function 'rec'
\t[string "traceback-test"]:7: in function 'rec'
\t[string "traceback-test"]:7: in function 'rec'
\t[string "traceback-test"]:7: in function 'rec'
\t[string "traceback-test"]:13: in main chunk`,
        "Correct element(s) on the stack"
    );
});


test('debug.traceback (with a upvalue)', function (t) {
    let luaCode = `
        local trace
        local rec

        rec = function(n)
            n = n or 0
            if n < 10 then
                rec(n + 1)
            else
                trace = debug.traceback()
            end
        end

        rec()

        return trace
    `, L;

    t.plan(3);

    t.doesNotThrow(function () {

        L = lauxlib.luaL_newstate();

        lualib.luaL_openlibs(L);

        luaCode = lua.to_luastring(luaCode);
        lauxlib.luaL_loadbuffer(L, luaCode, luaCode.length, lua.to_luastring("traceback-test"));

    }, "Lua program loaded without error");

    t.doesNotThrow(function () {

        lua.lua_call(L, 0, -1);

    }, "Lua program ran without error");

    t.strictEqual(
        lua.lua_tojsstring(L, -1), `stack traceback:
\t[string "traceback-test"]:10: in upvalue 'rec'
\t[string "traceback-test"]:8: in upvalue 'rec'
\t[string "traceback-test"]:8: in upvalue 'rec'
\t[string "traceback-test"]:8: in upvalue 'rec'
\t[string "traceback-test"]:8: in upvalue 'rec'
\t[string "traceback-test"]:8: in upvalue 'rec'
\t[string "traceback-test"]:8: in upvalue 'rec'
\t[string "traceback-test"]:8: in upvalue 'rec'
\t[string "traceback-test"]:8: in upvalue 'rec'
\t[string "traceback-test"]:8: in upvalue 'rec'
\t[string "traceback-test"]:8: in local 'rec'
\t[string "traceback-test"]:14: in main chunk`,
        "Correct element(s) on the stack"
    );
});

test('debug.getinfo', function (t) {
    let luaCode = `
        local alocal = function(p1, p2) end
        global = function() return alocal end

        local d1 = debug.getinfo(alocal)
        local d2 = debug.getinfo(global)

        print(d1.short_src, d1.nups, d1.what, d1.nparams,
              d2.short_src, d2.nups, d2.what, d2.nparams)

        return d1.short_src, d1.nups, d1.what, d1.nparams,
               d2.short_src, d2.nups, d2.what, d2.nparams
    `, L;

    t.plan(10);

    t.doesNotThrow(function () {

        L = lauxlib.luaL_newstate();

        lualib.luaL_openlibs(L);

        luaCode = lua.to_luastring(luaCode);
        lauxlib.luaL_loadbuffer(L, luaCode, luaCode.length, lua.to_luastring("getinfo-test"));

    }, "Lua program loaded without error");

    t.doesNotThrow(function () {

        lua.lua_call(L, 0, -1);

    }, "Lua program ran without error");

    t.strictEqual(
        lua.lua_tojsstring(L, -8),
        `[string "getinfo-test"]`,
        "Correct element(s) on the stack"
    );

    t.strictEqual(
        lua.lua_tointeger(L, -7),
        0,
        "Correct element(s) on the stack"
    );

    t.strictEqual(
        lua.lua_tojsstring(L, -6),
        `Lua`,
        "Correct element(s) on the stack"
    );

    t.strictEqual(
        lua.lua_tointeger(L, -5),
        2,
        "Correct element(s) on the stack"
    );

    t.strictEqual(
        lua.lua_tojsstring(L, -4),
        `[string "getinfo-test"]`,
        "Correct element(s) on the stack"
    );

    t.strictEqual(
        lua.lua_tointeger(L, -3),
        1,
        "Correct element(s) on the stack"
    );

    t.strictEqual(
        lua.lua_tojsstring(L, -2),
        `Lua`,
        "Correct element(s) on the stack"
    );

    t.strictEqual(
        lua.lua_tointeger(L, -1),
        0,
        "Correct element(s) on the stack"
    );
});