#!/usr/bin/env ruby
PAGETITLE = "Member Meeting Proxy Selection Form" # Wvisible:meeting
$LOAD_PATH.unshift '/srv/whimsy/lib'

require 'whimsy/asf'
require 'wunderbar'
require 'wunderbar/bootstrap'
require 'wunderbar/jquery'
require 'date'
require 'tmpdir'
require 'whimsy/asf/meeting-util'

# Emit basic instructions and details on quorum
def emit_instructions(today, cur_mtg_dir, meeting)
  meeting_display = meeting.gsub(%r{\A(\d\d\d\d)(\d\d)(\d\d)\z}, "\\1-\\2-\\3")
  if today > meeting
    _p.text_warning %{
        WARNING: Data for the next Member's Meeting is not yet available,
        so this form will not work yet.  Please wait until the Board Chair
        announces the opening of nominations for the board and new members,
        and then check back to assign a new proxy for the meeting.
        Data from the previous meeting on #{meeting_display} is shown below for debugging only.
      }
  end
  _p %{
    This form allows you to assign a proxy for the upcoming
    Member's Meeting on #{meeting_display}.  Submitting an attendance proxy will
    help us reach quorum at the start of the meeting - the meeting can't formally
    continue without quorum at the start.

    You can still vote and attend the meeting if you want, and you can revoke a
    proxy at any time.
  }
  _p %{
    If you submit a proxy, you will still be sent ballots by email to your personal
    @apache.org email address one week ahead of the meeting.

    If you won't have internet access for the full week of the meeting, ask
    for how to assign a proxy for your vote ballots as well.
  }
  _p do
    _ 'Note while the legal proxy form below states your proxy may have your voting rights, in practice '
    _strong 'you will still be emailed your ballots'
    _ ' unless you explicitly mark a \'*\' in the appropriate place in the '
    _code 'proxies'
    _ ' file.  The great majority of proxies assigned are for attendance only; not for voting.'
  end
  num_members, quorum_need, num_proxies, attend_irc = ASF::MeetingUtil.calculate_quorum(cur_mtg_dir)
  if num_members
    _p do
      _ 'Currently, we must have '
      _span.text_primary attend_irc
      _ " Members attend the #{meeting_display} meeting and respond to Roll Call to reach quorum and continue the meeting."
      _ " Calculation: Total voting members: #{num_members}, with one third for quorum: #{quorum_need}, minus previously submitted proxies: #{num_proxies}"
    end
  end
end

# Emit meeting data and form for user to select a proxy - GET
def emit_form(cur_mtg_dir, meeting, volunteers, disabled)
  if disabled
    _h3 'No upcoming meeting'
    _p 'There is currently no meeting scheduled. Call back later.'
    return
  end
  begin
    secretary_id = ASF::Committee.officer('secretary').id
  rescue StandardError
    secretary_id = ''
  end

  help, copypasta = ASF::MeetingUtil.is_user_proxied(cur_mtg_dir, $USER)
  user_is_proxy = help && copypasta
  _whimsy_panel(user_is_proxy ? "You Are Proxying For Others" : "Select A Proxy For Upcoming Meeting", style: 'panel-success') do
    _div do
      if help
        _p help
        if copypasta
          _ul.bg_success do
            copylines = copypasta.join("\n")
            _pre copylines
          end
        end
      else
        _p 'The following members have explicitly volunteered to serve as proxies; select any one of them, or select any other member that you know will proxy for you (or ask!):'
        _ul do
          volunteers.each do |vol|
            _pre vol
          end
        end
      end
    end

    if user_is_proxy
      _p.text_warning %{
          NOTE: you are proxying for other members, so you cannot assign
          someone else to proxy for your attendance.  If it turns out that
          you will not be able to attend the IRC meeting on Thursday,
          you MUST work with the Board Chair and your proxies to update the
          proxy records, and get someone else to mark their presence!
        }
    else
      _div.well.well_lg do
        _form method: 'POST', onsubmit: 'return validateForm();' do
          _div.form_group do
            _label 'Select proxy'
            _b do
              _p %{
                WARNING: If you select someone other than the Chair or Secretary (*), please note
                that your proxy will not be counted if the person is unable to attend.              }
            end
            _p %{
              (* The meeting will be postponed if the Chair and/or Secretary cannot attend)
            }


            # Fetch LDAP
            ldap_members = ASF.members
            ASF::Person.preload('cn', ldap_members)

            # Fetch members.txt
            members_txt = ASF::Member.list

            # get a list of members who have submitted proxies
            exclude = Dir[File.join(cur_mtg_dir,'proxies-received', '*')].
              map {|name| name[/(\w+)\.\w+$/, 1]}

            _select.combobox.input_large.form_control name: 'proxy' do
              if meeting != '20220615'
                _option 'Select an ASF Member', :selected, value: ''
              end
              # Allow for missing public name (should not happen unless LDAP is inconsistent)
              ldap_members.sort_by{|m| m.public_name || '_'}.each do |member|
                next if member.id == $USER               # No self proxies
                next if exclude.include? member.id       # Not attending
                next unless members_txt[member.id]       # Non-members
                next if members_txt[member.id]['status'] # Emeritus/Deceased
                # Display the availid to users to match volunteers array above
                _option "#{member.public_name || '?No public name?'} (#{member.id})",
                  selected: (member.id == secretary_id)
              end
            end
          end
          _div_.form_group do
            _p do
              _b 'Note that you cannot select a member who has nominated a proxy'
            end
            _p do
              _ "IMPORTANT! Be sure to tell the person that you select as proxy above that you've assigned them to mark your attendance! They simply need to mark your proxy attendance when the meeting starts."
              _a 'Read full procedures for Member Meeting', href: 'https://www.apache.org/foundation/governance/members.html#meetings'
            end
            _div.button_group.text_center do
              _button.btn.btn_primary 'Submit'
            end
          end
        end
        _pre IO.read(File.join(cur_mtg_dir, 'member_proxy.txt'))
      end
    end
  end

  _script src: "js/bootstrap-combobox.js" # TODO do we need this still?

  _script_ %{
    function validateForm() {
      if ($('.combobox').val() == '')  {
        alert("A proxy name is required");
        return false;
      }
      return true;
    }

    // convert select into combobox
    $('.combobox').combobox();

    // disable submit until a value is selected
    if ($('.combobox').val() == '') $('.btn').prop('disabled', true);

    // enable submit when proxy is chosen
    $('*[name="proxy"]').change(function() {
      $('.btn').prop('disabled', false);
      });
  }
end

# Emit a record of a user's submission - POST
def emit_post(cur_mtg_dir, meeting, _)
  # Detect missing/invalid proxy info (should not happen)
  raise ArgumentError,"Invalid proxy name '#{@proxy}'" unless @proxy =~ %r{\A.+ \([a-z0-9-]+\)\z}

  _h3_ 'Proxy Assignment - Session Transcript'

  # collect data
  proxy = File.read(File.join(cur_mtg_dir, 'member_proxy.txt'))
  user = ASF::Person.find($USER)
  date = Date.today.strftime("%B %-d, %Y")

  # update proxy form (match as many _ as possible up to the name length)
  proxy[/authorize _(_{,#{@proxy.length}})/, 1] = @proxy.gsub(' ', '_')

  proxy[/signature: _(_#{'_' * user.public_name.length}_)/, 1] =
    "/#{user.public_name.gsub(' ', '_')}/"

  proxy[/name: _(#{'_' * user.public_name.length})/, 1] =
    user.public_name.gsub(' ', '_')

  proxy[/availid: _(#{'_' * user.id.length})/, 1] =
    user.id.gsub(' ', '_')

  proxy[/Date: _(#{'_' * date.length})/, 1] = date.gsub(' ', '_')

  proxyform = proxy

  # report on commit
  _div.transcript do
    Dir.mktmpdir do |tmpdir|
      svn =  ASF::SVN.getInfoItem(File.join(MEETINGS,meeting),'url')

      ASF::SVN.svn_('checkout',[svn, tmpdir], _,
                    {quiet: true, user: $USER, password: $PASSWORD})
      Dir.chdir(tmpdir) do
        # write proxy form
        filename = "proxies-received/#{$USER}.txt"
        update_existing_form = File.exist? filename
        File.write(filename, proxyform)
        unless update_existing_form
          ASF::SVN.svn_('add', filename, _)
          ASF::SVN.svn_('propset', ['svn:mime-type', 'text/plain; charset=utf-8', filename], _)
        end

        # get a list of proxies
        list = Dir['proxies-received/*.txt'].map do |file|
          form = File.read(file)

          id = File.basename(file, '.txt') # assume filename is a valid id
          proxy = form[/hereby authorize ([\S].*) to act/, 1].
            gsub('_', ' ').strip
          # Ensure availid is not included in proxy name here
          proxy.sub!(%r{\([^)]+\)}, '')
          proxy.strip!
          name = form[/signature: ([\S].*)/, 1].gsub(/[\/_]/, ' ').strip

          "   #{proxy.ljust(24)} #{name} (#{id})"
        end

        # gather a list of all non-text proxies (TODO unused)
        nontext = Dir['proxies-received/*'].
          reject {|file| file.end_with? '.txt'}.
          map {|file| file[/([-A-Za-z0-9]+)\.\w+$/, 1]}

        # update proxies file
        proxies = IO.read('proxies')
        # look for lines containing '(id)' which start with 3 spaces
        # TODO this assumes that the volunteer lines start with 2 spaces
        existing = proxies.scan(/   \S.*\(\S+\).*$/)
        # extract the ids
        existing_ids = existing.map {|line| line[/\((\S+)\)/, 1] }
        # ensure this id is not treated as previously existing
        if existing_ids.delete(user.id)
          existing.reject! {|line| line[/\((\S+)\)$/, 1] == user.id}
        end
        # keep only new ids
        added = list.
          reject {|line| existing_ids.include? line[/\((\S+)\)$/, 1]}
        list = added + existing
        # look for the last '-' at the end of a line.
        # This should be under the 'For:' column heading just before the proxies
        # TODO it would be safer to look for <name>
        proxies[/.*-\n(.*)/m, 1] = list.flatten.sort.join("\n") + "\n"

        IO.write('proxies', proxies)

        # commit
        ASF::SVN.svn_('commit',[filename, 'proxies'], _,
          {msg: "assign #{@proxy} as my proxy", user: $USER, password: $PASSWORD})
# TODO: send email to @proxy per WHIMSY-78
      end
    end
  end

  # Report on contents now that they're checked in
  _h3! do
    _span "Contents of "
    _code "foundation/Meetings/#{meeting}/#{$USER}.txt"
    _span " as now checked in to svn:"
  end
  _pre proxyform
end

# produce HTML
_html do
  _style :system
  _style %{
    .transcript {margin: 0 16px}
    .transcript pre {border: none; line-height: 0}
  }
  _body? do
    # Find latest meeting and check if it's in the future yet
    MEETINGS = ASF::SVN['Meetings']
    cur_mtg_dir = ASF::MeetingUtil.get_latest(MEETINGS)
    meeting = File.basename(cur_mtg_dir)
    today = Date.today.strftime('%Y%m%d')
    _whimsy_body(
      title: PAGETITLE,
      style: (today > meeting ? 'panel-danger' : 'panel-info'),
      subtitle: today > meeting ? "ERROR: Next Meeting Data Not Available" : "How To Assign A Proxy For Upcoming Meeting",
      related: {
        '/members/meeting' => 'How-To / FAQ for Member Meetings',
        '/members/attendance-xcheck' => 'Members Meeting Attendance Crosscheck',
        '/members/inactive' => 'Inactive Member Feedback Form',
        '/members/subscriptions' => 'Members@ Mailing List Crosscheck'
      },
      helpblock: -> {
        emit_instructions(today, cur_mtg_dir, meeting)
      }
    ) do
      if _.get?
        emit_form(cur_mtg_dir, meeting, ASF::MeetingUtil::getVolunteers(cur_mtg_dir), today > meeting)
      else # POST
        # WHIMSY-409: improve UI
        begin
          emit_post(cur_mtg_dir, meeting, _)
        rescue ArgumentError => e
          _h2_.text_danger {_span.label.label_danger e}
        end
      end
    end
  end
end
