local sql = require("lsqlite3")
local datfile = {}
local db = sql.open(lfs.env_replace(mame_manager:ui():options().entries.historypath:value():match("([^;]+)")) .. "/history.db")
if db then
	local found = false
	db:exec("select * from sqllite_master where name = version", function() found = true return 0 end)
	if not found then
		db:exec([[
			CREATE TABLE version (
				version VARCHAR NOT NULL,
				datfile VARCHAR UNIQUE NOT NULL)]])
	end
end

function datfile.open(file, vertag, fixupcb)
	if not db then
		return nil
	end
	local function read(tag1, tag2, set)
		local data
		local stmt = db:prepare("SELECT f.data FROM \"" .. file .. "_idx\" AS fi, \"" .. file .. [["
		 AS f WHERE fi.type = ? AND fi.val = ? AND fi.romset = ? AND f.rowid = fi.data]])
		stmt:bind_values(tag1, tag2, set)
		if stmt:step() == sql.ROW then
			data = stmt:get_value(0)
		end
		stmt:finalize()
		return data
	end

	local ver, dbver
	local filepath
	local fh

	for path in mame_manager:ui():options().entries.historypath:value():gmatch("([^;]+)") do
		filepath = lfs.env_replace(path) .. "/" .. file
		fh = io.open(filepath, "r")
		if fh then
			break
		end
		return
	end
	-- remove unsafe chars from file for use in sql statement
	file = file:gsub("[^%w%._]", "")

	local stmt = db:prepare("SELECT version FROM version WHERE datfile = ?")
	stmt:bind_values(file)
	if stmt:step() == sql.ROW then
		dbver = stmt:get_value(0)
	end
	stmt:finalize()

	if not dbver then
		db:exec("CREATE TABLE \"" .. file .. [[_idx" (
				type VARCHAR NOT NULL,
				val VARCHAR NOT NULL,
				romset VARCHAR NOT NULL,
				data INTEGER NOT NULL)]])
		db:exec("CREATE TABLE \"" .. file .. "\" (data CLOB NOT NULL)")
		db:exec("CREATE INDEX \"typeval_" .. file .. "\" ON \"" .. file .. "_idx\"(type, val)")
	elseif not fh then
		-- data in database but missing file, just use what we have
		return read, dbver
	elseif not fh and not dbver then
		return nil
	end

	if vertag then
		for line in fh:lines() do
			local match = line:match(vertag .. "%s*([^%s]+)")
			if match then
				ver = match
				break
			end
		end
	else
		-- use file ctime for version
		ver = tostring(lfs.attributes(filepath, "change"))
	end
	if ver == dbver then
		return read, dbver
	end

	if dbver then
		db:exec("DELETE FROM \"" .. file .. "\"")
		db:exec("DELETE FROM \"" .. file .. "_idx\"")
		stmt = db:prepare("UPDATE version SET version = ? WHERE datfile = ?")
	else
		stmt = db:prepare("INSERT INTO version VALUES (?, ?)")
	end
	stmt:bind_values(ver, file)
	stmt:step()
	stmt:finalize()

	do
		local inblock = false
		fh:seek("set")
		local buffer = fh:read("a")
		db:exec("BEGIN TRANSACTION")
		local function gmatchpos()
			local pos = 1
			local function iter()
				local tag1, tag2, data, start, inblock = false
				while not data do
					local spos, epos, match = buffer:find("\n($[^\n]*)", pos)
					if not spos then
						return nil
					end
					if match ~= "$end" and not inblock then
						if not tag1 then
							tag1 = match
						else
							tag2 = match
							start = epos + 1
							inblock = true
						end
					elseif inblock == true then
						data = buffer:sub(start, spos)
						inblock = false
					end
					pos = epos
				end
				return tag1, tag2, data
			end
			return iter
		end
		for info1, info2, data in gmatchpos() do
			local tag, set = info1:match("^%$([^%s=]+)=?([^%s]*)")
			if set and set ~= "" then
				local tags = {}
				local sets = {}
				tag:gsub("([^,]+)", function(s) tags[#tags + 1] = s end)
				set:gsub("([^,]+)", function(s) sets[#sets + 1] = s end)
				if #tags > 0 and #sets > 0 then
					local tag1 = info2:match("^$([^%s]*)")
					if fixupcb then
						data = fixupcb(data)
					end
					stmt = db:prepare("INSERT INTO \"" .. file .. "\" VALUES (?)")
					stmt:bind_values(data)
					stmt:step()
					local row = stmt:last_insert_rowid()
					stmt:finalize()
					for num1, tag2 in pairs(tags) do
						for num2, set in pairs(sets) do
							if fixupcb then
								fixupcb(data)
							end
							stmt = db:prepare("INSERT INTO \"" .. file .. "_idx\" VALUES (?, ?, ?, ?)")
							stmt:bind_values(tag1, tag2, set, row)
							stmt:step()
							stmt:finalize()
						end
					end
				end
			end
		end
		db:exec("END TRANSACTION")
	end
	fh:close()

	return read, ver
end

return datfile
