-- lx-bundle:true

-- This file is part of Lx, created by Liqube <www.liqube.com>
-- Copyright Liqube Audio. All rights reserved.
-- Lx Interim License. See LICENSE for details.

local codepage = {}

local str_byte = string.byte
local str_char = string.char
local table_concat = table.concat
local utf8_char = utf8.char
local utf8_codes = utf8.codes

local table_cache = {}
local reverse_cache = {}
local list_cache = nil

local function module_name(name)
	return "cp." .. name
end

local function codepage_dir()
	return DATA_DIR .. "cp"
end

function codepage.load(name)
	if type(name) ~= "string" or name == "" then
		return nil, "codepage name must be a non-empty string"
	end
	if table_cache[name] ~= nil then
		return table_cache[name] or nil, table_cache[name] and nil or string.format('codepage "%s" is unavailable or invalid', name)
	end

	local ok, mod = pcall(require, module_name(name))
	if not ok or type(mod) ~= "table" or type(mod.table) ~= "table" then
		table_cache[name] = false
		return nil, string.format('codepage "%s" is unavailable or invalid', name)
	end

	table_cache[name] = mod.table
	return mod.table
end

function codepage.list()
	if list_cache then
		return list_cache
	end

	local files = system.list_dir(codepage_dir()) or {}
	local out = {}
	for i = 1, #files do
		local f = files[i]
		if type(f) == "string" and f:match("%.lua$") then
			out[#out + 1] = f:gsub("%.lua$", "")
		end
	end
	table.sort(out)
	list_cache = out
	return out
end

function codepage.symbol(name, b)
	local map, err = codepage.load(name)
	if not map then
		return nil, err
	end
	return map[b], nil
end

function codepage.resolve(name)
	return codepage.load(name)
end

function codepage.decode(data, name, replacement)
	local map, err = codepage.load(name)
	if not map then
		return nil, true, err
	end

	local text = data or ""
	local out = {}
	local had_errors = false
	local fallback = replacement or "?"
	for i = 1, #text do
		local b = str_byte(text, i)
		local mapped = map[b]
		if type(mapped) == "string" and mapped ~= "" then
			out[#out + 1] = mapped
		else
			had_errors = true
			out[#out + 1] = fallback
		end
	end
	return table_concat(out), had_errors, nil
end

function codepage.encode(text, name, replacement)
	local map, err = codepage.load(name)
	if not map then
		return nil, true, err
	end

	local rev = reverse_cache[name]
	if not rev then
		rev = {}
		for b = 0, 255 do
			local ch = map[b]
			if type(ch) == "string" and ch ~= "" and rev[ch] == nil then
				rev[ch] = b
			end
		end
		reverse_cache[name] = rev
	end

	local bytes = {}
	local had_errors = false
	local fallback = replacement or "?"
	for _, cp in utf8_codes(text or "") do
		local ch = utf8_char(cp)
		local b = rev[ch]
		if b == nil then
			had_errors = true
			bytes[#bytes + 1] = fallback
		else
			bytes[#bytes + 1] = str_char(b)
		end
	end
	return table_concat(bytes), had_errors, nil
end

return codepage
