require "asciidoctor"
require "asciidoctor/standoc/converter"
require "fileutils"

module Asciidoctor
  module NIST

    # A {Converter} implementation that generates RSD output, and a document
    # schema encapsulation of the document for validation
    #
    class Converter < Standoc::Converter

      def datetypes
        super + %w(abandoned superseded)
      end

      def metadata_version(node, xml)
        xml.edition node.attr("edition") if node.attr("edition")
        xml.revision node.attr("revision") if node.attr("revision")
        xml.version do |v|
          v.revision_date node.attr("revdate") if node.attr("revdate")
          v.draft node.attr("draft") if node.attr("draft")
        end
      end

      def title_subtitle(node, t, at)
        return unless node.attr("title-sub")
        t.title(**attr_code(at.merge(type: "subtitle"))) do |t1|
          t1 << asciidoc_sub(node.attr("title-sub"))
        end
        node.attr("title-sub-short") and
          t.title(**attr_code(at.merge(type: "short-subtitle"))) do |t1|
          t1 << asciidoc_sub(node.attr("title-sub-short"))
        end
      end

      def title_document_class(node, t, at)
        return unless node.attr("title-document-class")
        t.title(**attr_code(at.merge(type: "document-class"))) do |t1|
          t1 << asciidoc_sub(node.attr("title-document-class"))
        end
      end

      def title_main(node, t, at)
        t.title(**attr_code(at.merge(type: "main"))) do |t1|
          t1 << asciidoc_sub(node.attr("title-main") || node.title)
        end
        node.attr("title-main-short") and
          t.title(**attr_code(at.merge(type: "short-title"))) do |t1|
          t1 << asciidoc_sub(node.attr("title-main-short"))
        end
      end

      def title(node, xml)
        ["en"].each do |lang|
          at = { language: lang, format: "text/plain" }
          title_main(node, xml, at)
          title_subtitle(node, xml, at)
          title_document_class(node, xml, at)
        end
      end

      def metadata_id(node, xml)
        did = node.attr("docidentifier")
        dn = node.attr("docnumber")
        if did
          xml.docidentifier did, **attr_code(type: "nist")
          xml.docidentifier unabbreviate(did), **attr_code(type: "nist-long")
        else
          metadata_id_compose(node, xml, dn)
        end
        xml.docnumber node.attr("docnumber")
      end

      def unabbreviate(did)
        SERIES_ABBR.each { |k, v| did = did.sub(/^#{v} /, "#{k} ") }
        SERIES.each { |k, v| did = did.sub(/^#{k} /, "#{v} ") }
        did
      end

      def metadata_id_compose(node, xml, dn0)
        return unless dn0
        s = node.attr("series")
        e = node.attr("revision")
        v = node.attr("volume")
        xml.docidentifier add_id_parts(dn0, s, e, v, false),
          **attr_code(type: "nist")
        xml.docidentifier add_id_parts(dn0, s, e, v, true),
          **attr_code(type: "nist-long")
        xml.docidentifier add_id_parts_mr(dn0, s, e, v, node.attr("revdate")),
          **attr_code(type: "nist-mr")
      end

      def add_id_parts(dn, series, revision, vol, long)
        vol_delim = " Volume "
        ed_delim = " Revision "
        series and series_name = long ? SERIES.dig(series.to_sym) :
          SERIES_ABBR.dig(series.to_sym)
        dn = (series_name || "NIST #{series}")  + " " + dn
        dn += "#{vol_delim}#{vol}" if vol
        dn += "," if vol && revision
        dn += "#{ed_delim}#{revision}" if revision
        dn
      end

      def add_id_parts_mr(dn, series, revision, vol, revdate)
        series and name = SERIES_ABBR.dig(series.to_sym).sub(/^NIST /, "")
        "NIST.#{name}.#{vol}.#{revision}.#{revdate}"
      end

      def metadata_author(node, xml)
        personal_author(node, xml)
      end

      def metadata_publisher(node, xml)
        xml.contributor do |c|
          c.role **{ type: "publisher" }
          c.organization do |a|
            a.name "NIST"
            d = node.attr("nist-division") and a.subdivision d
          end
        end
      end

      def metadata_committee(node, xml)
        return unless node.attr("technical-committee") ||
          node.attr("subcommittee") ||
          node.attr("workgroup") || node.attr("workinggroup")
        xml.editorialgroup do |a|
          node.attr("technical-committee") and
            a.committee(node.attr("technical-committee"))
          node.attr("subcommittee") and
            a.subcommittee(node.attr("subcommittee"),
                           **attr_code(type: node.attr("subcommittee-type"),
                                       number: node.attr("subcommittee-number")))
          (node.attr("workgroup") || node.attr("workinggroup")) and
            a.workgroup(node.attr("workgroup") || node.attr("workinggroup"),
                        **attr_code(type: node.attr("workgroup-type"),
                                    number: node.attr("workgroup-number")))
        end
      end

      def metadata_status(node, xml)
        status = node.attr("status") || "final"
        xml.status do |s|
          s.stage status 
          s.substage (node.attr("substage") || "active")
          s.iteration node.attr("iteration") if node.attr("iteration") 
        end
      end

      def metadata_copyright(node, xml)
        from = node.attr("copyright-year") || node.attr("copyrightyear") ||
          Date.today.year
        xml.copyright do |c|
          c.from from
          c.owner do |owner|
            owner.organization do |o|
              o.name "NIST"
            end
          end
        end
      end

      def metadata_keywords(node, xml)
        return unless node.attr("keywords")
        node.attr("keywords").split(/,[ ]*/).each do |kw|
          xml.keyword kw
        end
      end

      def metadata_source(node, xml)
        super
        node.attr("doc-email") && xml.uri(node.attr("doc-email"), type: "email")
        node.attr("doi") && xml.uri(node.attr("doi"), type: "doi")
      end

      def metadata_series(node, xml)
        series = node.attr("series")
        series || return
        series and xml.series **{ type: "main" } do |s|
          s.title (SERIES.dig(series.to_sym) || series)
          SERIES_ABBR.dig(series.to_sym) and
            s.abbreviation SERIES_ABBR.dig(series.to_sym)
        end
      end

      def metadata_commentperiod(node, xml)
        from = node.attr("comment-from") or return
        to = node.attr("comment-to")
        extended = node.attr("comment-extended")
        xml.commentperiod do |c|
          c.from from
          c.to to if to
          c.extended extended if extended
        end
      end

      def relaton_relations
        super + %w(obsoletes obsoleted-by supersedes superseded-by)
      end

      def metadata_getrelation(node, xml, type)
        if type == "obsoleted-by" and node.attr("superseding-status")
          metadata_superseding_doc(node, xml)
        else
          super
        end
      end

      def metadata_superseding_doc(node, xml)
        xml.relation **{ type: "obsoletedBy" } do |r|
          r.bibitem do |b|
            metadata_superseding_titles(b, node)
            doi = node.attr("superseding-doi") and
              b.uri doi, **{ type: "doi" }
            url = node.attr("superseding-url") and
              b.uri url, **{ type: "uri" }
            did = xml&.parent&.at("./ancestor::bibdata/docidentifier"\
                                  "[@type = 'nist']")&.text
            didl = xml&.parent&.at("./ancestor::bibdata/docidentifier"\
                                   "[@type = 'nist-long']")&.text
            b.docidentifier did, **{ type: "nist" }
            b.docidentifier didl, **{ type: "nist-long" }
            metadata_superseding_authors(b, node)
            metadata_superseding_dates(b, node)
            b.status do |s|
              s.stage node.attr("superseding-status")
              iter = node.attr("superseding-iteration") and
                s.iteration iter
            end
          end
        end
      end

      def metadata_superseding_dates(b, node)
        cdate = node.attr("superseding-circulated-date") and
          b.date **{ type: "circulated" } do |d|
          d.on cdate
        end
        cdate = node.attr("superseding-published-date") and
          b.date **{ type: "published" } do |d|
          d.on cdate
        end
      end

      def metadata_superseding_titles(b, node)
        if node.attr("superseding-title")
          b.title asciidoc_sub(node.attr("superseding-title")),
            **{ type: "main" }
          node.attr("superseding-subtitle") and
            b.title asciidoc_sub(node.attr("superseding-subtitle")),
            **{ type: "subtitle" }
        else
          b.title asciidoc_sub(node.attr("title-main") || node.title),
            **{ type: "main" }
          node.attr("title-sub") and
            b.title asciidoc_sub(node.attr("title-sub")), **{ type: "subtitle" }
        end
      end

      def metadata_superseding_authors(b, node)
        node.attr("superseding-authors") and
          node.attr("superseding-authors").split(/,\s*/).each do |a|
          b.contributor do |c|
            c.role nil, **{ type: "author" }
            c.person do |p|
              p.name do |f|
                f.completename a
              end
            end
          end
        end
      end

      def metadata_note(node, xml)
        note = node.attr("bib-additional-note") and
          xml.note note, **{ type: "additional-note" }
        note = node.attr("bib-withdrawal-note") and
          xml.note note, **{ type: "withdrawal-note" }
        note = node.attr("bib-withdrawal-announcement-link") and
          xml.note note, **{ type: "withdrawal-announcement-link" }
      end

      def metadata(node, xml)
        super
        metadata_series(node, xml)
        metadata_keywords(node, xml)
        metadata_commentperiod(node, xml)
      end
    end
  end
end