
module MailAddress

  def self.parse_first(*addresses)
    self.parse(*addresses).first
  end

  def self.parse(*addresses)
    lines = addresses.grep(String)
    line = lines.join('').strip

    # empty or <> or < or >
    if line.empty? || line.match(/\A[<>;, \\]+\z/)
      return [ MailAddress::Address.new(line, nil, line) ]
    end

    # undisclosed-recipient
    if line.match(/undisclosed[ \-]recipients?: ?;?/i)
      return [ MailAddress::Address.new(line, nil, line) ]
    end

    phrase, address, objs = [], [], []
    original = ''
    depth, idx, end_paren_idx = 0, 0, 0

    tokens = _tokenize lines
    len    = tokens.length
    _next  = _find_next idx, tokens, len

    for idx in 0 ... len do

      token = tokens[idx]
      substr = token[0, 1]
      original << token

      if (end_paren_idx > 0 && end_paren_idx >= idx)
        next
      end

      if (substr == '(' && !address.empty?)
        end_paren_idx = _find_next_paren(idx, tokens, len)
        if end_paren_idx == -1
          # end paren doesn't exist
          # but nothing to do
        end
        rem = tokens[idx .. end_paren_idx]
        phrase.push(rem.join(''))
      elsif (substr == '<')
        depth += 1
      elsif (substr == '>')
        depth -= 1 if depth > 0
      elsif (substr == ',' || substr == ';')
        original.sub!(/[,;]\s*\z/, '')

        if depth > 0
          # raise "Unmatched '<>' in line"
          o = MailAddress::Address.new(original, nil, original)
          phrase.clear; address.clear
        else
          o = _complete(phrase, address, original)
        end

        objs.push(o) if o
        depth = 0
        end_paren_idx = 0
        original = ''
        _next = _find_next idx+1, tokens, len
      elsif (depth > 0)
        token.strip!
        address.push(token)
      elsif (_next == '<')
        phrase.push(token)
      elsif ( token.match(/^[.\@:;]/) || address.empty? || address[-1].match(/^[.\@:;]/) )
        token.strip!
        address.push(token)
      else
        phrase.push(token)
      end
    end
    objs
  end

  private

  def self._tokenize(addresses)
    line = addresses.join(',') # $_
    words = []

    line.gsub!(/\\/, '')
    line.sub!(/\A\s+/, '')
    line.gsub!(/[\r\n]+/,' ')

    while (line != '')
      tmp = nil
      if (
          line.match(/"[^"]+"/) && line.sub!(/\A(\\?"(?:[^"\\]+|\\.)*")(\s*)/, '')  || # "..."
          line.sub!(/\A([^\s()<>\@,;:\\".\[\]]+)(\s*)/, '') ||
          line.sub!(/\A([()<>\@,;:\\".\[\]])(\s*)/, '')
          )
        words.push("#{$1}#{$2}")
        next
      end
      raise "Unrecognized line: #{line}"
    end

    words.push(',')
    words
  end

  def self._find_next(idx, tokens, len)
    while (idx < len)
      c = tokens[idx].strip
      return c if c == ',' || c == ';' || c == '<'
      idx += 1
    end
    ""
  end

  # find next ending parenthesis
  def self._find_next_paren(idx, tokens, len)
    while (idx < len)
      c = tokens[idx].strip
      return idx if c.include?(')')
      idx += 1
    end
    -1
  end

  def self._complete (phrase, address, original)
    phrase.length > 0 || address.length > 0 or return nil

    name = phrase.join('').strip

    name = self.collapse_whitespace(name)
    name = name[1 .. -2] if name.start_with?('\'') && name.end_with?('\'')
    name = name[1 .. -2] if name.start_with?('"') && name.end_with?('"')

    # Replace escaped quotes and slashes.
    name = name.gsub(ESCAPED_DOUBLE_QUOTES_, '"')
    name = name.gsub(ESCAPED_BACKSLASHES_, '\\')

    new_address = MailAddress::Address.new(name, address.join(''), original)
    phrase.clear; address.clear
    new_address
  end

end
