dump = function (o)
   if type(o) == 'table' then
      local s = '{ '
      for k,v in pairs(o) do
         if type(k) ~= 'number' then k = '"'..k..'"' end
         s = s .. '['..k..'] = ' .. dump(v) .. ','
      end
      return s .. '} '
   else
      return tostring(o)
   end
end

dumpf = function (o)
	outfile=io.open("dump-file.txt", "w")

	outfile:write(dump(o))
	outfile:close()
end

-- Minimally effective quoting
quote = function (astring)
    local quoted1 = string.gsub(astring,'\"', '\\"')
    local quoted2 = '"' .. string.gsub(quoted1,"%'", "\\'") .. '"'
	local linefeed = string.gsub(quoted2,"\n", "\\n")
	return linefeed
end


write_saved = function (o)
--	local escaped
   if type(o) == 'table' then
      local s = '{\n'
      for k,v in pairs(o) do
         if type(k) ~= 'number' then
		 k = '"'..k..'"'
		 end
         s = s .. '['..k..'] = ' .. write_saved(v) .. ',\n'
      end
      return s .. '}\n'
   elseif
		type(o) == 'string' then
		return quote(o)
   else
		return tostring(o)
   end
end

_size = function (t)		-- return number of elements in table
	local i = 0
	for _,_ in pairs(t) do
		i = i +1
	end
	return i
end

reverse_id = function (Adata)	-- given a grp-id table like this
	--[[

	Grp_Dat["1N"].id = {
	[294]= {L=1, C=1},		-- "Fungal Grotto I Vanquisher",
	[78]= {L=2, C=1},		-- "Darkshade Caverns I Vanquisher"
	[272]= {L=3, C=1},		-- "Arx Corinium Vanquisher"
	[357]= {L=4, C=1},		-- "Direfrost Keep Vanquisher"
	[393]= {L=5, C=1},		-- "Blessed Crucible Vanquisher"

	return a table like this
	["11"] = 294
	["21"] = 78
	]]
	local rev = {}
	for ach, lc in pairs (Adata) do
		local key
		key = lc.L .. lc.C
		if rev[key] == nil then
			rev[key] = ach
		else
			print ("Reverse_id, error dup key: " .. key)
		end
	end
	return rev
end

blank_detail = function()
			detail_name.title = ""
			detail_desc.title = ""
end

box_mousemove_cb = 	function (self, l, c)	-- For Char Mode Only
-- we have previously added .dat to the box
		if self.dat == nil then
			detail_name.title = "box.dat is nil"
			return
		end

		if l == 0 or c == 0 then
			blank_detail()
			return
		end

		local key = l .. c

		local ach = self.dat.reverse_id[key]

		if ach == nil then
			blank_detail()
		--    detail_name.title = "No reverse lookup on " .. key	-- Normal on Group 2 and Pub panel with empty spaces
			return
		end

		if Ach_Detail[ach] == nil then
			detail_name.title = "No Achievement for " .. ach
			return
		end

		if (self.ach == nil) then
			print("box_mousemove_cb: self.ach is nil")
			return
		end

		if (self.ach[ach] ~= nil) then  -- completed (not on dungeon modes) This Mouseover shouldn't be on Dungeon Modes

			detail_name.title = "(ID: " .. ach .. ")  "  .. L.Completed .. os.date(dateformat,self.ach[ach].time) .. "   " .. Ach_Detail[ach].name
		else
			detail_name.title = "(ID: " .. ach .. ")  "  ..  Ach_Detail[ach].name
		end
		detail_desc.title = Ach_Detail[ach].description

	end


box_leavewindow_cb = function (self)
	blank_detail()
end

generate_id=function()
--write a combined list of achievement id we look for to add to the in-game part (cut and paste) for filtering
    local unique_id= {}   -- (id,true}

	outfile=io.open("data/ids.lua", "w")

	if outfile == nil then
		print ("Couldn't open id.lua file for writing.")
	end
	outfile:write("hist.IDs = {" .. "\n")

	for _,i in ipairs(Grp_Order) do
		outfile:write("-- GRP " .. i ..  "\n")
		for j,_ in pairs (Grp_Dat[i].id) do
		    if unique_id[j] == nil then
				unique_id[j] = true
				outfile:write("[" .. j .. "] = true,\n")
			end

		end
	end

	for _,i in ipairs(Trials_Order) do
		outfile:write("-- Trial " .. i .. "\n")
		for j,_ in pairs (Trials_Dat[i].id) do
		    if unique_id[j] == nil then
				unique_id[j] = true
				outfile:write("[" .. j .. "] = true,\n")
			end
		end
	end

	outfile:write("-- DLC " .. "\n")
	for j,_ in pairs (DLC_Dat.id) do
		    if unique_id[j] == nil then
				unique_id[j] = true
				outfile:write("[" .. j .. "] = true,\n")
			end
	end

	outfile:write("-- SQ " .. "\n")
	for j,_ in pairs (SQ_dat) do
		    if unique_id[j] == nil then
				unique_id[j] = true
				outfile:write("[" .. j .. "] = true,\n")
			end
	end

	outfile:write("-- Pub " .. "\n")
	for j,_ in pairs (Pub_Dat_Char.id) do
		    if unique_id[j] == nil then
				unique_id[j] = true
				outfile:write("[" .. j .. "] = true,\n")
			end
	end

	outfile:write("-- WB " .. "\n")
	for j,_ in pairs (WB_dat) do
		    if unique_id[j] == nil then
				unique_id[j] = true
				outfile:write("[" .. j .. "] = true,\n")
			end
	end

	outfile:write("-- Specials " .. "\n")
	for j,_ in pairs (Special_dat) do
		    if unique_id[j] == nil then
				unique_id[j] = true
				outfile:write("[" .. j .. "] = true,\n")
			end
	end

	outfile:write("-- DLC2 " .. "\n")

	for _,dlc in ipairs (DLC_Order) do
		outfile:write("-- DLC2 " .. dlc .."\n")
		for _,j in pairs (DLC_Dat2[dlc].line) do
			if unique_id[j] == nil then
				unique_id[j] = true
				outfile:write("[" .. j .. "] = true,\n")
			end
		end
	end

	outfile:write("}" .. "\n")
	outfile:close()
end


--Run through Location data to get the info needed to dimension the WB and SQ boxes
Location_Box = function (name)

	local cols = {}
	local lines = {}
	local Areas = {}
	--Summarise
	for i,entry in ipairs(Locations2Key) do
		if lines[entry.L] == nil then  lines[entry.L] = true end
		if cols[entry.C] == nil then  cols[entry.C] = entry.Area end	-- Note the Area for the Col
		if Areas[entry.Area] == nil then  Areas[entry.Area] = true end
	end
	--[[
	print("Cols:  " .. tostring(#cols))
	print("Lines: " .. #lines)
	print("Areas: " .. #Areas)
	--]]

	local return_t = iup.matrix{}
	return_t.numlin=#lines
	return_t.numcol=#cols
	return_t.widthdef=100

	iup.SetAttribute(return_t, "READONLY", "YES")
	iup.SetAttribute(return_t, "ALIGNMENT0", "ACENTER")
	-- Set Headings
	for col, Area in ipairs (cols) do
	--	print("Col:  " .. col .. ",  Area:  "  .. Area )
		return_t:setcell(0,col, Area_names[Area].long)
	--	print("Set Heading: " .. Area_names[Area].long)
	end

	--Set Lines
	return_t:setcell(0,0, L.Zone)
	for line,_ in ipairs(lines) do
		return_t:setcell(line,0, tostring(line))
	--	iup.SetAttribute(return_t,  "FGCOLOR*:".. tostring(line), FG_Colour_Not_Complete)
	--	iup.SetAttribute(return_t,  "BGCOLOR*:".. tostring(line), BG_Colour_Not_Complete)
	end

	--optionally set Contents
	if (type(name) ~= "string") then	return return_t end
--	get

	return return_t
end


-- take WB_Dat or SQ_Dat, which are indexed on id and return array
-- lines_t = table[key]  of  {done,of ids{id=false, id=false}}    then we can scan and set id to true if found
Reverse_Dat = function (dat_t,ids_t)
	if type(dat_t) ~= "table" then
		print("Reverse_Dat dat_t is not a table.")
		return
	end

	if type(ids_t) ~= "table" then
		print("Reverse_Dat ids_t is not a table.")
		return
	end



	local lines_t = {}	-- accumulate as LC Key
	for id, location_t in pairs (dat_t) do		--location is index into Locations2key and Locations2

		local key = Locations2Key[location_t.Location].key
		local link= location_t["link1"] ~= nil
		local gotid = (type(ids_t[id]) == "table")		-- do we have this achievement? nil is no. table is yes
		if lines_t[key] == nil then	--	add new line
			lines_t[key] = {}
			lines_t[key].L = Locations2Key[location_t.Location].L
			lines_t[key].C = Locations2Key[location_t.Location].C
			lines_t[key].ids={}
			lines_t[key].ids[id] = gotid
			lines_t[key].of =1
			lines_t[key].link=link		-- true if this key has Link1 (SQ)
			if gotid then lines_t[key].done = 1 else lines_t[key].done = 0 end
		else
			lines_t[key].ids[id] = gotid	--	add id to existing line
			lines_t[key].of = lines_t[key].of + 1
			if gotid then lines_t[key].done = lines_t[key].done + 1  end
		end
	end
	return lines_t
end

Populate_Dat = function(matrix, dat_t,ids_t)
		local detail_box={}
		local Rev = Reverse_Dat(dat_t,ids_t)
		matrix.Detail_zbox = iup.zbox{}		-- to which we append detail_box[key]


		for i,key_t  in ipairs (Locations2Key) do		--	{ key="1:1",L=1,C=1,Area=1},		--	name="Stonefalls"}
			local name = Locations2[i].name
			if name == nil then
				print("Populate_Dat: Locations2[i].name returns nil for i: " .. i)
				return
			end
			local line =Rev[key_t.key]
			if line == nil then 	-- this is ok. Location without a WB or SQ
			else
				matrix:setcell(line.L,line.C,name .. " (" .. line.done .. "/" .. line.of .. ")")
				if line.done == line.of then
					iup.SetAttribute(matrix, "FGCOLOR" .. key_t.key , FG_Colour_Complete)  -- Yup
					iup.SetAttribute(matrix, "BGCOLOR" .. key_t.key , BG_Colour_Complete)
				end
				if line.link then
					detail_box[key_t.key]=iup.matrix{numcol=3, numcol_visible=3,numlin=line.of}		--Detail box for that key "1:1"
					detail_box[key_t.key]:setcell(0,3, L.Link)
					iup.SetAttribute(detail_box[key_t.key], "WIDTH3", "200")
					iup.SetAttribute(detail_box[key_t.key], "ALIGNMENT3", "ALEFT")
				else
					detail_box[key_t.key]=iup.matrix{numcol=2, numcol_visible=2,numlin=line.of}		--Detail box for that key "1:1"
				end
				detail_box[key_t.key]:setcell(0,0, L.Ach_ID)
				detail_box[key_t.key]:setcell(0,1, L.Name)
				detail_box[key_t.key]:setcell(0,2, L.Detail)
				iup.SetAttribute(detail_box[key_t.key], "READONLY", "YES")
				iup.SetAttribute(detail_box[key_t.key], "ALIGNMENT0", "ACENTER")
				iup.SetAttribute(detail_box[key_t.key], "ALIGNMENTLIN0", "ALEFT")
				iup.SetAttribute(detail_box[key_t.key], "ALIGNMENT1", "ALEFT")
				iup.SetAttribute(detail_box[key_t.key], "ALIGNMENT2", "ALEFT")
				iup.SetAttribute(detail_box[key_t.key], "WIDTH0", "30")
				iup.SetAttribute(detail_box[key_t.key], "WIDTH1", "150")
				iup.SetAttribute(detail_box[key_t.key], "WIDTH2", "300")
				-- Fill in the details
				local j=1		--count the lines
				for id,isdone in pairs (line.ids) do
					if Ach_Detail[id] == nil then
						name = "UNKN"
						Desc = ""
					else
						name = Ach_Detail[id].name
						Desc = Ach_Detail[id].description
					end
					detail_box[key_t.key]:setcell(j,0,id)
					detail_box[key_t.key]:setcell(j,1, name)
					detail_box[key_t.key]:setcell(j,2, Desc)
					if line.link then
						detail_box[key_t.key]:setcell(j,3, dat_t[id]["link1"])
					end
					if isdone then
						iup.SetAttribute(detail_box[key_t.key],"BGCOLOR" .. tostring(j) .. ":*", BG_Colour_Complete)		-- Yes
					end
					j=j+1
				end

				local thisDetail =detail_box[key_t.key]
				function thisDetail:click_cb (L,C)
					if C == 3 then
						iup.Help(self:getcell(L,C))		-- Launch Browser
					else
						return IUP_IGNORE
					end
				end
				iup.Append(matrix.Detail_zbox, detail_box[key_t.key])
			end
		end
		function matrix:enteritem_cb(L,C)
			matrix.Detail_zbox.value = detail_box[tostring(L) ..":" .. tostring(C)]
		end
end