# coding: utf-8
module Webize
  module Plaintext

    class Reader < RDF::Reader

      # IRC log -> RDF
      def chat_triples

        # irssi:
        #  /set autolog on
        #  /set autolog_path ~/web/%Y/%m/%d/%H/$tag.$0.irc
        # weechat:
        #  /set logger.mask.irc "%Y/%m/%d/%H/$server.$channel.irc"

        type = (SIOC + 'InstantMessage').R
        parts = @base.parts
        dirname = File.dirname @base.path
        network, channame = @base.basename.split '.'
        channame = Rack::Utils.unescape_path(channame).gsub('#','')
        day = parts[0..2].join('-') + 'T'
        hourslug = parts[0..3].join
        linkgroup = [nil, parts[0..2]].join('/') + '/#IRClinks'
        lines = 0
        ts = {}
        @doc.lines.grep(/^[^-]/).map{|msg|
          tokens = msg.split /\s+/
          time = tokens.shift
          if ['*','-!-'].member? tokens[0] # actions, joins, parts
            nick = tokens[1]
            msg = tokens[2..-1].join ' '
            msg = '/me ' + msg if tokens[0] == '*'
          elsif tokens[0].match? /^-.*:.*-$/ # notices
            nick = tokens[0][1..tokens[0].index(':')-1]
            msg = tokens[1..-1].join ' '
          elsif re = msg.match(/<[\s@+*]*([^>]+)>\s?(.*)?/)
            nick = re[1]
            msg = re[2]
          end
          nick = CGI.escape(nick || 'anonymous')
          timestamp = day + time
          subject = '#' + channame + hourslug + (lines += 1).to_s
          yield subject, Type, type
          ts[timestamp] ||= 0
          yield subject, Date, [timestamp, '%02d' % ts[timestamp]].join('.')
          ts[timestamp] += 1
          yield subject, To, @base
          creator = (dirname + '/*irc?q=' + nick + '&sort=date&view=table#' + nick).R
          yield subject, Creator, creator
          yield subject, Content, ['<pre>',
                                   msg.hrefs{|p,o| yield [Image,Video].member?(p) ? subject : linkgroup, p, o}, # cluster non-media links per channel for space-efficient layout
                                   '</pre>'].join if msg}
      end

      # twtxt -> RDF
      def twtxt_triples
        dirname = File.dirname @base.path
        @doc.lines.grep(/^[^#]/).map{|line|
          date, msg = line.split /\t/
          graph = @base.join (dirname == '/' ? '' : dirname) + '/twtxt.' + date.gsub(/\D/,'.')
          subject = graph.join '#msg'
          yield subject, Type, Post.R, graph
          yield subject, Date, date, graph
          yield subject, Content, Webize::HTML.format(msg.hrefs, @base), graph if msg
          yield subject, Creator, (@base.host + dirname).split(/\W/).join('.'), graph
          yield subject, To, @base, graph
        }
      end
    end
  end
end

class WebResource
  module HTML

    # board-post -> RDF
    def Forum doc
      doc.css(".entry, .message, [id^='post'], .post, .postCell, .post-container, .post_wrapper, .views-row").map{|post| # posts
        hasDate = hasAuthor = hasContent = false
        id = post.css('a.linkSelf, a.post_no, .entry-title a, .postNum a, .views-field-title a')[0]                # identifying anchor-element
        subject = join id ? id['href'] : '#'+(post['data-post-no']||post['id']||Digest::SHA2.hexdigest(post.to_s)) # identifying attribute
        graph = ['//', subject.host, subject.path&.sub(/\.html$/, ''),
                 '/', subject.fragment].join.R                                # store fragment-URI posts in thread container

        yield subject, Type, (SIOC + 'BoardPost').R, graph                    # RDF type

        post.css('[data-time], [data-utc], [datetime], [unixtime]').map{|date|# ISO8601/UNIX timestamp
          hasDate = true
          yield subject, Date, (date['datetime'] || Time.at((date['data-time'] || date['data-utc'] || date['unixtime']).to_i).iso8601), graph }

        post.css(".labelCreated, td.thead > div.normal, .postdate, span.datetime").map{|created|     # freeform timestamp
          if date = Chronic.parse(created.inner_text)
            hasDate = true
            yield subject, Date, date.iso8601, graph
          end}

        post.css('.author, .bigusername, .name, .post_author, .poster, .poster-name, .postername, .username').map{|name|
          hasAuthor = true
          yield subject, Creator, name.inner_text, graph }                    # author name

        post.css('a.bigusername, a.username, .author > a, .p-author > a, .poster a').map{|a| # author URI
          hasAuthor = true
          yield subject, Creator, (join a['href']), graph }

        post.css('.entry-title, .post-subject, .post_title, .subject, .title, .views-field-title').map{|subj|
          yield subject, Title, subj.inner_text, graph }                      # title

        post.css('img').map{|i| yield subject, Image, (join i['src']), graph} # image

        post.css('a.file-image[href], a.fileThumb[href], a.imgLink[href]').map{|a|
          yield subject, Image, (join a['href']), graph}                      # image reference

        post.css('.post_image, .post-image, img.thumb').map{|img|             # image reference on parent
          yield subject, Image, (join img.parent['href']), graph }

        post.css('img.multithumb, img.multithumbfirst').map{|img|             # image reference on parent's parent
          yield subject, Image, (join img.parent.parent['href']), graph }

        post.css('[href$="m4v"], [href$="mp4"], [href$="webm"]').map{|a|      # videos
          yield subject, Video, (join a['href']), graph }

        post.css('.comment-comments').map{|c|                                 # comment count
          if count = c.inner_text.strip.match(/^(\d+) comments$/)
            yield subject, 'https://schema.org/commentCount', count[1], graph
          end}

        post.css(".body, .e-content, .entry-content, .divMessage, .message, .messageContent, .message-body, .post-body, .postarea, .postbody, [id^='post_message'], .postMessage, .text, .views-field-body").map{|msg|
          hasContent = true
          msg.css('a[class^="ref"], a[onclick*="Reply"], .post-link, .quote_link, .quotelink, .quoteLink').map{|reply_of|
            yield subject, To, (join reply_of['href']), graph                 # reply-of references
            reply_of.remove}

          msg.traverse{|n|                                                    # references in text content
            if n.text? && n.to_s.match?(/https?:\/\//)
              n.add_next_sibling (CGI.unescapeHTML n.to_s).hrefs{|p,o| yield subject, p, o}
              n.remove
            end}

          yield subject, Content, (Webize::HTML.format msg, self), graph }    # message body

        post.remove if hasDate && hasAuthor && hasContent}                    # remove raw post-data HTML

      doc.css('#boardNavMobile, #delform, #absbot, #navtopright, #postForm, #postingForm, #actionsForm, #thread-interactions').map &:remove
    end

    MarkupPredicate['http://www.w3.org/1999/xhtml/vocab#role'] = -> roles, env {} # TODO where are these triples coming from?

    MarkupPredicate[Type] = -> types, env {
      types.map{|t|
        t = t.to_s unless t.class == WebResource
        t = t.R env
        {_: :a, href: t.href, c: Icons[t.uri] || t.display_name}.update(Icons[t.uri] ? {class: :icon} : {})}}

    MarkupPredicate[Creator] = MarkupPredicate[To] = MarkupPredicate['http://xmlns.com/foaf/0.1/maker'] = -> creators, env {
      creators.map{|creator|
        if [WebResource, RDF::URI].member? creator.class
          uri = creator.R env
          name = uri.display_name
          color = env[:colors][name] ||= '#%06x' % (rand 16777216)
          {_: :a, href: uri.href, class: :fromto, style: "background-color: #{color}; color: black", c: name}
        else
          markup creator, env
        end}}

    MarkupPredicate[Abstract] = -> abstracts, env {
      {class: :abstract, c: abstracts.map{|a|
         [(markup a, env), ' ']}}}

    MarkupPredicate[Schema + 'authToken'] = -> tokens, env {tokens.map{|t| '🪙 '}}

    MarkupPredicate[Schema + 'value'] = -> vs, env {
      vs.map{|v|
        if v.class == RDF::Literal && v.to_s.match?(/^(http|\/)\S+$/)
          v = v.to_s.R env                                             # cast literal to URI (erroneous upstream data)
          if v.uri.match? /\bmp3/
            Markup[Audio][v, env]
          else
            markup v, env
          end
        else
          markup v, env
        end}}

    MarkupPredicate[Title] = -> titles, env, uri=nil, id=nil {
      if uri
        blocked = uri.deny? && !LocalAllow.has_key?(uri.host)          # resource blocked?
        offsite = uri.host != env[:base].host                          # resource on another host?
        color = HostColors[uri.host] if offsite && HostColors.has_key?(uri.host) # resource color
        ptr = {_: :a, href: uri.href, id: 'ptr' + id, class: offsite ? 'uri title' : 'uri path'}
        ptr[:style] = "background-color: #{color}" if color
      end
      hasPointer = false
      [titles.map(&:to_s).map(&:strip).compact.-([""]).uniq.map{|title|
         unless env[:title] == title                                   # hide title if same as previous
           env[:title] = title
           {class: :title,                                             # render title
            c: [({_: :img, src: ['//',uri.host,'/favicon.ico'].join.R(env).href} if offsite),
                CGI.escapeHTML(title),
                ([' ', {_: :span, class: :uri, style: color ? "background-color: black; color: #{color}" : nil,
                        c: CGI.escapeHTML((offsite ? uri.to_s.sub(/^https?:..(www.)?/,'') : (uri.path || '/'))[0..127])}] if uri && !hasPointer),
               ]}.update((!uri || hasPointer || blocked) ? {} : (hasPointer = true; ptr))
         end},
       (ptr.update({c: '#', class: 'uri path ptr'}) if uri && !hasPointer && !blocked), # proxy-URI pointer
       ({_: :a, class: :pointer, c: '🔗', href: uri} if uri)]}         # canonical-URI pointer

    Markup[Resource] = -> re, env {
      re.delete Type if (re[Type] || []).map(&:to_s).find{|t| [Post, SIOC+'BoardPost', SIOC+'BlogPost', SIOC+'MicroPost'].member? t}
      if uri = re.delete('uri')                                        # no identifier for blank node
        uri = uri.R env                                                # global identifier
        id = uri.local_id                                              # local identifier
      end
      p = -> a {MarkupPredicate[a][re.delete(a),env] if re.has_key? a} # predicate renderer
      unless (re[Creator]||[]).find{|a| KillFile.member? a.to_s}       # sender killfiled?
        {class: (uri && uri.deny? && !LocalAllow.has_key?(uri.host)) ? 'blocked post' : :post, # resource
         c: [MarkupPredicate[Title][re.delete(Title)||[],env,uri,id],  # title
             p[Date],                                                  # timestamp
             ({_: :table, class: :fromto,                              # address fields
               c: {_: :tr,
                   c: [{_: :td, c: p[Creator], class: :from}, "\n",
                       {_: :td, c: '&rarr;'},
                       {_: :td, c: p[To], class: :to}, "\n"]}} unless !re.has_key?(Creator) && !re.has_key?(To)),
             p[Abstract],                                              # abstract
             p[Image],                                                 # image(s)
             {class: :body, c: [Content, SIOC+'richContent'].map{|p|(re.delete(p)||[]).map{|o|markup o,env}}}, # body
             p[Link],                                                  # untyped links
             (["<br>\n", HTML.keyval(re,env)] unless re.keys.size < 1) # keyval render remaining fields
            ]}.update(id ? {id: id} : {})                              # attach local identifier if existent
      end}

    Markup[Schema + 'InteractionCounter'] = -> counter, env {
      if type = counter[Schema+'interactionType']
        type = type[0].to_s
        icon = Icons[type] || type
      end
      {_: :span, class: :interactionCount,
       c: [{_: :span, class: :type, c: icon},
           {_: :span, class: :count, c: counter[Schema+'userInteractionCount']}]}}

  end
end
