/*
    This file is part of Akonadi.
    SPDX-FileCopyrightText: 2003 Andreas Gungl <a.gungl@gmx.de>
    SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
    SPDX-FileCopyrightText: 2010 Leo Franchi <lfranchi@kde.org>

    SPDX-License-Identifier: LGPL-2.0-or-later
*/

#include "messagestatus.h"

#include "messageflags.h"

#include <QString>

/** The message status format. These can be or'd together.
    Note, that the StatusIgnored implies the
    status to be Read.
    This is done in isRead() and related getters.
    So we can preserve the state when switching a
    thread to Ignored and back. */
enum Status {
    StatusUnknown = 0x00000000,
    StatusUnread = 0x00000002, // deprecated
    StatusRead = 0x00000004,
    StatusDeleted = 0x00000010,
    StatusReplied = 0x00000020,
    StatusForwarded = 0x00000040,
    StatusQueued = 0x00000080,
    StatusSent = 0x00000100,
    StatusFlag = 0x00000200, // flag means important
    StatusWatched = 0x00000400,
    StatusIgnored = 0x00000800, // forces isRead()
    StatusToAct = 0x00001000,
    StatusSpam = 0x00002000,
    StatusHam = 0x00004000,
    StatusHasAttachment = 0x00008000,
    StatusHasInvitation = 0x00010000,
    StatusSigned = 0x00020000,
    StatusEncrypted = 0x00040000,
    StatusHasError = 0x00080000
};

Akonadi::MessageStatus::MessageStatus()
{
    mStatus = StatusUnknown;
}

bool Akonadi::MessageStatus::operator==(Akonadi::MessageStatus other) const
{
    return mStatus == other.mStatus;
}

bool Akonadi::MessageStatus::operator!=(Akonadi::MessageStatus other) const
{
    return mStatus != other.mStatus;
}

bool Akonadi::MessageStatus::operator&(Akonadi::MessageStatus other) const
{
    if (mStatus == StatusUnread) {
        return !(other.mStatus & StatusRead);
    }

    if (other.mStatus == StatusUnread) {
        return !(mStatus & StatusRead);
    }

    return mStatus & other.mStatus;
}

void Akonadi::MessageStatus::clear()
{
    mStatus = StatusUnknown;
}

void Akonadi::MessageStatus::set(Akonadi::MessageStatus other)
{
    Q_ASSERT(!(other.mStatus & StatusUnread));

    // Those stati are exclusive, but we have to lock at the
    // internal representation because Ignored can manipulate
    // the result of the getter methods.
    if (other.mStatus & StatusRead) {
        setRead();
    }
    if (other.isDeleted()) {
        setDeleted();
    }
    if (other.isReplied()) {
        setReplied();
    }
    if (other.isForwarded()) {
        setForwarded();
    }
    if (other.isQueued()) {
        setQueued();
    }
    if (other.isSent()) {
        setSent();
    }
    if (other.isImportant()) {
        setImportant();
    }

    if (other.isWatched()) {
        setWatched();
    }
    if (other.isIgnored()) {
        setIgnored();
    }
    if (other.isToAct()) {
        setToAct();
    }
    if (other.isSpam()) {
        setSpam();
    }
    if (other.isHam()) {
        setHam();
    }
    if (other.hasAttachment()) {
        setHasAttachment();
    }
    if (other.hasInvitation()) {
        setHasInvitation();
    }
    if (other.isSigned()) {
        setSigned();
    }
    if (other.isEncrypted()) {
        setEncrypted();
    }
    if (other.hasError()) {
        setHasError();
    }
}

void Akonadi::MessageStatus::toggle(Akonadi::MessageStatus other)
{
    Q_ASSERT(!(other.mStatus & StatusUnread));

    if (other.isDeleted()) {
        setDeleted(!(mStatus & StatusDeleted));
    }
    if (other.isReplied()) {
        setReplied(!(mStatus & StatusReplied));
    }
    if (other.isForwarded()) {
        setForwarded(!(mStatus & StatusForwarded));
    }
    if (other.isQueued()) {
        setQueued(!(mStatus & StatusQueued));
    }
    if (other.isSent()) {
        setSent(!(mStatus & StatusSent));
    }
    if (other.isImportant()) {
        setImportant(!(mStatus & StatusFlag));
    }

    if (other.isWatched()) {
        setWatched(!(mStatus & StatusWatched));
    }
    if (other.isIgnored()) {
        setIgnored(!(mStatus & StatusIgnored));
    }
    if (other.isToAct()) {
        setToAct(!(mStatus & StatusToAct));
    }
    if (other.isSpam()) {
        setSpam(!(mStatus & StatusSpam));
    }
    if (other.isHam()) {
        setHam(!(mStatus & StatusHam));
    }
    if (other.hasAttachment()) {
        setHasAttachment(!(mStatus & StatusHasAttachment));
    }
    if (other.hasInvitation()) {
        setHasInvitation(!(mStatus & StatusHasInvitation));
    }
    if (other.isSigned()) {
        setSigned(!(mStatus & StatusSigned));
    }
    if (other.isEncrypted()) {
        setEncrypted(!(mStatus & StatusEncrypted));
    }
    if (other.hasError()) {
        setHasError(!(mStatus & StatusHasError));
    }
}

bool Akonadi::MessageStatus::isOfUnknownStatus() const
{
    return mStatus == StatusUnknown;
}

bool Akonadi::MessageStatus::isRead() const
{
    return (mStatus & StatusRead) || (mStatus & StatusIgnored);
}

bool Akonadi::MessageStatus::isDeleted() const
{
    return mStatus & StatusDeleted;
}

bool Akonadi::MessageStatus::isReplied() const
{
    return mStatus & StatusReplied;
}

bool Akonadi::MessageStatus::isForwarded() const
{
    return mStatus & StatusForwarded;
}

bool Akonadi::MessageStatus::isQueued() const
{
    return mStatus & StatusQueued;
}

bool Akonadi::MessageStatus::isSent() const
{
    return mStatus & StatusSent;
}

bool Akonadi::MessageStatus::isImportant() const
{
    return mStatus & StatusFlag;
}

bool Akonadi::MessageStatus::isWatched() const
{
    return mStatus & StatusWatched;
}

bool Akonadi::MessageStatus::isIgnored() const
{
    return mStatus & StatusIgnored;
}

bool Akonadi::MessageStatus::isToAct() const
{
    return mStatus & StatusToAct;
}

bool Akonadi::MessageStatus::isSpam() const
{
    return mStatus & StatusSpam;
}

bool Akonadi::MessageStatus::isHam() const
{
    return mStatus & StatusHam;
}

bool Akonadi::MessageStatus::hasAttachment() const
{
    return mStatus & StatusHasAttachment;
}

bool Akonadi::MessageStatus::hasInvitation() const
{
    return mStatus & StatusHasInvitation;
}

bool Akonadi::MessageStatus::isSigned() const
{
    return mStatus & StatusSigned;
}

bool Akonadi::MessageStatus::isEncrypted() const
{
    return mStatus & StatusEncrypted;
}

bool Akonadi::MessageStatus::hasError() const
{
    return mStatus & StatusHasError;
}

void Akonadi::MessageStatus::setRead(bool read)
{
    if (read) {
        mStatus |= StatusRead;
    } else {
        mStatus &= ~StatusRead;
    }
}

void Akonadi::MessageStatus::setDeleted(bool deleted)
{
    if (deleted) {
        mStatus |= StatusDeleted;
    } else {
        mStatus &= ~StatusDeleted;
    }
}

void Akonadi::MessageStatus::setReplied(bool replied)
{
    if (replied) {
        mStatus |= StatusReplied;
    } else {
        mStatus &= ~StatusReplied;
    }
}

void Akonadi::MessageStatus::setForwarded(bool forwarded)
{
    if (forwarded) {
        mStatus |= StatusForwarded;
    } else {
        mStatus &= ~StatusForwarded;
    }
}

void Akonadi::MessageStatus::setQueued(bool queued)
{
    if (queued) {
        mStatus |= StatusQueued;
    } else {
        mStatus &= ~StatusQueued;
    }
}

void Akonadi::MessageStatus::setSent(bool sent)
{
    if (sent) {
        mStatus &= ~StatusQueued;
        mStatus |= StatusSent;
    } else {
        mStatus &= ~StatusSent;
    }
}

void Akonadi::MessageStatus::setImportant(bool important)
{
    if (important) {
        mStatus |= StatusFlag;
    } else {
        mStatus &= ~StatusFlag;
    }
}

// Watched and ignored are mutually exclusive
void Akonadi::MessageStatus::setWatched(bool watched)
{
    if (watched) {
        mStatus &= ~StatusIgnored;
        mStatus |= StatusWatched;
    } else {
        mStatus &= ~StatusWatched;
    }
}

void Akonadi::MessageStatus::setIgnored(bool ignored)
{
    if (ignored) {
        mStatus &= ~StatusWatched;
        mStatus |= StatusIgnored;
    } else {
        mStatus &= ~StatusIgnored;
    }
}

void Akonadi::MessageStatus::setToAct(bool toAct)
{
    if (toAct) {
        mStatus |= StatusToAct;
    } else {
        mStatus &= ~StatusToAct;
    }
}

// Ham and Spam are mutually exclusive
void Akonadi::MessageStatus::setSpam(bool spam)
{
    if (spam) {
        mStatus &= ~StatusHam;
        mStatus |= StatusSpam;
    } else {
        mStatus &= ~StatusSpam;
    }
}

void Akonadi::MessageStatus::setHam(bool ham)
{
    if (ham) {
        mStatus &= ~StatusSpam;
        mStatus |= StatusHam;
    } else {
        mStatus &= ~StatusHam;
    }
}

void Akonadi::MessageStatus::setHasAttachment(bool withAttachment)
{
    if (withAttachment) {
        mStatus |= StatusHasAttachment;
    } else {
        mStatus &= ~StatusHasAttachment;
    }
}

void Akonadi::MessageStatus::setHasInvitation(bool withInvitation)
{
    if (withInvitation) {
        mStatus |= StatusHasInvitation;
    } else {
        mStatus &= ~StatusHasInvitation;
    }
}

void Akonadi::MessageStatus::setSigned(bool value)
{
    if (value) {
        mStatus |= StatusSigned;
    } else {
        mStatus &= ~StatusSigned;
    }
}

void Akonadi::MessageStatus::setEncrypted(bool value)
{
    if (value) {
        mStatus |= StatusEncrypted;
    } else {
        mStatus &= ~StatusEncrypted;
    }
}

void Akonadi::MessageStatus::setHasError(bool hasError)
{
    if (hasError) {
        mStatus |= StatusHasError;
    } else {
        mStatus &= ~StatusHasError;
    }
}

qint32 Akonadi::MessageStatus::toQInt32() const
{
    return mStatus;
}

void Akonadi::MessageStatus::fromQInt32(qint32 status)
{
    mStatus = status;
}

QString Akonadi::MessageStatus::statusStr() const
{
    QByteArray sstr;
    if (mStatus & StatusRead) {
        sstr += 'R';
    } else {
        sstr += 'U';
    }
    if (mStatus & StatusDeleted) {
        sstr += 'D';
    }
    if (mStatus & StatusReplied) {
        sstr += 'A';
    }
    if (mStatus & StatusForwarded) {
        sstr += 'F';
    }
    if (mStatus & StatusQueued) {
        sstr += 'Q';
    }
    if (mStatus & StatusToAct) {
        sstr += 'K';
    }
    if (mStatus & StatusSent) {
        sstr += 'S';
    }
    if (mStatus & StatusFlag) {
        sstr += 'G';
    }
    if (mStatus & StatusWatched) {
        sstr += 'W';
    }
    if (mStatus & StatusIgnored) {
        sstr += 'I';
    }
    if (mStatus & StatusSpam) {
        sstr += 'P';
    }
    if (mStatus & StatusHam) {
        sstr += 'H';
    }
    if (mStatus & StatusHasAttachment) {
        sstr += 'T';
    }

    return QLatin1String(sstr);
}

void Akonadi::MessageStatus::setStatusFromStr(const QString &aStr)
{
    mStatus = StatusUnknown;

    if (aStr.contains(QLatin1Char('U'))) {
        setRead(false);
    }
    if (aStr.contains(QLatin1Char('R'))) {
        setRead();
    }
    if (aStr.contains(QLatin1Char('D'))) {
        setDeleted();
    }
    if (aStr.contains(QLatin1Char('A'))) {
        setReplied();
    }
    if (aStr.contains(QLatin1Char('F'))) {
        setForwarded();
    }
    if (aStr.contains(QLatin1Char('Q'))) {
        setQueued();
    }
    if (aStr.contains(QLatin1Char('K'))) {
        setToAct();
    }
    if (aStr.contains(QLatin1Char('S'))) {
        setSent();
    }
    if (aStr.contains(QLatin1Char('G'))) {
        setImportant();
    }
    if (aStr.contains(QLatin1Char('W'))) {
        setWatched();
    }
    if (aStr.contains(QLatin1Char('I'))) {
        setIgnored();
    }
    if (aStr.contains(QLatin1Char('P'))) {
        setSpam();
    }
    if (aStr.contains(QLatin1Char('H'))) {
        setHam();
    }
    if (aStr.contains(QLatin1Char('T'))) {
        setHasAttachment();
    }
    if (aStr.contains(QLatin1Char('C'))) {
        setHasAttachment(false);
    }
}

QSet<QByteArray> Akonadi::MessageStatus::statusFlags() const
{
    QSet<QByteArray> flags;

    if (mStatus & StatusDeleted) {
        flags += Akonadi::MessageFlags::Deleted;
    } else {
        if (mStatus & StatusRead) {
            flags += Akonadi::MessageFlags::Seen;
        }
        if (mStatus & StatusReplied) {
            flags += Akonadi::MessageFlags::Answered;
        }
        if (mStatus & StatusFlag) {
            flags += Akonadi::MessageFlags::Flagged;
        }

        // non standard flags
        if (mStatus & StatusSent) {
            flags += Akonadi::MessageFlags::Sent;
        }
        if (mStatus & StatusQueued) {
            flags += Akonadi::MessageFlags::Queued;
        }
        if (mStatus & StatusReplied) {
            flags += Akonadi::MessageFlags::Replied;
        }
        if (mStatus & StatusForwarded) {
            flags += Akonadi::MessageFlags::Forwarded;
        }
        if (mStatus & StatusToAct) {
            flags += Akonadi::MessageFlags::ToAct;
        }
        if (mStatus & StatusWatched) {
            flags += Akonadi::MessageFlags::Watched;
        }
        if (mStatus & StatusIgnored) {
            flags += Akonadi::MessageFlags::Ignored;
        }
        if (mStatus & StatusHasAttachment) {
            flags += Akonadi::MessageFlags::HasAttachment;
        }
        if (mStatus & StatusHasInvitation) {
            flags += Akonadi::MessageFlags::HasInvitation;
        }
        if (mStatus & StatusSigned) {
            flags += Akonadi::MessageFlags::Signed;
        }
        if (mStatus & StatusEncrypted) {
            flags += Akonadi::MessageFlags::Encrypted;
        }
        if (mStatus & StatusSpam) {
            flags += Akonadi::MessageFlags::Spam;
        }
        if (mStatus & StatusHam) {
            flags += Akonadi::MessageFlags::Ham;
        }
        if (mStatus & StatusHasError) {
            flags += Akonadi::MessageFlags::HasError;
        }
    }

    return flags;
}

void Akonadi::MessageStatus::setStatusFromFlags(const QSet<QByteArray> &flags)
{
    mStatus = StatusUnknown;

    for (const QByteArray &flag : flags) {
        const QByteArray &upperedFlag = flag.toUpper();
        if (upperedFlag == Akonadi::MessageFlags::Deleted) {
            setDeleted();
        } else if (upperedFlag == Akonadi::MessageFlags::Seen) {
            setRead();
        } else if (upperedFlag == Akonadi::MessageFlags::Answered) {
            setReplied();
        } else if (upperedFlag == Akonadi::MessageFlags::Flagged) {
            setImportant();

            // non standard flags
        } else if (upperedFlag == Akonadi::MessageFlags::Sent) {
            setSent();
        } else if (upperedFlag == Akonadi::MessageFlags::Queued) {
            setQueued();
        } else if (upperedFlag == Akonadi::MessageFlags::Replied) {
            setReplied();
        } else if (upperedFlag == Akonadi::MessageFlags::Forwarded) {
            setForwarded();
        } else if (upperedFlag == Akonadi::MessageFlags::ToAct) {
            setToAct();
        } else if (upperedFlag == Akonadi::MessageFlags::Watched) {
            setWatched();
        } else if (upperedFlag == Akonadi::MessageFlags::Ignored) {
            setIgnored();
        } else if (upperedFlag == Akonadi::MessageFlags::HasAttachment) {
            setHasAttachment();
        } else if (upperedFlag == Akonadi::MessageFlags::HasInvitation) {
            setHasInvitation();
        } else if (upperedFlag == Akonadi::MessageFlags::Signed) {
            setSigned();
        } else if (upperedFlag == Akonadi::MessageFlags::Encrypted) {
            setEncrypted();
        } else if (upperedFlag == Akonadi::MessageFlags::Spam) {
            setSpam();
        } else if (upperedFlag == Akonadi::MessageFlags::Ham) {
            setHam();
        } else if (upperedFlag == Akonadi::MessageFlags::HasError) {
            setHasError();
        }
    }
}

const Akonadi::MessageStatus Akonadi::MessageStatus::statusUnread()
{
    Akonadi::MessageStatus st;
    st.mStatus = StatusUnread;
    return st;
}

const Akonadi::MessageStatus Akonadi::MessageStatus::statusRead()
{
    Akonadi::MessageStatus st;
    st.setRead();
    return st;
}

const Akonadi::MessageStatus Akonadi::MessageStatus::statusDeleted()
{
    Akonadi::MessageStatus st;
    st.setDeleted();
    return st;
}

const Akonadi::MessageStatus Akonadi::MessageStatus::statusReplied()
{
    Akonadi::MessageStatus st;
    st.setReplied();
    return st;
}

const Akonadi::MessageStatus Akonadi::MessageStatus::statusForwarded()
{
    Akonadi::MessageStatus st;
    st.setForwarded();
    return st;
}

const Akonadi::MessageStatus Akonadi::MessageStatus::statusQueued()
{
    Akonadi::MessageStatus st;
    st.setQueued();
    return st;
}

const Akonadi::MessageStatus Akonadi::MessageStatus::statusSent()
{
    Akonadi::MessageStatus st;
    st.setSent();
    return st;
}

const Akonadi::MessageStatus Akonadi::MessageStatus::statusImportant()
{
    Akonadi::MessageStatus st;
    st.setImportant();
    return st;
}

const Akonadi::MessageStatus Akonadi::MessageStatus::statusWatched()
{
    Akonadi::MessageStatus st;
    st.setWatched();
    return st;
}

const Akonadi::MessageStatus Akonadi::MessageStatus::statusIgnored()
{
    Akonadi::MessageStatus st;
    st.setIgnored();
    return st;
}

const Akonadi::MessageStatus Akonadi::MessageStatus::statusToAct()
{
    Akonadi::MessageStatus st;
    st.setToAct();
    return st;
}

const Akonadi::MessageStatus Akonadi::MessageStatus::statusSpam()
{
    Akonadi::MessageStatus st;
    st.setSpam();
    return st;
}

const Akonadi::MessageStatus Akonadi::MessageStatus::statusHam()
{
    Akonadi::MessageStatus st;
    st.setHam();
    return st;
}

const Akonadi::MessageStatus Akonadi::MessageStatus::statusHasAttachment()
{
    Akonadi::MessageStatus st;
    st.setHasAttachment();
    return st;
}

const Akonadi::MessageStatus Akonadi::MessageStatus::statusHasInvitation()
{
    MessageStatus st;
    st.setHasInvitation();
    return st;
}

const Akonadi::MessageStatus Akonadi::MessageStatus::statusSigned()
{
    MessageStatus st;
    st.setSigned();
    return st;
}

const Akonadi::MessageStatus Akonadi::MessageStatus::statusEncrypted()
{
    MessageStatus st;
    st.setEncrypted();
    return st;
}

const Akonadi::MessageStatus Akonadi::MessageStatus::statusHasError()
{
    MessageStatus st;
    st.setHasError();
    return st;
}

QDebug operator<<(QDebug d, const Akonadi::MessageStatus &t)
{
    d << "status " << t.statusStr();
    return d;
}
