structure AuxFile:
sig
  val createMissingDirectories:
    {auxfile: string, outdir: string, seen: StringSet.set}
    -> bool * StringSet.set
  val extractBibTeXLines: {auxfile: string, outdir: string} -> string list
end =
struct
  fun stripPrefix prefix s =
    if String.isPrefix prefix s then
      SOME (Substring.extract (s, String.size prefix, NONE))
    else
      NONE
  fun isInput (line, outdir) =
    case stripPrefix "\\@input{" line of
      NONE => NONE
    | SOME rest =>
        let
          val subauxfile = Substring.string
            (Substring.takel (fn c => c <> #"}") rest)
          val subauxfile_abs =
            PathUtil.abspath {path = subauxfile, cwd = SOME outdir}
        in
          SOME {subauxfile = subauxfile, subauxfile_abs = subauxfile_abs}
        end
  fun createMissingDirectories {auxfile, outdir, seen} =
    let
      val ins = TextIO.openIn auxfile
      val seen = StringSet.add (seen, auxfile)
      fun go (did, seen) =
        case TextIO.inputLine ins of
          NONE => (TextIO.closeIn ins; (did, seen))
        | SOME line =>
            case isInput (line, outdir) of
              NONE => go (did, seen)
            | SOME {subauxfile, subauxfile_abs} =>
                if FSUtil.isFile subauxfile_abs then
                  let
                    val (did', seen) =
                      createMissingDirectories
                        {auxfile = subauxfile_abs, outdir = outdir, seen = seen}
                  in
                    go (did orelse did', seen)
                  end
                else
                  let
                    val dir =
                      PathUtil.join2 (outdir, PathUtil.dirname subauxfile)
                  in
                    if FSUtil.isDirectory dir then go (did, seen)
                    else (FSUtil.mkDirRec dir; go (true, seen))
                  end
    in
      go (false, seen)
    end

  (* \citation, \bibdata, \bibstyle *)
  fun extractBibTeXLines' {auxfile, outdir, revLines} =
    let
      val ins = TextIO.openIn auxfile
      fun go revLines =
        case TextIO.inputLine ins of
          NONE => (TextIO.closeIn ins; revLines)
        | SOME line =>
            case isInput (line, outdir) of
              SOME {subauxfile, subauxfile_abs} =>
                if FSUtil.isFile subauxfile_abs then
                  let
                    val revLines =
                      extractBibTeXLines'
                        { auxfile = subauxfile_abs
                        , outdir = outdir
                        , revLines = revLines
                        }
                  in
                    go revLines
                  end
                else
                  go revLines
            | NONE =>
                let
                  val isBibTeXLine =
                    case stripPrefix "\\" line of
                      SOME s =>
                        (case
                           Substring.string
                             (Substring.takel
                                (fn c => Char.isAlpha c orelse c = #"@") s)
                         of
                           "citation" => true
                         | "bibdata" => true
                         | "bibstyle" => true
                         | _ => false)
                    | NONE => false
                in
                  if isBibTeXLine then
                    ( if Message.getVerbosity () >= 2 then
                        Message.info
                          ("BibTeX line: "
                           ^
                           Substring.string
                             (Substring.dropr Char.isSpace (Substring.full line)))
                      else
                        ()
                    ; go (line :: revLines)
                    )
                  else
                    go revLines
                end
    in
      go revLines
    end
  fun extractBibTeXLines {auxfile, outdir} =
    List.rev
      (extractBibTeXLines' {auxfile = auxfile, outdir = outdir, revLines = []})
end;
