require "execjs"
require "nokogiri"
require "net/http"
require "json"
require "cgi"
require "optparse"

class KatexRenderer
  def initialize(source)
    @context = ExecJS.compile(source)
    @inline_cache = {}
    @display_cache = {}
  end

  def render(display, string)
    cache = display ? @display_cache : @inline_cache
    comment = display ? "display" : string
    string = CGI.unescapeHTML string

    cache.fetch(string) do
      puts "  Rendering #{comment}"
      options = { "throwOnError" => false, "displayMode" => display }
      cache[string] = @context.call("katex.renderToString", string, options)
    end
  end

  def substitute(content)
    found_any = false
    rendered = content.gsub /\\\(((?:[^\\]|\\[^\)])*)\\\)/ do |match|
      found_any = true
      render(false, $~[1])
    end
    rendered = rendered.gsub /\$\$((?:[^\$]|$[^\$])*)\$\$/ do |match|
      found_any = true
      render(true, $~[1])
    end
    return rendered, found_any
  end
end

# Provided via this project's Gemfile
ExecJS.runtime = ExecJS::Runtimes::Duktape

katex = nil
OptionParser.new do |opts|
  opts.banner = "Usage: convert.rb [options]"

  opts.on("--katex-js-file=FILE", "Use the given KaTeX JS file to process LaTeX") do |f|
    katex = f
  end
end.parse!
files = ARGV

if katex
  katex = File.read(katex)
else
  katex = Net::HTTP.get(URI("https://static.danilafe.com/katex/katex.min.js"))
end

renderer = KatexRenderer.new(katex)
files.each do |file|
  puts "Rendering file: #{file}"
  document = Nokogiri::HTML.parse(File.open(file))
  found_any = false
  document.search('//*[not(ancestor-or-self::code or ancestor-or-self::script)]/text()').each do |t|
    rendered, found_any_in_text = renderer.substitute(t.content)
    found_any ||= found_any_in_text
    t.replace(rendered)
  end

  found_any ||= document.at('meta[name="needs-latex"]')

  # If we didn't find any mathematical equations, no need to include KaTeX CSS.
  unless found_any
    document.css('link[href$="katex.css"], link[href$="katex.min.css"]').each(&:remove)
  end

  File.write(file, document.to_html(encoding: 'UTF-8'))
end
