local inifile = require "inifile"

local function parse(text)
	return inifile.parse(text, "memory")
end

local function save(data)
	return inifile.save("dummy", data, "memory")
end

describe("Parsing", function()
	it("returns data per section", function()
		local data = parse[==[
[Test1]
Some=Data

[Test2]
Some=Other data
]==]

		assert.are.equal("Data", data.Test1.Some)
		assert.are.equal("Other data", data.Test2.Some)
	end)

	it("detects numbers", function()
		local data = parse[==[
[Test]
Value1=3.14
]==]

		assert.are.equal(3.14, data.Test.Value1)
		assert.are.equal("number", type(data.Test.Value1))
	end)

	it("detects booleans", function()
		local data = parse[==[
[Test]
Value1=true
Value2=false
]==]

		assert.is_true(data.Test.Value1)
		assert.are.equal("boolean", type(data.Test.Value1))
		assert.is_false(data.Test.Value2)
		assert.are.equal("boolean", type(data.Test.Value2))
	end)

	it("handles values with = signs", function()
		local data = parse[==[
[Test]
Value=some=data
]==]

		assert.are.equal("some=data", data.Test.Value)
	end)

	it("supports reopening sections", function()
		local data = parse[==[
[Test1]
A=1

[Test2]
A=2
B=2

[Test1]
B=3
]==]

		assert.are.equal(1, data.Test1.A)
		assert.are.equal(2, data.Test2.A)
		assert.are.equal(2, data.Test2.B)
		assert.are.equal(3, data.Test1.B)
	end)

	it("returns no extra data", function()
		local data = parse[==[
[Test]
Some=data
Other=data
]==]

		assert.are.equal("data", data.Test.Some)
		assert.are.equal("data", data.Test.Other)

		for section in pairs(data) do
			assert.are.equal("Test", section)
		end

		for key in pairs(data.Test) do
			assert.is_true(key == "Some" or key == "Other")
		end
	end)

	it("ignores comments", function()
		local data = parse[==[
; Comment outside of section
[Test]
; Comment at start of section
Some=data
; Comment in middle of section
Other=data
; Comment at the end
]==]

		assert.are.equal("data", data.Test.Some)
		assert.are.equal("data", data.Test.Other)

		for section in pairs(data) do
			assert.are.equal("Test", section)
		end

		for key in pairs(data.Test) do
			assert.is_true(key == "Some" or key == "Other")
		end
	end)
end)

describe("Error handling", function()
	it("reports data is not in a section", function()
		local _, errors = parse[==[
Some=key

[Section]
Some=data
]==]

		assert.are.same(1, #errors)
		assert.matches("^Line 1", errors[1])
		assert.matches("Some=key", errors[1])
	end)

	it("reports invalid section headers", function()
		local _, errors = parse[==[
[Section] x
]==]

		assert.are.same(1, #errors)
		assert.matches("^Line 1", errors[1])
		assert.matches("%[Section%] x", errors[1])
	end)

	it("reports invalid data lines", function()
		local _, errors = parse[==[
[Section]
Some data
]==]

		assert.are.same(1, #errors)
		assert.matches("^Line 2", errors[1])
		assert.matches("Some data", errors[1])
	end)
end)

describe("Saving", function()
	it("reports invalid sections", function()
		assert.error_matches(
			function()
				save{
					Test = "not a table"
				}
			end,
			"Invalid section")
	end)

	it("writes all data types", function()
		local toStringable = setmetatable({}, {
			__tostring = function()
				return "using __tostring"
			end
		})

		local randomTable = {}

		local ini = save{
			Test = {
				string = "abc",
				number = 1.23,
				boolean = true,
				toStringable = toStringable,
				randomTable = randomTable,
			}
		}

		local expected_lines = {
			"string=abc",
			"number=1.23",
			"boolean=true",
			"toStringable=using __tostring",
			"randomTable=" .. tostring(randomTable)
		}

		-- Skip header
		ini = ini:match("^.-\n(.+)$")

		-- Parse all lines into a table
		local ini_lines = {}
		for line in ini:gmatch("(.-)\n") do
			table.insert(ini_lines, line)
		end

		-- Sort both, for equality
		table.sort(expected_lines)
		table.sort(ini_lines)
		assert.are.same(expected_lines, ini_lines)
	end)
end)

describe("Formatting", function()
	describe("preserves order and comments", function()
		it("without modifications", function()
			local input = [==[
; Comment outside of section

[Section1]
; Comment in section
Some=Value

[Section2]
Some=Other value
Second=value
]==]

			local output = save(parse(input))
			assert.are.equal(input, output)
		end)

		it("moving comments to the start of a section", function()
			local input = [==[
[Section]
; Comment at start
Some=Value
; Comment in middle
Other=Value
; Comment at end
]==]

			local expected = [==[
[Section]
; Comment at start
; Comment in middle
; Comment at end
Some=Value
Other=Value
]==]

			local output = save(parse(input))
			assert.are.equal(expected, output)
		end)

		it("skipping removed values", function()
			local input = [==[
[Section]
D=1
C=2
B=3
A=4
]==]

			local expected = [==[
[Section]
D=1
C=2
A=4
]==]

			local parsed = parse(input)
			parsed.Section.B = nil
			local output = save(parsed)
			assert.are.equal(expected, output)
		end)

		it("skipping removed sections", function()
			local input = [==[
[D]
Value=1

[C]
Value=2

[B]
Value=3

[A]
Value=4
]==]

			local expected = [==[
[D]
Value=1

[C]
Value=2

[A]
Value=4
]==]

			local parsed = parse(input)
			parsed.B = nil
			local output = save(parsed)
			assert.are.equal(expected, output)
		end)

		it("appending added values", function()
			local input = [==[
[Section]
A=1
C=3
D=4
]==]

			local expected = [==[
[Section]
A=1
C=3
D=4
B=2
]==]

			local parsed = parse(input)
			parsed.Section.B = 2
			local output = save(parsed)
			assert.are.equal(expected, output)
		end)

		it("appending added sections", function()
			local input = [==[
[D]
Value=1

[C]
Value=2

[A]
Value=4
]==]

			local expected = [==[
[D]
Value=1

[C]
Value=2

[A]
Value=4

[B]
Value=3
]==]

			local parsed = parse(input)
			parsed.B = { Value = 3 }
			local output = save(parsed)
			assert.are.equal(expected, output)
		end)
	end)
end)
