local dvireader = require "make4ht-dvireader"
local mkutils = require "mkutils"
local filter = require "make4ht-filter"
local log = logging.new "dvisvgm_hashes"


local dvisvgm_par = {}

local M = {}
-- mapping between tex4ht image names and hashed image names
local output_map = {}
local dvisvgm_options = "-n --exact -c ${scale},${scale}"
local parallel_size = 64
local make_command = "make -j ${process_count} -f ${make_file}"
local test_make_command = "make -v"
-- local parallel_size = 3

local function make_hashed_name(base, hash)
 return base .. "-" ..hash..".svg"
end

-- detect the number of available processors
local cpu_cnt = 3  -- set a reasonable default for non-Linux systems

if os.name == 'linux' then
 cpu_cnt = 0
 local cpuinfo=assert(io.open('/proc/cpuinfo', 'r'))
 for line in cpuinfo:lines() do
   if line:match('^processor') then
     cpu_cnt = cpu_cnt + 1
   end
 end
 -- set default number of threds if no CPU core have been found
 if cpu_cnt == 0 then cpu_cnt = 1 end
 cpuinfo:close()
elseif os.name == 'cygwin' or os.type == 'windows' then
 -- windows has NUMBER_OF_PROCESSORS environmental value
 local nop = os.getenv('NUMBER_OF_PROCESSORS')
 if tonumber(nop) then
   cpu_cnt = nop
 end
end



-- process output of dvisvgm and find output page numbers and corresponding files
local function get_generated_pages(output, pages)
 local pages = pages or {}
 local pos = 1
 local pos, finish, page = string.find(output, "processing page (%d+)", pos)
 while(pos) do
   pos, finish, file = string.find(output, "output written to ([^\n^\r]+)", finish)
   pages[tonumber(page)] = file
   if not finish then break end
   pos, finish, page = string.find(output, "processing page (%d+)", finish)
 end
 return pages
end

local function make_ranges(pages)
 local newpages = {}
 local start, stop
 for i=1,#pages do
   local current = pages[i]
   local next_el = pages[i+1] or current + 100 -- just select a big number
   local diff = next_el - current
   if diff == 1 then
     if not start then start = current end
   else
     local element
     if start then
       element = start .. "-" .. current
     else
       element = current
     end
     newpages[#newpages+1] = element
     start = nil
   end
 end
 return newpages
end

local function read_log(dvisvgmlog)
 local f = io.open(dvisvgmlog, "rb")
 if not f then return nil, "Cannot read dvisvgm log" end
 local output = f:read("*all")
 f:close()
 return output
end

-- test the existence of GNU Make, which can execute tasks in parallel
local function test_make()
 local make = io.popen(test_make_command, "r")
 local content = make:read("*all")
 make:close()
 -- io.popen always returns valid handle, so we can find that the command doesn't exists only by checking that the
 -- content is empty
 return content~=nil and content ~= ""
end

local function save_file(filename, text)
 local f = io.open(filename, "w")
 f:write(text)
 f:close()
end


local function make_makefile_command(idvfile, page_sequences)
 local logs = {}
 local all = {} -- list of targets in the "all:" makefile target
 local targets = {}
 local basename = idvfile:gsub(".idv$", "")
 local makefilename = basename .. "-images" .. ".mk"
 -- build make targets
 for i, ranges in ipairs(page_sequences) do
   local target = basename .. "-" .. i
   local logfile = target .. ".dlog"
   logs[#logs + 1] = logfile
   all[#all+1] = target
   local chunk = target .. ":\n\tdvisvgm -v4 " .. dvisvgm_options .. " -p " .. ranges  .. " " .. idvfile .. " 2> " .. logfile .. "\n"
   targets[#targets + 1] = chunk
 end
 -- construct makefile and save it
 local makefile = "all: " .. table.concat(all, " ") .. "\n\n" .. table.concat(targets, "\n")
 save_file(makefilename, makefile)
 local command = make_command % {process_count = cpu_cnt, make_file = makefilename}
 log:debug("Makefile command: " .. command)
 return command, logs
end

local function prepare_command(idvfile, pages)
 local logs = {}
 if #pages > parallel_size and test_make() then
   local page_sequences = {}
   for i=1, #pages, parallel_size do
     local current_pages = {}
     for x = i, i+parallel_size -1 do
       current_pages[#current_pages + 1] = pages[x]
     end
     table.insert(page_sequences,table.concat(make_ranges(current_pages), ","))
   end
   return make_makefile_command(idvfile, page_sequences)
 end
 -- else
   local pagesequence = table.concat(make_ranges(pages), ",")
   -- the stderr from dvisvgm must be redirected and postprocessed
   local dvisvgmlog = idvfile:gsub("idv$", "dlog")
   -- local dvisvgm = io.popen("dvisvgm -v4 -n --exact -c 1.15,1.15 -p " .. pagesequence .. " " .. idvfile, "r")
   local command = "dvisvgm -v4 " .. dvisvgm_options .. " -p " .. pagesequence .. " " .. idvfile .. " 2> " .. dvisvgmlog
   return command, {dvisvgmlog}
 -- end
end

local function execute_dvisvgm(idvfile, pages)
 if #pages < 1 then return nil, "No pages to convert" end
 local command, logs = prepare_command(idvfile, pages)
 log:info(command)
 os.execute(command)
 local generated_pages = {}
 for _, dvisvgmlog in ipairs(logs) do
   local output = read_log(dvisvgmlog)
   generated_pages = get_generated_pages(output, generated_pages)
 end
 return generated_pages
end

local function get_dvi_pages(arg)
 -- list of pages to convert in this run
 local to_convert = {}
 local idv_file = arg.input .. ".idv"
 -- set extension options
 local extoptions = mkutils.get_filter_settings "dvisvgm_hashes" or {}
 dvisvgm_options = arg.options or extoptions.options or dvisvgm_options
 parallel_size = arg.parallel_size or extoptions.parallel_size or parallel_size
 cpu_cnt = arg.cpu_cnt or extoptions.cpu_cnt or cpu_cnt
 dvisvgm_par.scale = arg.scale or extoptions.scale or 1.4
 dvisvgm_options = dvisvgm_options % dvisvgm_par
 make_command   = arg.make_command or extoptions.make_command or make_command
 test_make_command = arg.test_make_command or extoptions.test_make_command or test_make_command
 local f = io.open(idv_file, "rb")
 if not f then return nil, "Cannot open idv file: " .. idv_file end
 local content = f:read("*all")
 f:close()
 local dvi_pages = dvireader.get_pages(content)
 -- we must find page numbers and output name sfor the generated images
 local lg = mkutils.parse_lg(arg.input ..".lg", arg.builddir)
 for _, name in ipairs(lg.images) do
   local page = tonumber(name.page)
   local hash = dvi_pages[page]
   local tex4ht_name = name.output
   local output_name = make_hashed_name(arg.input, hash)
   output_map[tex4ht_name] = output_name
   if not mkutils.file_exists(output_name) then
     log:debug("output file: ".. output_name)
     to_convert[#to_convert+1] = page
   end
 end
 local generated_files, msg = execute_dvisvgm(idv_file, to_convert)
 if not generated_files then
   return nil, msg
 end

 -- rename the generated files to the hashed filenames
 for page, file in pairs(generated_files) do
   os.rename(file, make_hashed_name(arg.input, dvi_pages[page]))
 end

end

function M.test(format)
 -- ODT format doesn't support SVG
 if format == "odt" then return false end
 return true
end

function M.modify_build(make)
 -- this must be used in the .mk4 file as
 -- Make:dvisvgm_hashes {}
 make:add("dvisvgm_hashes", function(arg)
   get_dvi_pages(arg)
 end,
 {
 })

 -- insert dvisvgm_hashes command at the end of the build sequence -- it needs to be called after t4ht
 make:dvisvgm_hashes {}

 -- replace original image names with hashed names
 local executed = false
 make:match(".*", function(arg)
   if not executed then
     executed = true
     local lgfiles = make.lgfile.files
     for i, filename in ipairs(lgfiles) do
       local replace = output_map[filename]
       if replace then
         lgfiles[i] = replace
       end
     end
     -- tex4ebook process also the images table, so we need to replace generated filenames here as well
     local lgimages = make.lgfile.images
     for _, image in ipairs(lgimages) do
       local  replace = output_map[image.output]
       if replace then
         image.output = replace
       end
     end
   end
 end)

 -- fix src attributes
 local process = filter({
   function(str, filename)
     return str:gsub('src=["\'](.-)(["\'])', function(filename, endquote)
       local newname = output_map[filename] or filename
       log:debug("newname", newname)
       return 'src=' .. endquote .. newname  .. endquote
     end)
   end
 }, "dvisvgmhashes")

 make:match("htm.?$", process)

 -- disable the image processing
 for _,v in ipairs(make.build_seq) do
   if v.name == "t4ht" then
     local t4ht_par = v.params.t4ht_par or make.params.t4ht_par or ""
     v.params.t4ht_par = t4ht_par .. " -p"
   end
 end
 make:image(".", function() return "" end)
 return make
end

return M