local mkutils = require "mkutils"
local log = logging.new("mjcli")
-- other possible value is page2svg
local mathnodepath = "mjcli"
-- options for MathJax command
local options = ""
-- math fonts position
-- don't alter fonts if not set
local fontdir = nil
-- if we copy fonts 
local fontdest = nil
local fontformat = "woff"
local cssfilename =  "mathjax-chtml.css"

local function compile(filename, options)
  -- local tmpfile = os.tmpname()
  log:info("Compile using MathJax")
  local command =  mathnodepath .. " ".. options .. " " .. filename
  log:info(command)
  local commandhandle, msg = io.popen(command,"r") 
  if not commandhandle then return nil, msg end
  local content = commandhandle:read("*all")
  commandhandle:close()
  return content
end

local saved_styles = {}
local used_styles = {}

local function make_css(saved_styles)
  -- this buffer contains lines of the new CSS file
  local buffer = {}
  -- process table with saved CSS rules and make CSS file again
  for _, rule in ipairs(saved_styles) do
    buffer[#buffer+1] = rule.selector .. " {"
    -- save CSS properties
    for _, line in ipairs(rule.content) do
      buffer[#buffer+1] = line
    end
    buffer[#buffer+1] = "}"
    buffer[#buffer+1] = "" -- add blank line
  end
  return table.concat(buffer, "\n")
end

-- MathJax generated CSS contains reusable declarations but also
-- declarations of fixes for elements in the current file
-- the idea is to reuse the common declarations and to save each
-- fix. 
local function parse_css(css, file_class)
  local status = "init"
  local current = {}
  local current_selector
  for line in css:gmatch("([^\n]+)") do
    if status == "init" then
      local selector, rest = line:match("%s*(.-)%s*{(.-)")
      if selector then 
        current_selector = selector
        -- if the current selector contains class, we must prepend the current file class
        -- as the joined CSS file could contain multiple rules with the same class otherwise
        if current_selector:match("%.") then current_selector = "." .. file_class .. " " .. current_selector end
        status = "record"
      end
    elseif status == "record" then
      if line:match("%}") then
        status = "init"
        if not used_styles[current_selector] then
          table.insert(saved_styles, {selector = current_selector, content = current})
        end
        current = {}
        used_styles[current_selector] = true
      else
        table.insert(current, line)
      end
    end

  end
  -- save combined CSS for all files
  return make_css(saved_styles)
end

local function make_file_class(name)
  -- clean the filename to make it safe as a class name
  return name:gsub("[%s%p%s]", "_")
end

-- set class attribute in the body element of the current file
-- this is necessary for the updated CSS file
local function set_body_class(content, file_class)
  content = content:gsub("<body(.-)>", function(body)
    if body:match("class") then
      -- add new class if there already is one
      body = body:gsub("(class.-[\"'])", "%1" .. file_class .. " ")
    else
      body = body .. ' class="' .. file_class .. '"'
    end
    return "<body" ..body ..">"
  end)
  return content
end


-- save the css code from the html page generated by MathJax
local function extract_css(contents, currentfilename)
  local css
  local filename = cssfilename
  local file_class = make_file_class(currentfilename)
  -- detect all <style> tags
  contents = contents:gsub('<style [^>]+>(.-)</style>', function(style)
    -- replace only the style for mathjax
    if style:match "mjx%-container" then
      css = parse_css(style, file_class)
      return '<link rel="stylesheet" type="text/css" href="'..filename ..'" />'
    end
  end)
  contents = set_body_class(contents, file_class)
  return filename, contents, css
end

-- Update the paths to fonts to use the local versions
local function use_fonts(css)
  local family_pattern = "font%-family:%s*(.-);.-%/([^%/]+)%.".. fontformat
  local family_build = "@font-face {font-family: %s; src: url('%s/%s.%s') format('%s')}"
  local fontdir = fontdir:gsub("/$","")
  css = css:gsub("(@font%-face%s*{.-})", function(face)
    if not face:match("url%(") then return face end
    -- print(face)
    local family, filename = face:match(family_pattern)
    log:info("use font: ",family, filename)
    local newfile = string.format("%s/%s.%s", fontdir, filename, fontformat)
    Make:add_file(newfile)
    return family_build:format(family, fontdir, filename, fontformat, fontformat)
    -- return face
  end)
  return css
end


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

return function(text, arguments)
  -- if arguments.prg then mathnodepath = arguments.prg end
  local extoptions = mkutils.get_filter_settings "mjcli" or {}
  local arguments = arguments or {}
  mathnodepath = arguments.prg or extoptions.prg or  mathnodepath
  local options      = arguments.options or extoptions.options or options
  fontdir      = arguments.fontdir or extoptions.fontdir or fontdir
  -- the following ne is unused ATM
  fontdest     = arguments.fontdest or extoptions.fontdest or fontdest
  fontformat   = arguments.fontformat or extoptions.fontformat or fontformat
  cssfilename  = arguments.cssfilename or extoptions.cssfilename or arguments.input .. "-mathjax.css"
  local is_latex = arguments.latex or extoptions.latex or false
  local filename = arguments.filename

  -- modify options to use LaTeX syntax by MathJax
  if is_latex then
    options = options .. " -l"
  end

  -- compile current html file with mathjax
  local newtext, msg = compile(filename, options)
  if not newtext then 
    log:error(msg)
    return text
  end
  -- save CSS to a standalone file
  local cssfile, newtext,  css = extract_css(newtext, filename)
  -- use local font files if fontdir is present
  if fontdir then
    css = use_fonts(css)
  end
  if css then
    save_css(cssfile, css)
    Make:add_file(cssfile)
    -- print(css)
    log:info("CSS file: " .. cssfile)
  end
  return newtext
end
