Quantcast
local CHUNK_SIZE = 20000

local function separateNumericAndTextPortions(str)
	local result = {}
	local i, count = 1, 0
	while i <= str:len() do
		local j = i
		if str:sub(i, i) == "-" and i < str:len() then
			i = i + 1
		end
		while i <= str:len() and tonumber(str:sub(i, i)) do
			i = i + 1
		end
		if i > j then
			local num = tonumber(str:sub(j, i - 1))
			table.insert(result, num)
			count = count + 1
		end
		j = i
		while i <= str:len() and not tonumber(str:sub(i, i)) do
			i = i + 1
		end
		if i > j then
			if str:sub(i - 1, i - 1) == "-" and i <= str:len() then
				i = i - 1
			end
			if i > j then
				table.insert(result, str:sub(j, i - 1))
				count = count + 1
			end
		end
	end

	return result
end

local metatable = {
	__index = function(tbl, key)
		tbl[key] = separateNumericAndTextPortions(key)
		return tbl[key]
	end
}

local cache = setmetatable({}, metatable)

local function numericTextSort(str1, str2)
	local tokens1, tokens2
	tokens1 = cache[str1]
	tokens2 = cache[str2]

	local len1, len2 = table.maxn(tokens1), table.maxn(tokens2)

	for i = 1, math.min(len1, len2) do
		if tokens1[i] < tokens2[i] then
			return true
		elseif tokens2[i] < tokens1[i] then
			return false
		end
	end

	return len1 < len2
end

local function mixedSort(e1, e2)
	if type(e1) == type(e2) then
		if type(e1) == "string" then
			return numericTextSort(e1, e2)
		end
		return e1 < e2
	end
	return type(e1) < type(e2)
end

local function output(str)
	if str then
		io.write(str)
	end
	io.write("\n")
end

local function quotedString(str)
	return string.gsub(string.format("%q", str), "\n", "n")
end

local keywords = {
	["and"] = true,
	["break"] = true,
	["do"] = true,
	["else"] = true,
	["elseif"] = true,
	["end"] = true,
	["false"] = true,
	["for"] = true,
	["function"] = true,
	["if"] = true,
	["in"] = true,
	["local"] = true,
	["nil"] = true,
	["not"] = true,
	["or"] = true,
	["repeat"] = true,
	["return"] = true,
	["then"] = true,
	["true"] = true,
	["until"] = true,
	["while"] = true,
}

local function varName(name)
	if type(name) == "number" then
		return string.format("[%d]", name)
	elseif type(name) == "string" then
		if name:find("^%a[%w_]+$") and not keywords[name] then
			return name
		else
			return string.format("[%s]", quotedString(name))
		end
	else
		return string.format("Unhandled name type: %q", type(name))
	end
end

function sort(tbl, name, indent, incremental)
	local indentStr
	if indent then
		indentStr = string.rep("\t", indent)
	else
		indent = 0
		indentStr = ""
	end

	local indexTable = {}
	for index, value in pairs(tbl) do
		table.insert(indexTable, index)
	end
	table.sort(indexTable, mixedSort)

	if #(indexTable) > CHUNK_SIZE then
		output(string.format("%s = {}", name))
		for i = 1, #(indexTable), CHUNK_SIZE do
			output(string.format("\nlocal function chunk%d()", math.ceil(i / 20000)))
			output(string.format("\tlocal t = %s\n", name))
			for j = i, i + CHUNK_SIZE - 1 do
				local index = indexTable[j]
				if not index then
					break
				end
				local value = tbl[index]
				if type(value) == "string" then
					output(string.format("%s\tt[%s] = %s,", indentStr, quotedString(index), quotedString(value)))
				elseif type(value) == "number" then
					value = string.gsub(string.format("%f", value), "%.?0+$", "")
					output(string.format("%s\tt[%s] = %s,", indentStr, quotedString(index), value))
				elseif type(value) == "table" then
					output(string.format("%s\tt[%s] = {", indentStr, quotedString(index)))
					sort(value, nil, indent + 1, true)
				elseif type(value) == "boolean" then
					output(string.format("%s\tt[%s] = %s,", indentStr, quotedString(index), value and "true" or "false"))
				else
					output(string.format("%s\tUnhandled value type: %q", indentStr, type(value)))
				end
			end
			output("end")
		end
		output()
		for i = 1, #(indexTable), CHUNK_SIZE do
			output(string.format("chunk%d()", math.ceil(i / 20000)))
		end
	else
		if not incremental then
			output(string.format("%s%s = {", indentStr, varName(name)))
		end

		for _, index in ipairs(indexTable) do
			local value = tbl[index]
			if type(value) == "string" then
				output(string.format("%s\t%s = %s,", indentStr, varName(index), quotedString(value)))
			elseif type(value) == "number" then
				value = string.gsub(string.format("%f", value), "%.?0+$", "")
				output(string.format("%s\t%s = %s,", indentStr, varName(index), value))
			elseif type(value) == "table" then
				sort(value, index, indent + 1)
			elseif type(value) == "boolean" then
				output(string.format("%s\t%s = %s,", indentStr, varName(index), value and "true" or "false"))
			else
				output(string.format("%s\tUnhandled value type: %q", indentStr, type(value)))
			end
		end
		if indent > 0 then
			if incremental then
				output(string.format("%s}", indentStr))
			else
				output(string.format("%s},", indentStr))
			end
		else
			output("}")
		end
	end
end