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.edition "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 << Asciidoctor::Standoc::Utils::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 << Asciidoctor::Standoc::Utils::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 << Asciidoctor::Standoc::Utils::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 << Asciidoctor::Standoc::Utils::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 << Asciidoctor::Standoc::Utils::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 id_args(node, dn0)
        {
          id: dn0,
          series: node.attr("series"),
          revision: node.attr("revision"),
          vol: node.attr("volume"),
          stage: node.attr("status") || node.attr("docstage"),
          iter: node.attr("iteration"),
          date: /^draft/.match(node.attr("status") || node.attr("docstage")) ?
          (node.attr("circulated-date") || node.attr("revdate")) :
          node.attr("updated-date")
        }
      end

      def metadata_id_compose(node, xml, dn0)
        return unless dn0
        args = id_args(node, dn0)
        xml.docidentifier add_id_parts(args, false), **attr_code(type: "NIST")
        xml.docidentifier add_id_parts(args, true),
          **attr_code(type: "nist-long")
        xml.docidentifier add_id_parts_mr(args), **attr_code(type: "nist-mr")
      end

      def MMMddyyyy(isodate)
        return nil if isodate.nil?
        Date.parse(isodate).strftime("%B %d, %Y")
      end

      def add_id_parts(args, long)
        vol_delim = " Volume "
        ed_delim = " Revision "
        args[:series] and series_name = long ?
          SERIES.dig(args[:series].to_sym) :
          SERIES_ABBR.dig(args[:series].to_sym)
        dn = (series_name || "NIST #{args[:series]}")  + " " + args[:id]
        dn += "#{vol_delim}#{args[:vol]}" if args[:vol]
        dn += "," if args[:vol] && args[:revision]
        dn += "#{ed_delim}#{args[:revision]}" if args[:revision]
        stage = IsoDoc::NIST::Metadata.new(nil, nil, {}).stage_abbr(args[:stage], args[:iter])
        dn += " (#{stage})" if stage
        dn += " (#{MMMddyyyy(args[:date])})" if args[:date]
        dn
      end

      def add_id_parts_mr(args)
        args[:series] and
          name = SERIES_ABBR.dig(args[:series].to_sym).sub(/^NIST /, "")
        "NIST.#{name}.#{args[:vol]}.#{args[:revision]}.#{args[:date]}"
      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") || node.attr("docstage") || "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_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" }
            metadata_superseding_ids(b, xml)
            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_ids(b, xml)
        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" }
      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-issued-date") and
          b.date **{ type: "issued" } do |d|
          d.on cdate
        end
      end

      def metadata_superseding_titles(b, node)
        if node.attr("superseding-title")
          b.title Asciidoctor::Standoc::Utils::asciidoc_sub(node.attr("superseding-title")),
            **{ type: "main" }
          node.attr("superseding-subtitle") and
            b.title Asciidoctor::Standoc::Utils::asciidoc_sub(node.attr("superseding-subtitle")),
            **{ type: "subtitle" }
        else
          b.title Asciidoctor::Standoc::Utils::asciidoc_sub(node.attr("title-main") || node.title),
            **{ type: "main" }
          node.attr("title-sub") and
            b.title Asciidoctor::Standoc::Utils::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_ext(node, xml)
        metadata_doctype(node, xml)
        metadata_committee(node, xml)
        metadata_commentperiod(node, xml)
      end
    end
  end
end