#!/usr/bin/env ruby
PAGETITLE = "Potential ASF Member Watch List" # Wvisible:members
$LOAD_PATH.unshift '/srv/whimsy/lib'

require 'wunderbar'
require 'whimsy/asf'
require 'whimsy/asf/member-files'
require 'nokogiri'
require 'date'
require 'wunderbar/bootstrap'
require 'wunderbar/jquery/stupidtable'

_html do
  _head_ do
    _base href: File.dirname(ENV['SCRIPT_NAME'])
  end

  _body? do
    _whimsy_body(
      title: PAGETITLE,
      related: {
        '/members/memberless-pmcs.cgi' => 'PMCs with no/few ASF Members',
        '/members/nominate_member.cgi' => 'Nominate someone for ASF Member',
        '/members/check_membernoms.cgi' => 'Cross-check existing New Member Nominations',
        ASF::SVN.svnpath!('Meetings') => 'Official Meeting Agenda Directory'
      },
      helpblock: -> {
        _ 'To help evaluate potential Member candidates, here are a number of ways to see where non-Members are participating broadly at the ASF.'
        _ 'The table(s) below include non-Members who are chairs, widely active, have been nominated, or other criteria (depending on this URL).'
      }
    ) do
      # start with the Watch List itself
      watch_list = ASF::Person.member_watch_list.keys # array of Person entries
      meeting = ASF::MemberFiles.latest_meeting

      nominations = ASF::MemberFiles.member_nominees

      # determine which list to report on, based on the URI
      request = ENV['REQUEST_URI']

      _div.row do
        _div.col_sm10 do
          _div.panel.panel_primary do
            _div.panel_heading {_h3.panel_title 'Related Links'}
            _div.panel_body do
              _ul do
                if Time.new.strftime('%Y%m%d') < File.basename(meeting)
                  _li do
                    _a 'Cross-check existing New Member nominations', href: '/members/check_membernoms.cgi'
                  end
                else
                  unless request =~ /appstatus/
                    _li do
                      _a 'Application Status', href: 'members/watch/appstatus'
                    end
                  end
                end

                if request =~ %r{/watch/\w} # only show main link for a sub-page
                  _li do
                    _a 'Potential Member Watch List', href: 'members/watch'
                  end
                end

                unless request =~ /nominees/
                  _li do
                    _a 'Nominees', href: 'members/watch/nominees'
                  end
                end

                unless request =~ /multiple/
                  _li do
                    _a 'Active in Multiple (>=3) PMCs', href: 'members/watch/multiple'
                  end
                end

                unless request =~ /chairs/
                  _li do
                    _a 'Non-member PMC chairs', href: 'members/watch/chairs'
                  end
                end

                unless request =~ /mentors/
                  _li do
                    _a 'Non-member incubator mentors', href: 'members/watch/mentors'
                  end
                end

                _li do
                  _a 'PMCs with no/few members', href: 'members/memberless-pmcs'
                end
              end
            end
          end
        end
      end

      list = {} # Avoid lint errors of shadowing
      if request =~ /multiple/
        _h2_ 'Active In Multiple Committees'
        # Use actual PMCs rather than LDAP derived
        list = ASF::Committee.pmcs.map {|pmc| pmc.roster.keys}.
          reduce(&:+).group_by {|uid| uid}.
          delete_if {|_, lst| lst.length < 3}.
          map {|uid, _| ASF::Person.find(uid)}
        list -= ASF.members
      elsif request =~ /chairs/
        _h2_ 'PMC Chairs'
        list = ASF.pmc_chairs
        list -= ASF.members
      elsif request =~ /mentors/
        _h2_ 'Incubator Mentors'
        list = ASF::Podling.current.map(&:mentors).flatten.
          uniq.map {|id| ASF::Person.find(id)}
        list -= ASF.members
      elsif request =~ /nominees/
        _h2_ 'Member Nominees'
        # Keep only ids that are not current members
        list = nominations.filter_map do |id, info|
          if id.start_with? 'n/a_'
            p = ASF::Person.new(id)
            p.attrs['cn'] = info['Public Name'] # public name
            p
          else
            p = ASF::Person.find(id)
            p if p.asf_member_status != :current # drop current members
          end
        end
      elsif request =~ /appstatus/
        _h2_ 'Elected Members - Application Status'
        nanum = 0
        status = {} # status keyed by id or notinavail_n
        list = [] # list of People entries
        File.read(File.join(meeting, 'memapp-received.txt')).
          scan(/^(yes|no)\s+(yes|no)\s+(yes|no)\s+(yes|no)\s+(\S+)\s+(\S.+)/).each do |tokens|
            name = tokens.pop
            id = tokens.pop
            if id == 'n/a'
              nanum += 1
              id = "notinavail_#{nanum+=1}"
              p = ASF::Person.new(id)
              p.attrs['cn'] = name # public name
              list << p
            else
              list << ASF::Person.find(id)
            end
            status[id] = tokens
          end
        _p do
          applied = status.filter_map {|k, v| list.find{|p| p.name == k}.public_name.to_s if v[1] == 'yes' }
          _ "Applied: (#{applied.size}) "
          _ applied.sort.join(', ')
        end
      else
        _h2_ 'From potential-member-watch-list.txt'
        list = watch_list
      end

      _table.table do

        _thead_ do
          _tr do
            if request =~ /appstatus/
              _th 'Invited?', data_sort: 'string'
              _th 'Applied?', data_sort: 'string'
              _th 'members@?', data_sort: 'string'
              _th 'Karma', data_sort: 'string'
            elsif request =~ /nominees/
              _th 'Seconded?'
            else
              _th 'Nominated?'
            end

            _th 'AvailID', data_sort: 'string'
            _th 'Name', data_sort: 'string'

            if request !~ /appstatus/
            _th 'Committees', data_sort: 'string'
            _th 'Chair Since', data_sort: 'string'
            end
          end
        end

        _tbody do
          list.sort_by {|id| id.public_name.to_s}.each do |person|

            _tr_ do

              if request =~ /appstatus/
                cols = status[person.id]

                if cols[0] == 'yes'
                  _td cols[0]
                else
                  _td.text_danger cols[0]
                end

                if cols[0] == 'no' or cols[1] == 'yes'
                  _td cols[1]
                else
                  _td.text_danger cols[1]
                end

                if cols[1] == 'no' or cols[2] == 'yes'
                  _td cols[2]
                else
                  _td.text_danger cols[2]
                end

                if cols[3] == 'yes'
                  _td cols[3], class: ('issue' unless person.asf_member?)
                elsif cols[1] == 'no'
                  _td cols[3], class: ('issue' if person.asf_member?)
                else
                  _td.text_danger cols[3]
                end
              elsif request =~ /nominees/
                if person.member_nomination =~ /Seconded by: \w/
                  _td 'yes'
                else
                  _td.text_danger 'no'
                end
              else
                if nominations.key? person.id
                  _td 'yes'
                else
                  _td
                end
              end

              # ASF id
              if person.id =~ %r{^(notinavail_|n/a_)\d+$}
                _td! '(not yet a committer)'
              elsif person.asf_member_status == :current
                _td! do
                  _strong {_a person.id, href: "roster/committer/#{person.id}"}
                end
              elsif person.asf_member_status == :emeritus
                _td! do
                  _strong {_a person.id, href: "roster/committer/#{person.id}"}
                  _ ' (emeritus)'
                end
              else
                _td! {_a person.id, href: "roster/committer/#{person.id}"}
              end

              # public name
              _td person.public_name


              if request !~ /appstatus/
                # committees
                _td do
                  person.committees.sort_by(&:name).each do |committee|
                    if committee.chair == person
                      _strong do
                        _a committee.name, href: "roster/committee/#{committee.name}"
                      end
                    else
                      _a committee.name, href: "roster/committee/#{committee.name}"
                    end
                  end
                end

                # chair since
                chair = person.committees.find {|committee| committee.chair == person}
                if chair
                  minutes = Dir['../board/minutes/*'].find do |name|
                    File.basename(name).split('.').first.downcase.gsub(/[_\W]/,'') ==
                      chair.name.gsub(/\W/,'')
                  end

                  search_string = "RESOLVED, that #{person.public_name}"
                  search_string.force_encoding('utf-8')

                  # search published minutes
                  if minutes
                    resolution = nil
                    Nokogiri::HTML(File.read(minutes)).search('pre').each do |pre|
                      if pre.text.include? search_string
                        resolution = pre
                        while resolution and resolution.name != 'h2'
                          resolution = resolution.previous
                        end
                        break if resolution
                      end
                    end
                  end

                  date = 'unknown'
                  if minutes
                    minutes = 'board/minutes/' + File.basename(minutes)
                  end
                  if resolution
                    minutes += '#' + resolution.at('a')['id']
                    date = Date.parse(resolution.text)
                  else
                    # search unpublished agendas
                    board = ASF::SVN['foundation_board']
                    Dir[File.join(board, 'board_agenda_*')].sort.each do |agenda|
                      if File.read(agenda).include? search_string
                        minutes = ASF::SVN.svnpath!('foundation_board', File.basename(agenda))
                        date = agenda.gsub('_','-')[/(\d+-\d+-\d+)/,1]
                        break
                      end
                    end
                  end

                  _td do
                    _a date, href: minutes
                  end
                else
                  _td '-'
                end
              end
            end
          end
        end
      end

      _script %{
        var table = $(".table").stupidtable();
        table.on("aftertablesort", function (event, data) {
          var th = $(this).find("th");
          th.find(".arrow").remove();
          var dir = $.fn.stupidtable.dir;
          var arrow = data.direction === dir.ASC ? "&uarr;" : "&darr;";
          th.eq(data.column).append('<span class="arrow">' + arrow +'</span>');
          });
        }
    end
  end
end
