-- thomson.lua : lots of utility for handling
-- thomson screen.
--
-- Version: 02-jan-2017
--
-- Copyright 2016-2017 by Samuel Devulder
--
-- This program is free software; you can redistribute
-- it and/or modify it under the terms of the GNU
-- General Public License as published by the Free
-- Software Foundation; version 2 of the License.
-- See 
if not thomson then
run("color.lua") -- optionnal
thomson = {optiMAP=true}
-- RAM banks
thomson.ramA = {}
thomson.ramB = {}
function thomson.clear()
	for i=1,8000 do
		thomson.ramA[i] = 0
		thomson.ramB[i] = 0
	end
end
-- color levels
thomson.levels = {
	-- in pc-space (0-255):
	pc = {0,100,127,142,163,179,191,203,215,223,231,239,
		  243,247,251,255},
	-- in linear space (0-255):
	linear = {},
	-- maps pc-levels (0-255) to thomson levels (1-16)
	pc2to={},
	-- maps linear-levels (0-255) to thomson levels (1-16)
	linear2to={}
};
-- pc space to linear space
local function toLinear(val)
	-- use the version from Color library
	if not Color then
		val = val/255
		if val<=0.081 then
			val = val/4.5;
		else
			val = ((val+0.099)/1.099)^2.2;
		end
		val = val*255
		return val;
	else
		return Color:new(val,0,0):toLinear().r
	end
end
for i=1,16 do
	thomson.levels.linear[i] = toLinear(thomson.levels.pc[i])
end
for i=0,255 do
	local r,cm,dm;
	r,cm,dm = toLinear(i),0,1e30
	for c,v in ipairs(thomson.levels.linear) do
		local d = math.abs(v-r);
		if d=0 and i=0 and i 00 02 aa bb
			if default and num==1 and partial[1]==1 then
				partial = {0,2,partial[2],car}
				default = false
			end
			-- 00 n xx xx xx 01 bb ==> 00 n+1 xx xx xx bb
			if default and num==1 and partial[1]==0 and partial[2]<255 then
				addCarToPartial(car)
				default = false
			end
			-- 00 n xx xx xx 02 bb ==> 00 n+2 xx xx xx bb bb (pas utile mais sert quand combiné ŕ la regle ci-dessus)
			if default and num==2 and partial[1]==0 and partial[2]<254 then
				addCarToPartial(car)
				addCarToPartial(car)
				default = false
			end
		end
		if default then
			thomson._append(result, partial)
			partial = {num,car}
		end
		p=p+1
	end
	thomson._append(result, partial)
	return result
end
-- save a map file corresponging to the current file
-- if a map file already exist, a confirmation is
-- prompted to the user
local function save_current_file()
	local function exist(file)
		local f=io.open(file,'rb')
		if not f then return false else io.close(f); return true; end
	end
	local name,path = getfilename()
	local mapname = string.gsub(name,"%.%w*$","") .. ".map"
	local fullname = path .. '/' .. mapname
	local ok = not exist(fullname)
	if not ok then
		selectbox("Ovr " .. mapname .. "?", "Yes", function() ok = true; end, "No", function() ok = false; end)
	end
	if ok then thomson.savep(fullname) end
end
-- saves the thomson screen into a MAP file
function thomson.savep(name)
	if not name then return save_current_file()	end
	wait(0) -- allow for key handling
	local data = thomson._get_map_data()
	local tmp = {0, math.floor(#data/256), #data%256,0,0}
	thomson._append(tmp,data,{255,0,0,0,0})
	local function save(name, buf)
		local out = io.open(name,"wb")
		out:write(buf)
		out:close()
	end
	save(name, string.char(unpack(tmp)))
	-- save raw data as well ?
	local moved, key, mx, my, mb = waitinput(0.01)
	if key==4123 then -- shift-ESC ==> save raw files as well
		save(name .. ".rama", string.char(unpack(thomson.ramA)))
		save(name .. ".ramb", string.char(unpack(thomson.ramB)))
		local pal = ""
		for i=0,15 do
			local val = thomson.palette(i)
			pal=pal..string.char(math.floor(val/256),val%256)
		end
		save(name .. ".pal", pal)
		messagebox('Saved MAP + RAMA/RAMB/PAL files.')
	end
end
waitbreak(0.01)
function thomson.info(...)
	local txt = ""
	for _,t in ipairs({...}) do txt = txt .. t end
	statusmessage(txt);
	if waitbreak(0)==1 then
		local ok=false
		selectbox("Abort ?", "Yes", function() ok = true end, "No", function() ok = false end)
		if ok then error('Operation aborted') end
	end
end
-- copy ramA/B onto GrafX2 screen
function thomson.updatescreen()
	-- back out
	for i=0,255 do
		setcolor(i,0,0,0)
	end
	-- refresh screen content
	clearpicture(thomson._palette.offset + thomson.border())
	for y=0,thomson.h-1 do
		for x=0,thomson.w-1 do
			local p = thomson.point(x,y)
			if p<0 then p=-p-1 end
			thomson._putpixel(x,y,thomson._palette.offset + p)
		end
	end
	-- refresh palette
	for i=1,thomson._palette.max do
		local v=thomson._palette[i]
		local r=v % 16
		local g=math.floor(v/16)  % 16
		local b=math.floor(v/256) % 16
		setcolor(i+thomson._palette.offset-1,
				 thomson.levels.pc[r+1],
				 thomson.levels.pc[g+1],
				 thomson.levels.pc[b+1])
	end
	updatescreen()
end
-- bitmap 16 mode
function thomson.setBM16()
	-- put a pixel onto real screen
	function thomson._putpixel(x,y,c)
		putpicturepixel(x*2+0,y,c)
		putpicturepixel(x*2+1,y,c)
	end
	-- put a pixel in thomson screen
	function thomson.pset(x,y,c)
		local bank = x%4<2 and thomson.ramA or thomson.ramB
		local offs = math.floor(x/4)+y*40+1
		if x%2==0 then
			bank[offs] = (bank[offs]%16)+c*16
		else
			bank[offs] = math.floor(bank[offs]/16)*16+c
		end
		-- c=c+thomson._palette.offset
		-- putpicturepixel(x*2+0,y,c)
		-- putpicturepixel(x*2+1,y,c)
	end
	-- get thomson pixel at (x,y)
	function thomson.point(x,y)
		local bank = x%4<2 and thomson.ramA or thomson.ramB
		local offs = math.floor(x/4)+y*40+1
		if x%2==0 then
			return math.floor(bank[offs]/16)
		else
			return bank[offs]%16
		end
	end
	-- return internal MAP file
	function thomson._get_map_data()
		local tmp = {}
		for x=1,40 do
			for y=x,x+7960,40 do
				table.insert(tmp, thomson.ramA[y])
			end
			for y=x,x+7960,40 do
				table.insert(tmp, thomson.ramB[y])
			end
			wait(0) -- allow for key handling
		end
		local pal = {}
		for i=1,16 do
			pal[2*i-1] = math.floor(thomson._palette[i]/256)
			pal[2*i+0] =            thomson._palette[i]%256
		end
		-- build data
		local data={
			-- BM16
			0x40,
			-- ncols-1
			79,
			-- nlines-1
			24
		};
		thomson._compress(data, tmp)
		thomson._append(data,{0,0})
		-- padd to word
		if #data%2==1 then table.insert(data,0); end
		-- tosnap
		thomson._append(data,{0,128,0,thomson.border(),0,3})
		thomson._append(data, pal)
		thomson._append(data,{0xa5,0x5a})
		return data
	end
	thomson.w = 160
	thomson.h = 200
	thomson.palette(0,thomson.default_palette)
	thomson.border(0)
	thomson.clear()
end
-- mode MO5
function thomson.setMO5()
	-- put a pixel onto real screen
	thomson._putpixel = putpicturepixel
	-- helpers
	local function bittst(val,mask)
		-- return bit32.btest(val,mask)
		return (val % (2*mask))>=mask;
	end
	local function bitset(val,mask)
		-- return bit32.bor(val, mask)
		return bittst(val,mask) and val or (val+mask)
	end
	local function bitclr(val,mask)
		-- return bit32.band(val,255-mask)
		return bittst(val,mask) and (val-mask) or val
	end
	-- put a pixel in thomson screen
	function thomson.pset(x,y,c)
		local offs = math.floor(x/8)+y*40+1
		local mask = 2^(7-(x%8))
		if c>=0 then
			thomson.ramB[offs] = (thomson.ramB[offs]%16)+c*16
			thomson.ramA[offs] = bitset(thomson.ramA[offs],mask)
		else
			c=-c-1
			thomson.ramB[offs] = math.floor(thomson.ramB[offs]/16)*16+c
			thomson.ramA[offs] = bitclr(thomson.ramA[offs],mask)
		end
	end
	-- get thomson pixel at (x,y)
	function thomson.point(x,y)
		local offs = math.floor(x/8)+y*40+1
		local mask = 2^(7-(x%8))
		if bittst(thomson.ramA[offs],mask) then
			return math.floor(thomson.ramB[offs]/16)
		else
			return -(thomson.ramB[offs]%16)-1
		end
	end
	-- convert color from MO5 to TO7 (MAP requires TO7 encoding)
	local function mo5to7(val)
		-- MO5: DCBA 4321
		--      __
		-- TO7: 4DCB A321
		local t=((val%16)>=8) and 0 or 128
		val = math.floor(val/16)*8 + (val%8)
		val = (val>=64 and val-64 or val+64) + t
		return val
	end
	-- return internal MAP file
	function thomson._get_map_data()
		-- create columnwise data
		local tmpA,tmpB={},{};
		for x=1,40 do
			for y=x,x+7960,40 do
				table.insert(tmpA, thomson.ramA[y])
				table.insert(tmpB, thomson.ramB[y])
			end
			wait(0) -- allow for key handling
		end
		if thomson.optiMAP then
			-- optimize
			for i=2,8000 do
				local c1,c2 = math.floor(tmpB[i-0]/16),tmpB[i-0]%16
				local d1,d2 = math.floor(tmpB[i-1]/16),tmpB[i-1]%16
				if tmpA[i-1]==255-tmpA[i] or c1==d2 and c2==c1 then
					tmpA[i] = 255-tmpA[i]
					tmpB[i] = c2*16+c1
				elseif tmpA[i]==255 and c1==d1 or tmpA[i]==0 and c2==d2 then
					tmpB[i] = tmpB[i-1]
				end
			end
		else
			for i=1,8000 do
				local c1,c2 = math.floor(tmpB[i]/16),tmpB[i]%16
				if tmpA[i]==255 or c1