/*
  Copyright (c) 2004 Matt Douhan <matt@fruitsalad.org>

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Library General Public
  License as published by the Free Software Foundation; either
  version 2 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Library General Public License for more details.

  You should have received a copy of the GNU Library General Public License
  along with this library; see the file COPYING.LIB.  If not, write to
  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
  Boston, MA 02110-1301, USA.
*/

#include "kemailaddress.h"
#include "kcodecs.h"
#include "kcodecs_debug.h"

#include <QUrl>

#include <QRegExp>
#include <QByteArray>

using namespace KEmailAddress;

//-----------------------------------------------------------------------------
QStringList KEmailAddress::splitAddressList(const QString &aStr)
{
    // Features:
    // - always ignores quoted characters
    // - ignores everything (including parentheses and commas)
    //   inside quoted strings
    // - supports nested comments
    // - ignores everything (including double quotes and commas)
    //   inside comments

    QStringList list;

    if (aStr.isEmpty()) {
        return list;
    }

    QString addr;
    uint addrstart = 0;
    int commentlevel = 0;
    bool insidequote = false;

    for (int index = 0; index < aStr.length(); index++) {
        // the following conversion to latin1 is o.k. because
        // we can safely ignore all non-latin1 characters
        switch (aStr[index].toLatin1()) {
        case '"' : // start or end of quoted string
            if (commentlevel == 0) {
                insidequote = !insidequote;
            }
            break;
        case '(' : // start of comment
            if (!insidequote) {
                ++commentlevel;
            }
            break;
        case ')' : // end of comment
            if (!insidequote) {
                if (commentlevel > 0) {
                    --commentlevel;
                } else {
                    return list;
                }
            }
            break;
        case '\\' : // quoted character
            index++; // ignore the quoted character
            break;
        case ',' :
        case ';' :
            if (!insidequote && (commentlevel == 0)) {
                addr = aStr.mid(addrstart, index - addrstart);
                if (!addr.isEmpty()) {
                    list += addr.simplified();
                }
                addrstart = index + 1;
            }
            break;
        }
    }
    // append the last address to the list
    if (!insidequote && (commentlevel == 0)) {
        addr = aStr.mid(addrstart, aStr.length() - addrstart);
        if (!addr.isEmpty()) {
            list += addr.simplified();
        }
    }

    return list;
}

//-----------------------------------------------------------------------------
// Used by KEmailAddress::splitAddress(...) and KEmailAddress::firstEmailAddress(...).
KEmailAddress::EmailParseResult splitAddressInternal(const QByteArray &address,
                                                     QByteArray &displayName,
                                                     QByteArray &addrSpec,
                                                     QByteArray &comment,
                                                     bool allowMultipleAddresses)
{
    //  qCDebug(KCODECS_LOG) << "address";
    displayName = "";
    addrSpec = "";
    comment = "";

    if (address.isEmpty()) {
        return AddressEmpty;
    }

    // The following is a primitive parser for a mailbox-list (cf. RFC 2822).
    // The purpose is to extract a displayable string from the mailboxes.
    // Comments in the addr-spec are not handled. No error checking is done.

    enum {
        TopLevel,
        InComment,
        InAngleAddress
    } context = TopLevel;
    bool inQuotedString = false;
    int commentLevel = 0;
    bool stop = false;

    for (const char *p = address.data(); *p && !stop; ++p) {
        switch (context) {
        case TopLevel : {
            switch (*p) {
            case '"' :
                inQuotedString = !inQuotedString;
                displayName += *p;
                break;
            case '(' :
                if (!inQuotedString) {
                    context = InComment;
                    commentLevel = 1;
                } else {
                    displayName += *p;
                }
                break;
            case '<' :
                if (!inQuotedString) {
                    context = InAngleAddress;
                } else {
                    displayName += *p;
                }
                break;
            case '\\' : // quoted character
                displayName += *p;
                ++p; // skip the '\'
                if (*p) {
                    displayName += *p;
                } else {
                    return UnexpectedEnd;
                }
                break;
            case ',' :
                if (!inQuotedString) {
                    if (allowMultipleAddresses) {
                        stop = true;
                    } else {
                        return UnexpectedComma;
                    }
                } else {
                    displayName += *p;
                }
                break;
            default :
                displayName += *p;
            }
            break;
        }
        case InComment : {
            switch (*p) {
            case '(' :
                ++commentLevel;
                comment += *p;
                break;
            case ')' :
                --commentLevel;
                if (commentLevel == 0) {
                    context = TopLevel;
                    comment += ' '; // separate the text of several comments
                } else {
                    comment += *p;
                }
                break;
            case '\\' : // quoted character
                comment += *p;
                ++p; // skip the '\'
                if (*p) {
                    comment += *p;
                } else {
                    return UnexpectedEnd;
                }
                break;
            default :
                comment += *p;
            }
            break;
        }
        case InAngleAddress : {
            switch (*p) {
            case '"' :
                inQuotedString = !inQuotedString;
                addrSpec += *p;
                break;
            case '>' :
                if (!inQuotedString) {
                    context = TopLevel;
                } else {
                    addrSpec += *p;
                }
                break;
            case '\\' : // quoted character
                addrSpec += *p;
                ++p; // skip the '\'
                if (*p) {
                    addrSpec += *p;
                } else {
                    return UnexpectedEnd;
                }
                break;
            default :
                addrSpec += *p;
            }
            break;
        }
        } // switch ( context )
    }
    // check for errors
    if (inQuotedString) {
        return UnbalancedQuote;
    }
    if (context == InComment) {
        return UnbalancedParens;
    }
    if (context == InAngleAddress) {
        return UnclosedAngleAddr;
    }

    displayName = displayName.trimmed();
    comment = comment.trimmed();
    addrSpec = addrSpec.trimmed();

    if (addrSpec.isEmpty()) {
        if (displayName.isEmpty()) {
            return NoAddressSpec;
        } else {
            addrSpec = displayName;
            displayName.truncate(0);
        }
    }
    /*
      qCDebug(KCODECS_LOG) << "display-name : \"" << displayName << "\"";
      qCDebug(KCODECS_LOG) << "comment      : \"" << comment << "\"";
      qCDebug(KCODECS_LOG) << "addr-spec    : \"" << addrSpec << "\"";
    */
    return AddressOk;
}

//-----------------------------------------------------------------------------
EmailParseResult KEmailAddress::splitAddress(const QByteArray &address,
                                             QByteArray &displayName,
                                             QByteArray &addrSpec,
                                             QByteArray &comment)
{
    return splitAddressInternal(address, displayName, addrSpec, comment,
                                false/* don't allow multiple addresses */);
}

//-----------------------------------------------------------------------------
EmailParseResult KEmailAddress::splitAddress(const QString &address,
                                             QString &displayName,
                                             QString &addrSpec,
                                             QString &comment)
{
    QByteArray d, a, c;
    // FIXME: toUtf8() is probably not safe here, what if the second byte of a multi-byte character
    //        has the same code as one of the ASCII characters that splitAddress uses as delimiters?
    EmailParseResult result = splitAddress(address.toUtf8(), d, a, c);

    if (result == AddressOk) {
        displayName = QString::fromUtf8(d);
        addrSpec = QString::fromUtf8(a);
        comment = QString::fromUtf8(c);
    }
    return result;
}

//-----------------------------------------------------------------------------
EmailParseResult KEmailAddress::isValidAddress(const QString &aStr)
{
    // If we are passed an empty string bail right away no need to process
    // further and waste resources
    if (aStr.isEmpty()) {
        return AddressEmpty;
    }

    // count how many @'s are in the string that is passed to us
    // if 0 or > 1 take action
    // at this point to many @'s cannot bail out right away since
    // @ is allowed in qoutes, so we use a bool to keep track
    // and then make a judgment further down in the parser

    bool tooManyAtsFlag = false;

    int atCount = aStr.count(QLatin1Char('@'));
    if (atCount > 1) {
        tooManyAtsFlag = true;
    } else if (atCount == 0) {
        return TooFewAts;
    }

    int dotCount = aStr.count(QLatin1Char('.'));

    // The main parser, try and catch all weird and wonderful
    // mistakes users and/or machines can create

    enum {
        TopLevel,
        InComment,
        InAngleAddress
    } context = TopLevel;
    bool inQuotedString = false;
    int commentLevel = 0;

    unsigned int strlen = aStr.length();

    for (unsigned int index = 0; index < strlen; index++) {
        switch (context) {
        case TopLevel : {
            switch (aStr[index].toLatin1()) {
            case '"' :
                inQuotedString = !inQuotedString;
                break;
            case '(' :
                if (!inQuotedString) {
                    context = InComment;
                    commentLevel = 1;
                }
                break;
            case '[' :
                if (!inQuotedString) {
                    return InvalidDisplayName;
                }
                break;
            case ']' :
                if (!inQuotedString) {
                    return InvalidDisplayName;
                }
                break;
            case ':' :
                if (!inQuotedString) {
                    return DisallowedChar;
                }
                break;
            case '<' :
                if (!inQuotedString) {
                    context = InAngleAddress;
                }
                break;
            case '\\' : // quoted character
                ++index; // skip the '\'
                if ((index + 1) > strlen) {
                    return UnexpectedEnd;
                }
                break;
            case ',' :
                if (!inQuotedString) {
                    return UnexpectedComma;
                }
                break;
            case ')' :
                if (!inQuotedString) {
                    return UnbalancedParens;
                }
                break;
            case '>' :
                if (!inQuotedString) {
                    return UnopenedAngleAddr;
                }
                break;
            case '@' :
                if (!inQuotedString) {
                    if (index == 0) {    // Missing local part
                        return MissingLocalPart;
                    } else if (index == strlen - 1) {
                        return MissingDomainPart;
                    }
                } else if (inQuotedString) {
                    --atCount;
                    if (atCount == 1) {
                        tooManyAtsFlag = false;
                    }
                }
                break;
            case '.' :
                if (inQuotedString) {
                    --dotCount;
                }
                break;
            }
            break;
        }
        case InComment : {
            switch (aStr[index].toLatin1()) {
            case '(' :
                ++commentLevel;
                break;
            case ')' :
                --commentLevel;
                if (commentLevel == 0) {
                    context = TopLevel;
                }
                break;
            case '\\' : // quoted character
                ++index; // skip the '\'
                if ((index + 1) > strlen) {
                    return UnexpectedEnd;
                }
                break;
            }
            break;
        }

        case InAngleAddress : {
            switch (aStr[index].toLatin1()) {
            case ',' :
                if (!inQuotedString) {
                    return UnexpectedComma;
                }
                break;
            case '"' :
                inQuotedString = !inQuotedString;
                break;
            case '@' :
                if (inQuotedString) {
                    --atCount;
                }
                if (atCount == 1) {
                    tooManyAtsFlag = false;
                }
                break;
            case '.' :
                if (inQuotedString) {
                    --dotCount;
                }
                break;
            case '>' :
                if (!inQuotedString) {
                    context = TopLevel;
                    break;
                }
                break;
            case '\\' : // quoted character
                ++index; // skip the '\'
                if ((index + 1) > strlen) {
                    return UnexpectedEnd;
                }
                break;
            }
            break;
        }
        }
    }

    if (dotCount == 0 && !inQuotedString) {
        return TooFewDots;
    }

    if (atCount == 0 && !inQuotedString) {
        return TooFewAts;
    }

    if (inQuotedString) {
        return UnbalancedQuote;
    }

    if (context == InComment) {
        return UnbalancedParens;
    }

    if (context == InAngleAddress) {
        return UnclosedAngleAddr;
    }

    if (tooManyAtsFlag) {
        return TooManyAts;
    }

    return AddressOk;
}

//-----------------------------------------------------------------------------
KEmailAddress::EmailParseResult KEmailAddress::isValidAddressList(const QString &aStr,
                                                                  QString &badAddr)
{
    if (aStr.isEmpty()) {
        return AddressEmpty;
    }

    const QStringList list = splitAddressList(aStr);

    QStringList::const_iterator it = list.begin();
    EmailParseResult errorCode = AddressOk;
    for (it = list.begin(); it != list.end(); ++it) {
        qCDebug(KCODECS_LOG) << " *it" << (*it);
        errorCode = isValidAddress(*it);
        if (errorCode != AddressOk) {
            badAddr = (*it);
            break;
        }
    }
    return errorCode;
}

//-----------------------------------------------------------------------------
QString KEmailAddress::emailParseResultToString(EmailParseResult errorCode)
{
    switch (errorCode) {
    case TooManyAts :
        return QObject::tr("The email address you entered is not valid because it "
                           "contains more than one @. "
                           "You will not create valid messages if you do not "
                           "change your address.");
    case TooFewAts :
        return QObject::tr("The email address you entered is not valid because it "
                           "does not contain a @. "
                           "You will not create valid messages if you do not "
                           "change your address.");
    case AddressEmpty :
        return QObject::tr("You have to enter something in the email address field.");
    case MissingLocalPart :
        return QObject::tr("The email address you entered is not valid because it "
                           "does not contain a local part.");
    case MissingDomainPart :
        return QObject::tr("The email address you entered is not valid because it "
                           "does not contain a domain part.");
    case UnbalancedParens :
        return QObject::tr("The email address you entered is not valid because it "
                           "contains unclosed comments/brackets.");
    case AddressOk :
        return QObject::tr("The email address you entered is valid.");
    case UnclosedAngleAddr :
        return QObject::tr("The email address you entered is not valid because it "
                           "contains an unclosed angle bracket.");
    case UnopenedAngleAddr :
        return QObject::tr("The email address you entered is not valid because it "
                           "contains too many closing angle brackets.");
    case UnexpectedComma :
        return QObject::tr("The email address you have entered is not valid because it "
                           "contains an unexpected comma.");
    case UnexpectedEnd :
        return QObject::tr("The email address you entered is not valid because it ended "
                           "unexpectedly. This probably means you have used an escaping "
                           "type character like a '\\' as the last character in your "
                           "email address.");
    case UnbalancedQuote :
        return QObject::tr("The email address you entered is not valid because it "
                           "contains quoted text which does not end.");
    case NoAddressSpec :
        return QObject::tr("The email address you entered is not valid because it "
                           "does not seem to contain an actual email address, i.e. "
                           "something of the form joe@example.org.");
    case DisallowedChar :
        return QObject::tr("The email address you entered is not valid because it "
                           "contains an illegal character.");
    case InvalidDisplayName :
        return QObject::tr("The email address you have entered is not valid because it "
                           "contains an invalid display name.");
    case TooFewDots :
        return QObject::tr("The email address you entered is not valid because it "
                           "does not contain a \'.\'. "
                           "You will not create valid messages if you do not "
                           "change your address.");

    }
    return QObject::tr("Unknown problem with email address");
}

//-----------------------------------------------------------------------------
bool KEmailAddress::isValidSimpleAddress(const QString &aStr)
{
    // If we are passed an empty string bail right away no need to process further
    // and waste resources
    if (aStr.isEmpty()) {
        return false;
    }

    int atChar = aStr.lastIndexOf(QLatin1Char('@'));
    QString domainPart = aStr.mid(atChar + 1);
    QString localPart = aStr.left(atChar);

    // Both of these parts must be non empty
    // after all we cannot have emails like:
    // @kde.org, or  foo@
    if (localPart.isEmpty() || domainPart.isEmpty()) {
        return false;
    }

    bool tooManyAtsFlag = false;
    bool inQuotedString = false;
    int atCount = localPart.count(QLatin1Char('@'));

    unsigned int strlen = localPart.length();
    for (unsigned int index = 0; index < strlen; index++) {
        switch (localPart[ index ].toLatin1()) {
        case '"' :
            inQuotedString = !inQuotedString;
            break;
        case '@' :
            if (inQuotedString) {
                --atCount;
                if (atCount == 0) {
                    tooManyAtsFlag = false;
                }
            }
            break;
        }
    }

    QString addrRx;

    if (localPart[ 0 ] == QLatin1Char('\"') || localPart[ localPart.length() - 1 ] == QLatin1Char('\"')) {
        addrRx = QStringLiteral("\"[a-zA-Z@]*[\\w.@-]*[a-zA-Z0-9@]\"@");
    } else {
        addrRx = QStringLiteral("[a-zA-Z]*[~|{}`\\^?=/+*'&%$#!_\\w.-]*[~|{}`\\^?=/+*'&%$#!_a-zA-Z0-9-]@");
    }
    if (domainPart[ 0 ] == QLatin1Char('[') || domainPart[ domainPart.length() - 1 ] == QLatin1Char(']')) {
        addrRx += QStringLiteral("\\[[0-9]{,3}(\\.[0-9]{,3}){3}\\]");
    } else {
        addrRx += QStringLiteral("[\\w-#]+(\\.[\\w-#]+)*");
    }
    QRegExp rx(addrRx);
    return  rx.exactMatch(aStr) && !tooManyAtsFlag;
}

//-----------------------------------------------------------------------------
QString KEmailAddress::simpleEmailAddressErrorMsg()
{
    return QObject::tr("The email address you entered is not valid because it "
                       "does not seem to contain an actual email address, i.e. "
                       "something of the form joe@example.org.");
}

//-----------------------------------------------------------------------------
QByteArray KEmailAddress::extractEmailAddress(const QByteArray &address)
{
    QString errorMessage;
    return extractEmailAddress(address, errorMessage);
}

QByteArray KEmailAddress::extractEmailAddress(const QByteArray &address, QString &errorMessage)
{
    QByteArray dummy1, dummy2, addrSpec;
    const EmailParseResult result =
        splitAddressInternal(address, dummy1, addrSpec, dummy2,
                             false/* don't allow multiple addresses */);
    if (result != AddressOk) {
        addrSpec = QByteArray();
        if (result != AddressEmpty) {
            errorMessage = emailParseResultToString(result);
            qCDebug(KCODECS_LOG) << "Input:" << address << "\nError:"
                     << errorMessage;
        }
    } else {
        errorMessage.clear();
    }

    return addrSpec;
}

//-----------------------------------------------------------------------------
QString KEmailAddress::extractEmailAddress(const QString &address)
{
    QString errorMessage;
    return extractEmailAddress(address, errorMessage);
}

QString KEmailAddress::extractEmailAddress(const QString &address, QString &errorMessage)
{
    return QString::fromUtf8(extractEmailAddress(address.toUtf8(), errorMessage));
}

//-----------------------------------------------------------------------------
QByteArray KEmailAddress::firstEmailAddress(const QByteArray &addresses)
{
    QString errorMessage;
    return firstEmailAddress(addresses, errorMessage);
}

QByteArray KEmailAddress::firstEmailAddress(const QByteArray &addresses, QString &errorMessage)
{
    QByteArray dummy1, dummy2, addrSpec;
    const EmailParseResult result =
        splitAddressInternal(addresses, dummy1, addrSpec, dummy2,
                             true/* allow multiple addresses */);
    if (result != AddressOk) {
        addrSpec = QByteArray();
        if (result != AddressEmpty) {

            errorMessage = emailParseResultToString(result);
            qCDebug(KCODECS_LOG) << "Input: aStr\nError:"
                     << errorMessage;
        }
    } else {
        errorMessage.clear();
    }

    return addrSpec;
}

//-----------------------------------------------------------------------------
QString KEmailAddress::firstEmailAddress(const QString &addresses)
{
    QString errorMessage;
    return firstEmailAddress(addresses, errorMessage);
}

QString KEmailAddress::firstEmailAddress(const QString &addresses, QString &errorMessage)
{
    return QString::fromUtf8(firstEmailAddress(addresses.toUtf8(), errorMessage));
}

//-----------------------------------------------------------------------------
bool KEmailAddress::extractEmailAddressAndName(const QString &aStr,
                                               QString &mail, QString &name)
{
    name.clear();
    mail.clear();

    const int len = aStr.length();
    const char cQuotes = '"';

    bool bInComment = false;
    bool bInQuotesOutsideOfEmail = false;
    int i = 0, iAd = 0, iMailStart = 0, iMailEnd = 0;
    QChar c;
    unsigned int commentstack = 0;

    // Find the '@' of the email address
    // skipping all '@' inside "(...)" comments:
    while (i < len) {
        c = aStr[i];
        if (QLatin1Char('(') == c) {
            ++commentstack;
        }
        if (QLatin1Char(')') == c) {
            --commentstack;
        }
        bInComment = commentstack != 0;
        if (QLatin1Char('"') == c && !bInComment) {
            bInQuotesOutsideOfEmail = !bInQuotesOutsideOfEmail;
        }

        if (!bInComment && !bInQuotesOutsideOfEmail) {
            if (QLatin1Char('@') == c) {
                iAd = i;
                break; // found it
            }
        }
        ++i;
    }

    if (!iAd) {
        // We suppose the user is typing the string manually and just
        // has not finished typing the mail address part.
        // So we take everything that's left of the '<' as name and the rest as mail
        for (i = 0; len > i; ++i) {
            c = aStr[i];
            if (QLatin1Char('<') != c) {
                name.append(c);
            } else {
                break;
            }
        }
        mail = aStr.mid(i + 1);
        if (mail.endsWith(QLatin1Char('>'))) {
            mail.truncate(mail.length() - 1);
        }

    } else {
        // Loop backwards until we find the start of the string
        // or a ',' that is outside of a comment
        //          and outside of quoted text before the leading '<'.
        bInComment = false;
        bInQuotesOutsideOfEmail = false;
        for (i = iAd - 1; 0 <= i; --i) {
            c = aStr[i];
            if (bInComment) {
                if (QLatin1Char('(') == c) {
                    if (!name.isEmpty()) {
                        name.prepend(QLatin1Char(' '));
                    }
                    bInComment = false;
                } else {
                    name.prepend(c);   // all comment stuff is part of the name
                }
            } else if (bInQuotesOutsideOfEmail) {
                if (QLatin1Char(cQuotes) == c) {
                    bInQuotesOutsideOfEmail = false;
                } else if (c != QLatin1Char('\\')) {
                    name.prepend(c);
                }
            } else {
                // found the start of this addressee ?
                if (QLatin1Char(',') == c) {
                    break;
                }
                // stuff is before the leading '<' ?
                if (iMailStart) {
                    if (QLatin1Char(cQuotes) == c) {
                        bInQuotesOutsideOfEmail = true; // end of quoted text found
                    } else {
                        name.prepend(c);
                    }
                } else {
                    switch (c.toLatin1()) {
                    case '<':
                        iMailStart = i;
                        break;
                    case ')':
                        if (!name.isEmpty()) {
                            name.prepend(QLatin1Char(' '));
                        }
                        bInComment = true;
                        break;
                    default:
                        if (QLatin1Char(' ') != c) {
                            mail.prepend(c);
                        }
                    }
                }
            }
        }

        name = name.simplified();
        mail = mail.simplified();

        if (mail.isEmpty()) {
            return false;
        }

        mail.append(QLatin1Char('@'));

        // Loop forward until we find the end of the string
        // or a ',' that is outside of a comment
        //          and outside of quoted text behind the trailing '>'.
        bInComment = false;
        bInQuotesOutsideOfEmail = false;
        int parenthesesNesting = 0;
        for (i = iAd + 1; len > i; ++i) {
            c = aStr[i];
            if (bInComment) {
                if (QLatin1Char(')') == c) {
                    if (--parenthesesNesting == 0) {
                        bInComment = false;
                        if (!name.isEmpty()) {
                            name.append(QLatin1Char(' '));
                        }
                    } else {
                        // nested ")", add it
                        name.append(QLatin1Char(')'));   // name can't be empty here
                    }
                } else {
                    if (QLatin1Char('(') == c) {
                        // nested "("
                        ++parenthesesNesting;
                    }
                    name.append(c);   // all comment stuff is part of the name
                }
            } else if (bInQuotesOutsideOfEmail) {
                if (QLatin1Char(cQuotes) == c) {
                    bInQuotesOutsideOfEmail = false;
                } else if (c != QLatin1Char('\\')) {
                    name.append(c);
                }
            } else {
                // found the end of this addressee ?
                if (QLatin1Char(',') == c) {
                    break;
                }
                // stuff is behind the trailing '>' ?
                if (iMailEnd) {
                    if (QLatin1Char(cQuotes) == c) {
                        bInQuotesOutsideOfEmail = true; // start of quoted text found
                    } else {
                        name.append(c);
                    }
                } else {
                    switch (c.toLatin1()) {
                    case '>':
                        iMailEnd = i;
                        break;
                    case '(':
                        if (!name.isEmpty()) {
                            name.append(QLatin1Char(' '));
                        }
                        if (++parenthesesNesting > 0) {
                            bInComment = true;
                        }
                        break;
                    default:
                        if (QLatin1Char(' ') != c) {
                            mail.append(c);
                        }
                    }
                }
            }
        }
    }

    name = name.simplified();
    mail = mail.simplified();

    return !(name.isEmpty() || mail.isEmpty());
}

//-----------------------------------------------------------------------------
bool KEmailAddress::compareEmail(const QString &email1, const QString &email2,
                                 bool matchName)
{
    QString e1Name, e1Email, e2Name, e2Email;

    extractEmailAddressAndName(email1, e1Email, e1Name);
    extractEmailAddressAndName(email2, e2Email, e2Name);

    return e1Email == e2Email &&
           (!matchName || (e1Name == e2Name));
}

//-----------------------------------------------------------------------------
// Used internally by normalizedAddress()
QString removeBidiControlChars(const QString &input)
{
    const int LRO = 0x202D;
    const int RLO = 0x202E;
    const int LRE = 0x202A;
    const int RLE = 0x202B;
    QString result = input;
    result.remove(LRO);
    result.remove(RLO);
    result.remove(LRE);
    result.remove(RLE);
    return result;
}

QString KEmailAddress::normalizedAddress(const QString &displayName,
                                         const QString &addrSpec,
                                         const QString &comment)
{
    const QString realDisplayName = removeBidiControlChars(displayName);
    if (realDisplayName.isEmpty() && comment.isEmpty()) {
        return addrSpec;
    } else if (comment.isEmpty()) {
        if (!realDisplayName.startsWith(QLatin1Char('\"'))) {
            return quoteNameIfNecessary(realDisplayName) + QLatin1String(" <") + addrSpec + QLatin1Char('>');
        } else {
            return realDisplayName + QLatin1String(" <") + addrSpec + QLatin1Char('>');
        }
    } else if (realDisplayName.isEmpty()) {
        QString commentStr = comment;
        return quoteNameIfNecessary(commentStr) + QLatin1String(" <") + addrSpec + QLatin1Char('>');
    } else {
        return realDisplayName + QLatin1String(" (") + comment + QLatin1String(") <") + addrSpec + QLatin1Char('>');
    }
}

//-----------------------------------------------------------------------------
QString KEmailAddress::fromIdn(const QString &addrSpec)
{
    const int atPos = addrSpec.lastIndexOf(QLatin1Char('@'));
    if (atPos == -1) {
        return addrSpec;
    }

    QString idn = QUrl::fromAce(addrSpec.mid(atPos + 1).toLatin1());
    if (idn.isEmpty()) {
        return QString();
    }

    return addrSpec.left(atPos + 1) + idn;
}

//-----------------------------------------------------------------------------
QString KEmailAddress::toIdn(const QString &addrSpec)
{
    const int atPos = addrSpec.lastIndexOf(QLatin1Char('@'));
    if (atPos == -1) {
        return addrSpec;
    }

    QString idn = QLatin1String(QUrl::toAce(addrSpec.mid(atPos + 1)));
    if (idn.isEmpty()) {
        return addrSpec;
    }

    return addrSpec.left(atPos + 1) + idn;
}

//-----------------------------------------------------------------------------
QString KEmailAddress::normalizeAddressesAndDecodeIdn(const QString &str)
{
    //  qCDebug(KCODECS_LOG) << str;
    if (str.isEmpty()) {
        return str;
    }

    const QStringList addressList = splitAddressList(str);
    QStringList normalizedAddressList;

    QByteArray displayName, addrSpec, comment;

    for (QStringList::ConstIterator it = addressList.begin();
            (it != addressList.end());
            ++it) {
        if (!(*it).isEmpty()) {
            if (splitAddress((*it).toUtf8(),
                             displayName, addrSpec, comment) == AddressOk) {
                QByteArray cs;
                displayName = KCodecs::decodeRFC2047String(displayName, &cs).toUtf8();
                comment = KCodecs::decodeRFC2047String(comment, &cs).toUtf8();

                normalizedAddressList
                        << normalizedAddress(QString::fromUtf8(displayName),
                                             fromIdn(QString::fromUtf8(addrSpec)),
                                             QString::fromUtf8(comment));
            }
        }
    }
    /*
      qCDebug(KCODECS_LOG) << "normalizedAddressList: \""
               << normalizedAddressList.join( ", " )
               << "\"";
    */
    return normalizedAddressList.join(QStringLiteral(", "));
}

//-----------------------------------------------------------------------------
QString KEmailAddress::normalizeAddressesAndEncodeIdn(const QString &str)
{
    //qCDebug(KCODECS_LOG) << str;
    if (str.isEmpty()) {
        return str;
    }

    const QStringList addressList = splitAddressList(str);
    QStringList normalizedAddressList;

    QByteArray displayName, addrSpec, comment;

    for (QStringList::ConstIterator it = addressList.begin();
            (it != addressList.end());
            ++it) {
        if (!(*it).isEmpty()) {
            if (splitAddress((*it).toUtf8(),
                             displayName, addrSpec, comment) == AddressOk) {

                normalizedAddressList << normalizedAddress(QString::fromUtf8(displayName),
                                      toIdn(QString::fromUtf8(addrSpec)),
                                      QString::fromUtf8(comment));
            }
        }
    }

    /*
      qCDebug(KCODECS_LOG) << "normalizedAddressList: \""
               << normalizedAddressList.join( ", " )
               << "\"";
    */
    return normalizedAddressList.join(QStringLiteral(", "));
}

//-----------------------------------------------------------------------------
// Escapes unescaped doublequotes in str.
static QString escapeQuotes(const QString &str)
{
    if (str.isEmpty()) {
        return QString();
    }

    QString escaped;
    // reserve enough memory for the worst case ( """..."" -> \"\"\"...\"\" )
    escaped.reserve(2 * str.length());
    unsigned int len = 0;
    for (int i = 0; i < str.length(); ++i, ++len) {
        if (str[i] == QLatin1Char('"')) {   // unescaped doublequote
            escaped[len] = QLatin1Char('\\');
            ++len;
        } else if (str[i] == QLatin1Char('\\')) {   // escaped character
            escaped[len] = QLatin1Char('\\');
            ++len;
            ++i;
            if (i >= str.length()) {   // handle trailing '\' gracefully
                break;
            }
        }
        escaped[len] = str[i];
    }
    escaped.truncate(len);
    return escaped;
}

//-----------------------------------------------------------------------------
QString KEmailAddress::quoteNameIfNecessary(const QString &str)
{
    QString quoted = str;

    QRegExp needQuotes(QStringLiteral("[^ 0-9A-Za-z\\x0080-\\xFFFF]"));
    // avoid double quoting
    if ((quoted[0] == QLatin1Char('"')) && (quoted[quoted.length() - 1] == QLatin1Char('"'))) {
        quoted = QLatin1String("\"") + escapeQuotes(quoted.mid(1, quoted.length() - 2)) + QLatin1String("\"");
    } else if (quoted.indexOf(needQuotes) != -1) {
        quoted = QLatin1String("\"") + escapeQuotes(quoted) + QLatin1String("\"");
    }

    return quoted;
}

QUrl KEmailAddress::encodeMailtoUrl(const QString &mailbox)
{
    const QByteArray encodedPath = KCodecs::encodeRFC2047String(mailbox, "utf-8");
    QUrl mailtoUrl;
    mailtoUrl.setScheme(QStringLiteral("mailto"));
    mailtoUrl.setPath(QLatin1String(encodedPath));
    return mailtoUrl;
}

QString KEmailAddress::decodeMailtoUrl(const QUrl &mailtoUrl)
{
    Q_ASSERT(mailtoUrl.scheme() == QLatin1String("mailto"));
    return KCodecs::decodeRFC2047String(mailtoUrl.path());
}
