# frozen_string_literal: true require ‘mail/utilities’ require ‘mail/parser_tools’

begin

original_verbose, $VERBOSE = $VERBOSE, nil

%%{

machine address_lists;
alphtype int;

# Phrase
action phrase_s { phrase_s = p }
action phrase_e { phrase_e = p-1 }

# Quoted String.
action qstr_s { qstr_s = p }
action qstr_e { qstr = chars(data, qstr_s, p-1) }

# Comment
action comment_s { comment_s = p unless comment_s }
action comment_e {
  if address
    address.comments << chars(data, comment_s, p-2)
  end
  comment_s = nil
}

# Group Name
action group_name_s { group_name_s = p }
action group_name_e {
  if qstr
    group = qstr
    qstr = nil
  else
    group = chars(data, group_name_s, p-1)
    group_name_s = nil
  end
  address_list.group_names << group
  group_name = group

  # Start next address
  address = AddressStruct.new(nil, nil, [], nil, nil, nil, nil)
  address_s = p
  address.group = group_name
}

# Address
action address_s { address_s = p }

# Ignore address end events without a start event.
action address_e {
  if address_s
    if address.local.nil? && local_dot_atom_pre_comment_e && local_dot_atom_s && local_dot_atom_e
      if address.domain
        address.local = chars(data, local_dot_atom_s, local_dot_atom_e)
      else
        address.local = chars(data, local_dot_atom_s, local_dot_atom_pre_comment_e)
      end
    end
    address.raw = chars(data, address_s, p-1)
    address_list.addresses << address if address

    # Start next address
    address = AddressStruct.new(nil, nil, [], nil, nil, nil, nil)
    address.group = group_name
    address_s = nil
  end
}

# Don't set the display name until the address has actually started. This
# allows us to choose quoted_s version if it exists and always use the
# 'full' phrase version.
action angle_addr_s {
  if qstr
    address.display_name = Mail::Utilities.unescape(qstr)
    qstr = nil
  elsif phrase_e
    address.display_name = chars(data, phrase_s, phrase_e).strip
    phrase_e = phrase_s = nil
  end
}

# Domain
action domain_s { domain_s = p }
action domain_e {
  address.domain = chars(data, domain_s, p-1).rstrip if address
}

# Local
action local_dot_atom_s { local_dot_atom_s = p }
action local_dot_atom_e { local_dot_atom_e = p-1 }
action local_dot_atom_pre_comment_e { local_dot_atom_pre_comment_e = p-1 }
action local_quoted_string_e { address.local = '"' + qstr + '"' if address }

# obs_domain_list
action obs_domain_list_s { obs_domain_list_s = p }
action obs_domain_list_e { address.obs_domain_list = chars(data, obs_domain_list_s, p-1) }

# Junk actions
action addr_spec { }
action ctime_date_e { }
action ctime_date_s { }
action date_e { }
action date_s { }
action disp_type_e { }
action disp_type_s { }
action encoding_e { }
action encoding_s { }
action main_type_e { }
action main_type_s { }
action major_digits_e { }
action major_digits_s { }
action minor_digits_e { }
action minor_digits_s { }
action msg_id_e { }
action msg_id_s { }
action param_attr_e { }
action param_attr_s { }
action param_val_e { }
action param_val_s { }
action received_tokens_e { }
action received_tokens_s { }
action sub_type_e { }
action sub_type_s { }
action time_e { }
action time_s { }
action token_string_e { }
action token_string_s { }

include rfc5322 "rfc5322.rl";
main := address_lists;

}%%

module Mail::Parsers

module AddressListsParser
  extend Mail::ParserTools

  AddressListStruct = Struct.new(:addresses, :group_names, :error)
  AddressStruct = Struct.new(:raw, :domain, :comments, :local,
                           :obs_domain_list, :display_name, :group, :error)

  %%write data noprefix;

  def self.parse(data)
    data = data.dup.force_encoding(Encoding::ASCII_8BIT) if data.respond_to?(:force_encoding)

    address_list = AddressListStruct.new([], [])
    return address_list if Mail::Utilities.blank?(data)

    phrase_s = phrase_e = qstr_s = qstr = comment_s = nil
    group_name_s = domain_s = group_name = nil
    local_dot_atom_s = local_dot_atom_e = nil
    local_dot_atom_pre_comment_e = nil
    obs_domain_list_s = nil

    address_s = 0
    address = AddressStruct.new(nil, nil, [], nil, nil, nil, nil)

    # 5.1 Variables Used by Ragel
    p = 0
    eof = pe = data.length
    stack = []

    %%write init;
    %%write exec;

    if p != eof || cs < %%{ write first_final; }%%
      raise Mail::Field::IncompleteParseError.new(Mail::AddressList, data, p)
    end

    address_list
  end
end

end

ensure

$VERBOSE = original_verbose

end