local M = {}

--[[ This comment is for LuaDoc.
---
-- A module for hotexit - keeps unsaved buffers on quit
module('_M.hotexit')]]

-- for better random()
math.randomseed(os.time())

-- Base 64 encoding decoding from: https://stackoverflow.com/a/35303321
local b='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' -- You will need this for encoding/decoding
-- encoding
function enc_base64(data)
	return ((data:gsub('.', function(x)
		local r,b='',x:byte()
		for i=8,1,-1 do r=r..(b%2^i-b%2^(i-1)>0 and '1' or '0') end
		return r;
	end)..'0000'):gsub('%d%d%d?%d?%d?%d?', function(x)
		if (#x < 6) then return '' end
		local c=0
		for i=1,6 do c=c+(x:sub(i,i)=='1' and 2^(6-i) or 0) end
		return b:sub(c+1,c+1)
	end)..({ '', '==', '=' })[#data%3+1])
end

-- decoding
function dec_base64(data)
	data = string.gsub(data, '[^'..b..'=]', '')
	return (data:gsub('.', function(x)
		if (x == '=') then return '' end
		local r,f='',(b:find(x)-1)
		for i=6,1,-1 do r=r..(f%2^i-f%2^(i-1)>0 and '1' or '0') end
		return r;
	end):gsub('%d%d%d?%d?%d?%d?%d?%d?', function(x)
		if (#x ~= 8) then return '' end
		local c=0
		for i=1,8 do c=c+(x:sub(i,i)=='1' and 2^(8-i) or 0) end
			return string.char(c)
	end))
end

_UNSAVED_CHANGED = false

-- when made changes on a saved buffer
events.connect(events.SAVE_POINT_LEFT, function(str)
	-- we remember that this has not been saved on unsaved memory
	buffer.unsavedbuffer = true
	-- pending changes to be written to disk (changes are in RAM)
	buffer.unsavedpendingchanges = true
	-- we give unsaved buffer an id
	if buffer.unsavedid == nil then
		buffer.unsavedid = math.random(100001,999999)
	end
	-- we keep the time when it was modified
	-- we will use it later to determine if buffer changed after we last
	-- saved it to unsaved memory
	buffer.unsavedmod = os.time(os.date("!*t"))
	_UNSAVED_CHANGED = true
end)
events.connect(events.CHAR_ADDED, function(str)
	-- so that ui.print does not see it as a savable change
	if buffer.tab_label ~= '[Message Buffer]' then
		buffer.unsavedbuffer = true
		buffer.unsavedpendingchanges = true
		_UNSAVED_CHANGED = true
	end
end)
events.connect(events.SAVE_POINT_REACHED, function(str)
	buffer.unsavedbuffer = false
	buffer.unsavedpendingchanges = false
end)

function save_unsaveddata(force_save)
	if not force_save then force_save = false end
	-- when force_save is true, we won't respect the timeout.
	-- it will run the function right away if it's set to true
	-- regardless of what is set on _UNSAVED_CHANGED.
	if force_save == false and _UNSAVED_CHANGED == false then return '' end

	local buffer_filename = ''
	local buffer_filenames = {}
	local unsaved_buffer_count = 0
	os.execute("mkdir " .. _USERHOME..'/_hotexitdata')
	os.remove(_USERHOME..'/_hotexitdata/hotexitdata.txt')
	if (#_BUFFERS > 0) then
		file = io.open(_USERHOME..'/_hotexitdata/hotexitdata.txt', "a")
		for j = 1, #_BUFFERS do
			if (_BUFFERS[j].unsavedbuffer == true) then
				unsaved_buffer_count = unsaved_buffer_count + 1
				if not _BUFFERS[j].filename then
					buffer_filename = ''
				else
					buffer_filename = _BUFFERS[j].filename
				end
				--[[
				Data order:
				1. the text "unsavedbuffer"
				2. unsaved buffer serial number
				3. buffer filename (blank if not saved anywhere)
				4. unsaved buffer modified date
				5. cursor position
				6. lexer type for the unsaved buffer
				* Separated by double pipe characters (||)
				--]]
				file:write('unsavedbuffer||'.._BUFFERS[j].unsavedid..'||'..buffer_filename..'||'.._BUFFERS[j].unsavedmod..'||'.._BUFFERS[j].current_pos..'||'..buffer.get_lexer(_BUFFERS[j]), "\n")

				if _BUFFERS[j].unsavedpendingchanges == true then
					unsavedfile = io.open(_USERHOME..'/_hotexitdata/'.._BUFFERS[j].unsavedid..'.txt', "w") -- w to overwrite existing file
					unsavedfile:write(enc_base64(buffer.get_text(_BUFFERS[j])))
					io.close(unsavedfile)
					_BUFFERS[j].unsavedpendingchanges = false
				end
				
				-- keep the filename for cleanup later
				buffer_filenames[#buffer_filenames+1] = _BUFFERS[j].unsavedid .. '.txt'
			end
		end
		io.close(file)
		_UNSAVED_CHANGED = false
		-- cleanup
		clean_unsaveddata(buffer_filenames)
	end

	return false -- quit. returning true will not quit
end

-- gets the filenames in an array
function get_files(dir)
	local dircmd = "ls -1 " .. dir -- default to Unix
	if string.sub(package.config,1,1) == '\\' then
			-- Windows
			dircmd = "dir /b " .. dir
	end

	--~ local files = os.execute(dircmd)
	local handle = io.popen(dircmd)
	local result = handle:read("*a")
	handle:close()
	-- get the files as array
	local files = explode("\n", result)
	--~ print(files)
	return files
end

-- Source: http://lua-users.org/wiki/MakingLuaLikePhp
-- Credit: http://richard.warburton.it/
function explode(div,str)
    if (div=='') then return false end
    local pos,arr = 0,{}
    for st,sp in function() return string.find(str,div,pos,true) end do
        table.insert(arr,string.sub(str,pos,st-1))
        pos = sp + 1
    end
    table.insert(arr,string.sub(str,pos))
    return arr
end

-- check if a string is there in an array
local function array_has_value(tab, val)
    for index, value in ipairs(tab) do
        if value == val then
            return true
        end
    end

    return false
end

-- clean unsaved data files that we don't need
-- (files that are no longer referenced in the hostexitdata.txt)
function clean_unsaveddata(logged_buffers)
	local buffer_files = get_files(_USERHOME..'/_hotexitdata/')
	for fileCount = 1, #buffer_files do
		if buffer_files[fileCount] ~= 'hotexitdata.txt' and array_has_value(logged_buffers, buffer_files[fileCount]) == false then
			os.remove(_USERHOME .. '/_hotexitdata/' .. buffer_files[fileCount])
		end
	end
end

events.connect(events.QUIT, function(str)
	textadept.session.save( _USERHOME..'/session' )
	save_unsaveddata(true)
	return false
end, 1) -- adding 1 prevents the default quit event function to run

-- save unsaved data after certain intervals
timeout(10, function()
	save_unsaveddata()
	return true -- true means repeat
end)

function file_exists(file)
	local f = io.open(file, "rb")
	if f then f:close() end
	return f ~= nil
end

function split(pString, pPattern)
	local Table = {n = 0}  -- NOTE: use {n = 0} in Lua-5.0
	local fpat = "(.-)" .. pPattern
	local last_end = 1
	local s, e, cap = pString:find(fpat, 1)
	while s do
		if s ~= 1 or cap ~= "" then
			table.insert(Table,cap)
		end
		last_end = e+1
		s, e, cap = pString:find(fpat, last_end)
	end
	if last_end <= #pString then
		cap = pString:sub(last_end)
		table.insert(Table, cap)
	end
	return Table
end

-- return buffer index after searching for filename
function getbufferbyfilename(filename)
	for j = 1, #_BUFFERS do
		if (_BUFFERS[j].filename == filename) then
			return j
		end
	end
	return nil
end

-- read all the content on file
function readallcontent(file)
    local f = assert(io.open(file, "rb"))
    local content = f:read("*all")
    f:close()
    return content
end

-- read back the previously saved unsaved data
function restore_unsaveddata()
	local unsaveddata_filename = _USERHOME..'/_hotexitdata/hotexitdata.txt'
	local line_data
	if not file_exists(unsaveddata_filename) then return {} end
	lines = {}
	for line in io.lines(unsaveddata_filename) do
		line_data = split(line, "||") -- index is 1 based
		
		local unsaveddata_buffer_filename = _USERHOME..'/_hotexitdata/'..line_data[2]..'.txt'
		
		if file_exists(unsaveddata_buffer_filename) then

			local buffer_index = getbufferbyfilename(line_data[3])
			if buffer_index ~= nil then -- buffer already exists
				_mybuffer = _BUFFERS[buffer_index]
			else
				_mybuffer = buffer.new()
			end

			-- fix: set the text so that it can undo to original text
			-- when buffer has unsaved change
			buffer.set_text(_mybuffer, dec_base64(readallcontent(unsaveddata_buffer_filename)))

			if (line_data[3] ~= '') then
				_mybuffer.filename = line_data[3]
			end
			buffer.goto_pos(_mybuffer, tonumber(line_data[5])) -- doesn't work properly for all buffers
			buffer.scroll_caret(_mybuffer)
			buffer.set_lexer(buffer, line_data[6])

			-- we indicate that we have this state saved
			_mybuffer.unsavedbuffer = true
			_mybuffer.unsavedid = line_data[2]
			_mybuffer.unsavedmod = line_data[4]
			_mybuffer.unsavedpendingchanges = false

		 end
	end
end

events.connect(events.INITIALIZED, function(str)
	restore_unsaveddata()
end)



return M
