/+
- Name: Spine, Doc Reform [a part of]
  - Description: documents, structuring, processing, publishing, search
    - static content generator

  - Author: Ralph Amissah
    [ralph.amissah@gmail.com]

  - Copyright: (C) 2015 - 2022 Ralph Amissah, All Rights
    Reserved.

  - License: AGPL 3 or later:

    Spine (SiSU), a framework for document structuring, publishing and
    search

    Copyright (C) Ralph Amissah

    This program is free software: you can redistribute it and/or modify it
    under the terms of the GNU AFERO General Public License as published by the
    Free Software Foundation, either version 3 of the License, or (at your
    option) any later version.

    This program is distributed in the hope that it will be useful, but WITHOUT
    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
    FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
    more details.

    You should have received a copy of the GNU General Public License along with
    this program. If not, see [https://www.gnu.org/licenses/].

    If you have Internet connection, the latest version of the AGPL should be
    available at these locations:
    [https://www.fsf.org/licensing/licenses/agpl.html]
    [https://www.gnu.org/licenses/agpl.html]

  - Spine (by Doc Reform, related to SiSU) uses standard:
    - docReform markup syntax
      - standard SiSU markup syntax with modified headers and minor modifications
    - docReform object numbering
      - standard SiSU object citation numbering & system

  - Homepages:
    [https://www.doc_reform.org]
    [https://www.sisudoc.org]

  - Git
    [https://git.sisudoc.org/projects/?p=software/spine.git;a=summary]

+/
module doc_reform.io_out.html;
template outputHTML() {
  import
    std.file,
    std.outbuffer,
    std.uri,
    std.conv : to;
  import
    doc_reform.io_out,
    doc_reform.io_out.create_zip_file,
    doc_reform.io_out.xmls,
    doc_reform.io_out.xmls_css;
  mixin outputXHTMLs;
  @safe void scroll(D,M)(
    const        D    doc_abstraction,
                 M    doc_matters,
  ) {
    mixin spineRgxOut;
    auto xhtml_format = outputXHTMLs();
    static auto rgx = RgxO();
    string[] doc_html;
    string[] doc;
    string suffix = ".html";
    string previous_part = "";
    string delimit = "";
    foreach (part; doc_matters.has.keys_seq.scroll) {
      foreach (obj; doc_abstraction[part]) {
        delimit = xhtml_format.div_delimit(part, previous_part);
        string _txt = xhtml_format.special_characters_breaks_indents_bullets(obj);
        switch (obj.metainfo.is_of_part) {
        case "frontmatter":              assert(part == "head" || "toc");
          switch (obj.metainfo.is_of_type) {
          case "para":
            switch (obj.metainfo.is_a) {
            case "heading":
              doc_html ~= delimit ~ xhtml_format.heading_scroll(_txt, obj, doc_matters, suffix);
              break;
            case "toc":
              doc_html ~= xhtml_format.para_scroll(_txt, obj, doc_matters, suffix);
              break;
            default:
              { /+ debug +/
                if (doc_matters.opt.action.debug_do_html) {
                  writeln(__FILE__, ":", __LINE__, ": ", obj.metainfo.is_a);
                }
              }
              break;
            }
            break;
          default:
            { /+ debug +/
              if (doc_matters.opt.action.debug_do_html) {
                writeln(__FILE__, ":", __LINE__, ": ", obj.metainfo.is_of_type);
              }
            }
            break;
          }
          break;
        case "body":                     assert(part == "body" || "head");
          switch (obj.metainfo.is_of_type) {
          case "para":
            switch (obj.metainfo.is_a) {
            case "heading":
              doc_html ~= delimit ~ xhtml_format.heading_scroll(_txt, obj, doc_matters, suffix);
              break;
            case "para":
              doc_html ~= xhtml_format.para_scroll(_txt, obj, doc_matters, suffix);
              break;
            default:
              { /+ debug +/
                if (doc_matters.opt.action.debug_do_html) {
                  writeln(__FILE__, ":", __LINE__, ": ", obj.metainfo.is_a);
                }
              }
              break;
            }
            break;
          case "block":
            switch (obj.metainfo.is_a) {
            case "quote":
              doc_html ~= xhtml_format.quote_scroll(_txt, obj, doc_matters);
              break;
            case "group":
              doc_html ~= xhtml_format.group_scroll(_txt, obj, doc_matters);
              break;
            case "block":
              doc_html ~= xhtml_format.block_scroll(_txt, obj, doc_matters);
              break;
            case "poem":
              break;
            case "verse":
              doc_html ~= xhtml_format.verse_scroll(_txt, obj, doc_matters, suffix);
              break;
            case "code":
              doc_html ~= xhtml_format.code(_txt, obj, doc_matters);
              break;
            case "table":
              doc_html ~= xhtml_format.table(_txt, obj, doc_matters);
              break;
            default:
              { /+ debug +/
                if (doc_matters.opt.action.debug_do_html) {
                  writeln(__FILE__, ":", __LINE__, ": ", obj.metainfo.is_a);
                }
              }
              break;
            }
            break;
          default:
            { /+ debug +/
              if (doc_matters.opt.action.debug_do_html) {
                writeln(__FILE__, ":", __LINE__, ": ", obj.metainfo.is_of_type);
              }
            }
            break;
          }
          break;
        case "backmatter":
          assert(part == "endnotes" || "glossary" || "bibliography" || "bookindex" || "blurb" || "tail");
          switch (obj.metainfo.is_of_type) {
          case "para":
            switch (obj.metainfo.is_a) {
            case "heading":
              doc_html ~= delimit ~ xhtml_format.heading_scroll(_txt, obj, doc_matters, suffix);
              break;
            case "endnote":              assert(part == "endnotes");
              doc_html ~= xhtml_format.para_scroll(_txt, obj, doc_matters, suffix);
              break;
            case "glossary":             assert(part == "glossary");
              doc_html ~= xhtml_format.para_scroll(_txt, obj, doc_matters, suffix);
              break;
            case "bibliography":         assert(part == "bibliography");
              doc_html ~= xhtml_format.para_scroll(_txt, obj, doc_matters, suffix);
              break;
            case "bookindex":            assert(part == "bookindex");
              doc_html ~= xhtml_format.para_scroll(_txt, obj, doc_matters, suffix);
              break;
            case "blurb":                assert(part == "blurb");
              doc_html ~= xhtml_format.para_scroll(_txt, obj, doc_matters, suffix);
              break;
            case "tail":                 assert(part == "tail");
              doc_html ~= xhtml_format.para_scroll(_txt, obj, doc_matters, suffix);
              break;
            default:
              { /+ debug +/
                if (doc_matters.opt.action.debug_do_html) {
                  writeln(__FILE__, ":", __LINE__, ": ", obj.metainfo.is_a);
                }
              }
              break;
            }
            break;
          default:
            { /+ debug +/
              if (doc_matters.opt.action.debug_do_html) {
                writeln(__FILE__, ":", __LINE__, ": ", obj.metainfo.is_of_type);
              }
            }
            break;
          }
          break;
        case "comment":
          break;
        default:
          { /+ debug +/
            if (doc_matters.opt.action.debug_do_html) {
              writeln(__FILE__, ":", __LINE__, ": ", obj.metainfo.is_of_part);
              writeln(__FILE__, ":", __LINE__, ": ", obj.metainfo.is_a);
              writeln(__FILE__, ":", __LINE__, ": ", obj.text);
            }
          }
          break;
        }
      }
    }
    doc = xhtml_format.html_head(doc_matters, "scroll")
      ~ doc_html
      ~ xhtml_format.dom_close
      ~ xhtml_format.tail(doc_matters);
    scroll_write_output(doc, doc_matters);
  }
  @trusted void scroll_write_output(D,M)(
    D doc,
    M doc_matters,
  ) {
    debug(asserts) {
      static assert(is(typeof(doc)    == string[]));
    }
    auto pth_html = spinePathsHTML!()(doc_matters.output_path, doc_matters.src.language);
    try {
      if (!exists(pth_html.base)) {
        pth_html.base.mkdirRecurse;
      }
      auto f = File(pth_html.fn_scroll(doc_matters.src.filename), "w");
      foreach (o; doc) {
        f.writeln(o);
      }
    } catch (ErrnoException ex) {
      // Handle error
    }
    if (doc_matters.opt.action.vox_gt0) {
      writeln(" ", pth_html.fn_scroll(doc_matters.src.filename));
    }
  }
  @safe void seg(D,M)(
    const D    doc_abstraction,
          M    doc_matters,
  ) {
    mixin spineRgxOut;
    static auto rgx = RgxO();
    auto xhtml_format = outputXHTMLs();
    string[][string] doc_html;
    string[][string] doc_html_endnotes;
    string[] doc;
    string segment_filename;
    string[] top_level_headings = ["","","",""];
    string previous_seg_filename = "";
    string suffix = ".html";
    string previous_part = "";
    string delimit = "";
    foreach (part; doc_matters.has.keys_seq.seg) {
      foreach (obj; doc_abstraction[part]) {
        delimit = xhtml_format.div_delimit(part, previous_part);
        string _txt = xhtml_format.special_characters_breaks_indents_bullets(obj);
        if (obj.metainfo.is_a == "heading") {
          assert(part == "head" || "toc" || "body" || "endnotes" || "glossary" || "bibliography" || "bookindex" || "blurb" || "tail");
          switch (obj.metainfo.heading_lev_markup) {
          case 0: .. case 3:
            /+ fill buffer, and replace with new levels from 1 to 3 +/
            switch (obj.metainfo.heading_lev_markup) {
            case 0:
              top_level_headings[0] = "";
              top_level_headings[1] = "";
              top_level_headings[2] = "";
              top_level_headings[3] = "";
              goto default;
            case 1:
              top_level_headings[1] = "";
              top_level_headings[2] = "";
              top_level_headings[3] = "";
              goto default;
            case 2:
              top_level_headings[2] = "";
              top_level_headings[3] = "";
              goto default;
            case 3:
              top_level_headings[3] = "";
              goto default;
            default:
              Tuple!(string, string[]) t = xhtml_format.heading_seg(_txt, obj, doc_matters, suffix, "seg");
              top_level_headings[obj.metainfo.heading_lev_markup] = t[0];
              break;
            }
            break;
          case 4:
            segment_filename = obj.tags.segment_anchor_tag_epub;
            doc_html[segment_filename] ~= xhtml_format.html_head(doc_matters, "seg");
            auto navigation_bar = xhtml_format.nav_pre_next_svg(obj, doc_matters);
            doc_html[segment_filename] ~= navigation_bar.toc_pre_next;
            previous_seg_filename = segment_filename;
            foreach (top_level_heading; top_level_headings) {
              doc_html[segment_filename] ~= top_level_heading;
            }
            Tuple!(string, string[]) t = xhtml_format.heading_seg(_txt, obj, doc_matters, suffix, "seg");
            doc_html[segment_filename] ~= t[0].to!string;
            doc_html[segment_filename] ~= xhtml_format.lev4_heading_subtoc(obj, doc_matters);
            doc_html_endnotes[segment_filename] ~= t[1];
            break;
          case 5: .. case 7:
            Tuple!(string, string[]) t = xhtml_format.heading_seg(_txt, obj, doc_matters, suffix, "seg");
            doc_html[segment_filename] ~= t[0].to!string;
            doc_html_endnotes[segment_filename] ~= t[1];
            break;
          case 8: .. case 9:
            { /+ debug +/
              if (doc_matters.opt.action.debug_do_html) {
                writeln(__FILE__, ":", __LINE__, ": ", obj.metainfo.is_a, ": ", obj.metainfo.heading_lev_markup);
                writeln(__FILE__, ":", __LINE__, ": ", obj.text);
              }
            }
            break;
          default:
            { /+ debug +/
              if (doc_matters.opt.action.debug_do_html) {
                writeln(__FILE__, ":", __LINE__, ": ", obj.metainfo.is_a, ": ", obj.metainfo.heading_lev_markup);
              }
            }
            break;
          }
        } else {
          assert(part == "head" || "toc" || "body" || "endnotes" || "glossary" || "bibliography" || "bookindex" || "blurb" || "tail");
          Tuple!(string, string[]) t;
          switch (obj.metainfo.is_of_part) {
          case "frontmatter":             assert(part == "head" || "toc");
            switch (obj.metainfo.is_of_type) {
            case "para":
              switch (obj.metainfo.is_a) {
              case "toc":
                t = xhtml_format.para_seg(_txt, obj, doc_matters, suffix, "seg");
                doc_html[segment_filename] ~= t[0].to!string;
                break;
              default:
                { /+ debug +/
                  if (doc_matters.opt.action.debug_do_html) {
                    writeln(__FILE__, ":", __LINE__, ": ", obj.metainfo.is_a);
                  }
                }
                break;
              }
              break;
            default:
              { /+ debug +/
                if (doc_matters.opt.action.debug_do_html) {
                  writeln(__FILE__, ":", __LINE__, ": ", obj.metainfo.is_a);
                }
              }
              break;
            }
            break;
          case "body":                    assert(part == "body");
            switch (obj.metainfo.is_of_type) {
            case "para":
              switch (obj.metainfo.is_a) {
              case "para":
                t = xhtml_format.para_seg(_txt, obj, doc_matters, suffix, "seg");
                doc_html[segment_filename] ~= t[0].to!string;
                doc_html_endnotes[segment_filename] ~= t[1];
                break;
              default:
                { /+ debug +/
                  if (doc_matters.opt.action.debug_do_html) {
                    writeln(__FILE__, ":", __LINE__, ": ", obj.metainfo.is_a);
                  }
                }
                break;
              }
              break;
            case "block":
              switch (obj.metainfo.is_a) {
              case "quote":
                t = xhtml_format.quote_seg(_txt, obj, doc_matters, suffix, "seg");
                goto default;
              case "group":
                t = xhtml_format.group_seg(_txt, obj, doc_matters, suffix, "seg");
                goto default;
              case "block":
                t = xhtml_format.block_seg(_txt, obj, doc_matters, suffix, "seg");
                goto default;
              case "poem":
                break;
              case "verse":
                t = xhtml_format.verse_seg(_txt, obj, doc_matters, suffix, "seg");
                goto default;
              case "code":
                doc_html[segment_filename] ~= xhtml_format.code(_txt, obj, doc_matters);
                break;
              case "table":
                doc_html[segment_filename] ~= xhtml_format.table(_txt, obj, doc_matters);
                doc_html_endnotes[segment_filename] ~= "";
                break;
              default:
                if ((obj.metainfo.is_a == "quote"
                  || obj.metainfo.is_a == "group"
                  || obj.metainfo.is_a == "block"
                  || obj.metainfo.is_a == "verse"
                )) {
                  doc_html[segment_filename] ~= t[0].to!string;
                  doc_html_endnotes[segment_filename] ~= t[1];
                } else { /+ debug +/
                  if (doc_matters.opt.action.debug_do_html) {
                    writeln(__FILE__, ":", __LINE__, ": ", obj.metainfo.is_a);
                  }
                }
                break;
              }
              break;
            default:
              { /+ debug +/
                if (doc_matters.opt.action.debug_do_html) {
                  writeln(__FILE__, ":", __LINE__, ": ", obj.metainfo.is_of_type);
                }
              }
              break;
            }
            break;
          case "backmatter":
            assert(part == "endnotes" || "glossary" || "bibliography" || "bookindex" || "blurb" || "tail");
            switch (obj.metainfo.is_of_type) {
            case "para":
              switch (obj.metainfo.is_a) {
              case "endnote":             assert(part == "endnotes");
                t = xhtml_format.para_seg(_txt, obj, doc_matters, suffix, "seg");
                doc_html[segment_filename] ~= t[0];
                break;
              case "glossary":            assert(part == "glossary");
                t = xhtml_format.para_seg(_txt, obj, doc_matters, suffix, "seg");
                doc_html[segment_filename] ~= t[0];
                doc_html_endnotes[segment_filename] ~= t[1];
                break;
              case "bibliography":        assert(part == "bibliography");
                t = xhtml_format.para_seg(_txt, obj, doc_matters, suffix, "seg");
                doc_html[segment_filename] ~= t[0];
                doc_html_endnotes[segment_filename] ~= t[1];
                break;
              case "bookindex":           assert(part == "bookindex");
                t = xhtml_format.para_seg(_txt, obj, doc_matters, suffix, "seg");
                doc_html[segment_filename] ~= t[0];
                doc_html_endnotes[segment_filename] ~= t[1];
                break;
              case "blurb":               assert(part == "blurb");
                t = xhtml_format.para_seg(_txt, obj, doc_matters, suffix, "seg");
                doc_html[segment_filename] ~= t[0];
                doc_html_endnotes[segment_filename] ~= t[1];
                break;
              case "tail":                assert(part == "tail");
                t = xhtml_format.para_seg(_txt, obj, doc_matters, suffix, "seg");
                doc_html[segment_filename] ~= t[0];
                doc_html_endnotes[segment_filename] ~= t[1];
                break;
              default:
                { /+ debug +/
                  if (doc_matters.opt.action.debug_do_html) {
                    writeln(__FILE__, ":", __LINE__, ": ", obj.metainfo.is_a);
                  }
                }
                break;
              }
              break;
            default:
              { /+ debug +/
                if (doc_matters.opt.action.debug_do_html) {
                  writeln(__FILE__, ":", __LINE__, ": ", obj.metainfo.is_of_type);
                }
              }
              break;
            }
            break;
          case "comment":
            break;
          default:
            { /+ debug +/
              if (doc_matters.opt.action.debug_do_html) {
                writeln(__FILE__, ":", __LINE__, ": ", obj.metainfo.is_of_part);
              }
            }
            break;
          }
        }
      }
    }
    seg_write_output(doc_html, doc_html_endnotes, doc_matters);
  }
  @trusted void seg_write_output(D,E,M)( // @system?
    D doc_html,
    E doc_html_endnotes,
    M doc_matters,
  ) {
    debug(asserts) {
      static assert(is(typeof(doc_html)      == string[][string]));
    }
    mixin spineRgxOut;
    static auto rgx = RgxO();
    auto pth_html = spinePathsHTML!()(doc_matters.output_path, doc_matters.src.language);
    auto xhtml_format = outputXHTMLs();
    auto m = doc_matters.src.filename.matchFirst(rgx.src_fn);
    try {
      if (!exists(pth_html.seg(doc_matters.src.filename))) {
        pth_html.seg(doc_matters.src.filename).mkdirRecurse;
      }
      foreach (seg_filename; doc_matters.has.segnames_lv4) {
        auto f = File(pth_html.fn_seg(doc_matters.src.filename, seg_filename), "w");
        foreach (docseg; doc_html[seg_filename]) {
          f.writeln(docseg);
        }
        foreach (docseg; doc_html_endnotes[seg_filename]) {
          f.writeln(docseg);
        }
        f.writeln(xhtml_format.tail(doc_matters));
      }
    } catch (ErrnoException ex) {
      // handle error
    }
    if (doc_matters.opt.action.vox_gt0) {
      writeln(" ", pth_html.fn_seg(doc_matters.src.filename, "toc"));
    }
  }
  @safe void css(M)(M doc_matters) {
    auto css = spineCss(doc_matters);
    auto pth_html = spinePathsHTML!()(doc_matters.output_path, doc_matters.src.language);
    try {
      if (!exists(pth_html.css)) {
        (pth_html.css).mkdirRecurse;
      }
      auto f = File(pth_html.fn_seg_css, "w");
      f.writeln(css.html_seg);
      f = File(pth_html.fn_scroll_css, "w");
      f.writeln(css.html_scroll);
    } catch (ErrnoException ex) {
      // Handle error
    }
  }
  @trusted void images_cp(M)( // @system
    M    doc_matters,
  ) {
    { /+ (copy html images) +/
      auto pth_html = spinePathsHTML!()(doc_matters.output_path, doc_matters.src.language);
      if (!exists(pth_html.image)) {
        pth_html.image.mkdirRecurse;
      }
      foreach (image; doc_matters.srcs.image_list) {
        auto fn_src_in = doc_matters.src.image_dir_path ~ "/" ~ image;
        auto fn_src_out = pth_html.image ~ "/" ~ image;
        debug(images_html) {
          writeln(fn_src_in, " -> ", fn_src_out);
        }
        if (exists(fn_src_in)) {
          fn_src_in.copy(fn_src_out);
        } else {
          if (doc_matters.opt.action.vox_gt0) {
            writeln("WARNING image not found: ", fn_src_in);
          }
        }
      }
    }
  }
}