local SUITE = require 'TestSuite' 'pretty'
SUITE:setEnvironment{
    format  = require('pretty')
}

local ASSERT_ERROR_APPROX = [[
Approximate strings not similar enough:
    Should match: %s
    Gotten:       %s
]]

--------------------------------------------------------------------------------

-- Compat
if not loadstring then  loadstring = load  end                        -- Lua 5.3 compat
local HAS_ADV_GETLOCAL  =  not not debug.getinfo(1, 'u').nparams      -- Lua 5.1 compat
local HAS_UNICODE_IDEN  =  not not loadstring 'local ϕ = 1; return ϕ' -- Lua 5.1 compat
local HAS_JIT_LIBRARY   =  type(rawget(_G, 'jit')) == 'table'         -- Non-LuaJIT compat
--

local function format_test (t)
    if t.longterm     then return  end
    if t.adv_getlocal and not HAS_ADV_GETLOCAL then return  end
    SUITE:addTest(t.name or t.expect, function ()
    	local actual_result    =  format(t.input, t.options)
        if not t.approx or type(actual_result) ~= 'string' then
            assert_equal(t.expect, actual_result)
        else
            if not actual_result:match(t.expect) then
                error(ASSERT_ERROR_APPROX:format(t.expect, actual_result))
            end
        end
    end, { line = debug.getinfo(2).currentline })
end

local function format_parsable_test (t)
    local stripped = t.text:match '^%s*(.-)%s*$'
    return format_test {
        name   = t.name,
        input  = loadstring('return '..stripped)(),
        expect = stripped
    }
end

--------------------------------------------------------------------------------
-- Primitive types

format_test {
    input  = nil,
    expect = 'nil',
}

format_test {
    input  = true,
    expect = 'true',
}

format_test {
    input  = false,
    expect = 'false',
}

--------------------------------------------------------------------------------
-- Userdata printing

-- TODO: Figure out a way to print userdata.
-- Maybe look into using the one available debug.getupvalue(pairs, 1)

--------------------------------------------------------------------------------
-- Thread printing

do
    local suspended_coroutine = coroutine.create(function () end)
    format_test {
        input  = suspended_coroutine,
        approx = true,
        expect = 'suspended coroutine: 0x%x+',
    }
end

do
    local dead_coroutine      = coroutine.create(function () end)
    coroutine.resume(dead_coroutine)
    format_test {
        input  = dead_coroutine,
        approx = true,
        expect = 'dead coroutine: 0x%x+',
    }
end

--------------------------------------------------------------------------------
-- Single-line tables

format_test {
    input  = {},
    expect = '{}',
}

format_test {
    input  = {1, 2, 3},
    expect = '{ 1, 2, 3 }',
}

format_test {
    input  = { 'Hello', 'World' },
    expect = '{ \'Hello\', \'World\' }',
}

format_test {
    input  = { a = 1, b = 2 },
    expect = '{ a = 1, b = 2 }',
}

format_test {
    input  = { __hello = true },
    expect = '{ __hello = true }',
}

format_test {
    input  = { [']]'] = true },
    expect = '{ [\']]\'] = true }',
}

format_test {
    input  = { ['and'] = true },
    expect = '{ [\'and\'] = true }',
}

format_test {
    input  = { [false] = false, [true] = true },
    expect = '{ [false] = false, [true] = true }',
}

format_test { -- Order does not matter
    input  = { b = 1, a = 2 },
    expect = '{ a = 2, b = 1 }',
}

format_test {  -- Can include empty tables
    input  = { {}, {}, {} },
    expect = '{ {}, {}, {} }',
}

format_test {  -- Can include very small tables
    input  = { {1}, {2}, {3} },
    expect = '{ { 1 }, { 2 }, { 3 } }',
}

--------------------------------------------------------------------------------
-- Multi-line tables

format_test {
    input  = { {1, 2, 3}, {4, 5, 6} },
    expect = '{ { 1, 2, 3 }, { 4, 5, 6 } }',
}

format_test {
    input  = { a = {1, 2, 3}, b = {4, 5, 6} },
    expect = '{ a = { 1, 2, 3 }, b = { 4, 5, 6 } }',
}

format_test {
    name = 'Unicode characters can be used as string keys in tables',
    input  = { ['a'] = 1, ['ψ'] = 2 },
    expect = '{ a = 1, ψ = 2 }',
}

format_test {
    input  = { [100] = 'Hi', [300] = 'Hello' },
    expect = '{ [100] = \'Hi\', [300] = \'Hello\' }',
}

format_test {
    input  = { 'Hi', [300] = 'Hello' },
    expect = '{ [1] = \'Hi\', [300] = \'Hello\' }',
}

format_test {
    input  = { { {} } },
    expect = '{ { {} } }',
}

format_test {
    input  = { [{ 1, 2 }] = { 2, 1 } },
    expect = '{ [{ 1, 2 }] = { 2, 1 } }',
}

format_test {
    input  = { { {1, 2}, {3, 4} }, {5, 6} },
    expect  = '{ { { 1, 2 }, { 3, 4 } }, { 5, 6 } }',
}

format_test {
    input  = { [{ {1,2}, {3,4} }] = 'Hello World' },
    expect = '{ [{...}] = \'Hello World\' }',
}

format_test {
    input  = { a = {1,2}, bcdefg = {3,4} },
    expect = '{ a = { 1, 2 }, bcdefg = { 3, 4 } }',
}

format_test {
    input  = { [true] = 1, [1] = false },
    expect = '{ [1] = false, [true] = 1 }',
}

format_test {
    -- Proper indent
    input  = { [1] = 1, ['whatever'] = false },
    expect = '{ [1] = 1, [\'whatever\'] = false }',
}

format_test {
    name = 'Proper alignment when using unicode characters as keys',
    input  = {
        ['djævle'] = 'dyr?',
        ['europa'] = 'måne',
        ['øå']     = 'en å på en ø?',
    },
    expect = '{\n    djævle = \'dyr?\',\n    europa = \'måne\',\n    øå     = \'en å på en ø?\'\n}',
}

format_test {
    name = 'Proper alignment when using zero-width unicode characters as keys',
    input  = {
        ['hello_world_1'] = 'dyr?',
        ['hello_world_2'] = 'dyr!',
        ['hello\204\133_wo\204\133rld_3'] = 'dyr.',
    },
    expect = '{\n    hello_world_1 = \'dyr?\',\n    hello_world_2 = \'dyr!\',\n    hello\204\133_wo\204\133rld_3 = \'dyr.\'\n}',
}

-- Depth Tests

format_test {
    name = 'Tables with a mix of values don\'t expand subtables',
    input  = {
        a = 'hello',
        b = {1, 2, 3},
        c = {1, 2, 3},
        d = {1, 2, 3},
    },
    expect = '{\n    a = \'hello\',\n    b = {...},\n    c = {...},\n    d = {...}\n}',
}

--------------------------------------------------------------------------------
-- Pattern specific table display.

format_test {
    name = 'Column style',
    input = {
		'hello',	'world',	'how',
		'is',	'it',	'going?',
		'Im',	'doing great',	'thanks',
		'that',	'was',	'what',
		'I',	'were',	'expecting'
    },
    expect = '{\n    \'hello\', \'world\',       \'how\',\n    \'is\',    \'it\',          \'going?\',\n    \'Im\',    \'doing great\', \'thanks\',\n    \'that\',  \'was\',         \'what\',\n    \'I\',     \'were\',        \'expecting\'\n}',
}

format_test {
    name = 'Column style with trailing last row',
    input = {
		'hello',	'world',	'how',
		'is',	'it',	'going?',
		'Im',	'doing great',	'thanks',
		'that',	'was',	'what',
		'I'
    },
    expect = '{\n    \'hello\', \'world\',       \'how\',\n    \'is\',    \'it\',          \'going?\',\n    \'Im\',    \'doing great\', \'thanks\',\n    \'that\',  \'was\',         \'what\',\n    \'I\'\n}',
}

format_parsable_test {
    name = 'Column style with aligned numbers',
    text = [[
{
    200, -522, 423, 516, 523, 126, 2912,
    523, -620,   0,   0,   0,  -5,    2,
     72,    6
}
]] }

format_test {
	name = 'Tabular style with strings left aligned',
    input  = {
		{ a = 'hello',  b = 'hi'    },
		{ a = 'hi',	    b = 'hello' },
		{ a = 'hi',	    b = 'hi'    },
	},
    expect = '{\n    { a = \'hello\', b = \'hi\'    },\n    { a = \'hi\',    b = \'hello\' },\n    { a = \'hi\',    b = \'hi\'    }\n}',
}

format_test {
	name = 'Tabular style with subtables in keys',
    input  = {
		[false]	= { a = 'hi',	b = 'hello'	},
		[true]	= { a = 'hello',	b = 'hi'	}
	},
    expect = '{\n    [false] = { a = \'hi\',    b = \'hello\' },\n    [true]  = { a = \'hello\', b = \'hi\'    }\n}',
}

format_test {
	name = 'Tabular style also works with strange keys',
    input  = {
		{ [false] = 'hi',	[true] = 'hello',	},
		{ [false] = 'hello',	[true] = 'hi',	}
	},
    expect = '{\n    { [false] = \'hi\',    [true] = \'hello\' },\n    { [false] = \'hello\', [true] = \'hi\'    }\n}',
}

format_test {
	name = 'Tabular style also works with lists',
    input  = {
		{ 'hello',	'world', 	'how is things?'	},
		{ 'hi',	'planetdroid',	'sup?'	}
	},
    expect = '{\n    { \'hello\', \'world\',       \'how is things?\' },\n    { \'hi\',    \'planetdroid\', \'sup?\'           }\n}',
}

format_test {
	name = 'Tabular style with numbers right aligned',
    input  = {
		{ x =	 140,	y =	 72,	z =	 31	},
		{ x =	 5010,	y =	 1,	z =	 314	}
	},
    expect = '{\n    { x =  140, y = 72, z =  31 },\n    { x = 5010, y =  1, z = 314 }\n}',
}

format_test {
	name = 'Tabular style with small subtables',
    input  = {
		{ a = 'hel',	x = {b = 'abc',	c = 'yo'	}	},
		{ a = 'hi',	x = {b = 'yo',	c = 'abc'	}	}
	},
    expect = '{\n    { a = \'hel\', x = { b = \'abc\', c = \'yo\'  } },\n    { a = \'hi\',  x = { b = \'yo\',  c = \'abc\' } }\n}',
}


------------------------

format_test {
    name = 'Pseudo-Tabular style with justification',
    input  = {
        { st = 'ita', tx = 'annah',          },
        { st = 'bld', tx = 'tommy', sz = 20  },
        { st = 'unl', tx = 'mike',           },
    },
    expect = '{\n    { st = \'ita\', tx = \'annah\'          },\n    { st = \'bld\', tx = \'tommy\', sz = 20 },\n    { st = \'unl\', tx = \'mike\'           }\n}',
}

format_test {
    name = 'Pseudo-Tabular style with extreme justification',
    input  = {
        { st = 'ita', tx = 'annah',                   },
        { st = 'bld', tx = 'tommy',          sz = 20  },
        { st = 'unl', tx = 'mike',  sy = 21           },
    },
    expect = '{\n    { st = \'ita\', tx = \'annah\'                   },\n    { st = \'bld\', tx = \'tommy\',          sz = 20 },\n    { st = \'unl\', tx = \'mike\',  sy = 21          }\n}',
}

format_test {
    name = 'Pseudo-Tabular style without justification',
    input  = {
        { st = 'ita', tx = 'annah', sx = 19 },
        { st = 'bld', tx = 'tommy', sz = 20 },
        { st = 'unl', tx = 'mike',  sy = 21 },
        { st = 'ita', tx = 'milo'           },
        { st = 'bld', tx = 'yota'           },
        { st = 'unl', tx = 'kilo'           },
    },
    expect = '{\n    { st = \'ita\', tx = \'annah\', sx = 19 },\n    { st = \'bld\', tx = \'tommy\', sz = 20 },\n    { st = \'unl\', tx = \'mike\',  sy = 21 },\n    { st = \'ita\', tx = \'milo\'           },\n    { st = \'bld\', tx = \'yota\'           },\n    { st = \'unl\', tx = \'kilo\'           }\n}',
}

--------------------------------------------------------------------------------
-- Table recursion

SUITE:addTest('Avoid infinite loops in recursion', function ()
    local rec = {}
          rec[1] = rec
    format(rec)
    assert(true) -- We don't care about the output.
end)

-- TODO: This is a very complex topic, and will expanded upon after 1.0.0.

--------------------------------------------------------------------------------
-- General

SUITE:addTest('UseCase: Can print global enviroment', function ()
    format(_G)
    assert(true)
end)

SUITE:addTest('UseCase: Can print value with __tostring returning unusual values', function ()
    format( setmetatable({}, {__tostring = function() end}) )
    assert(true)
end)

SUITE:addTest('UseCase: Can load function from file that is shortly deleted', function ()
    local module_name = 'tmp_'..os.time()
    -- Create module
    local file = io.open('./'..module_name..'.lua', 'w')
          file:write '\nlocal function yo ()\n    -- Hello World\n    return math.random()\nend\n\nreturn yo\n'
          file:close()
    -- Load module
    local yo = require(module_name)
    -- Remove module
    os.remove('./'..module_name..'.lua')
    package.loaded[module_name] = nil

    -- Format the function, even though the module it came from is gone.
    format(yo)
    assert(true)
end)

local BIG_EXAMPLE_TABLE = [[
return {
    [0]   = 21082, [1]  = 696,
    [96]  = 62,    [97] = 334,
    [98]  = 119,   [99] = 217,
    [100] = 261
}]]

SUITE:addTest('UseCase: Big Example Table', function ()
    assert_equal(BIG_EXAMPLE_TABLE, 'return '..format(loadstring(BIG_EXAMPLE_TABLE)()))
end)


--------------------------------------------------------------------------------

return SUITE