/*
 *
 */

#include "config.h"
#include "Gradient.h"

#include "Color.h"
#include "FloatRect.h"
#include <wtf/UnusedParam.h>

namespace WebCore {

Gradient::Gradient(const FloatPoint& p0, const FloatPoint& p1)
    : m_radial(false)
    , m_p0(p0)
    , m_p1(p1)
    , m_r0(0)
    , m_r1(0)
    , m_aspectRatio(1)
    , m_stopsSorted(false)
    , m_lastStop(0)
    , m_spreadMethod(SpreadMethodPad)
{
    platformInit();
}

Gradient::Gradient(const FloatPoint& p0, float r0, const FloatPoint& p1, float r1, float aspectRatio)
    : m_radial(true)
    , m_p0(p0)
    , m_p1(p1)
    , m_r0(r0)
    , m_r1(r1)
    , m_aspectRatio(aspectRatio)
    , m_stopsSorted(false)
    , m_lastStop(0)
    , m_spreadMethod(SpreadMethodPad)
{
    platformInit();
}

Gradient::~Gradient()
{
    platformDestroy();
}

void Gradient::adjustParametersForTiledDrawing(IntSize& size, FloatRect& srcRect)
{
    if (m_radial)
        return;

    if (srcRect.isEmpty())
        return;

    if (m_p0.x() == m_p1.x()) {
        size.setWidth(1);
        srcRect.setWidth(1);
        srcRect.setX(0);
        return;
    }
    if (m_p0.y() != m_p1.y())
        return;

    size.setHeight(1);
    srcRect.setHeight(1);
    srcRect.setY(0);
}

void Gradient::addColorStop(float value, const Color& color)
{
    float r;
    float g;
    float b;
    float a;
    color.getRGBA(r, g, b, a);
    m_stops.append(ColorStop(value, r, g, b, a));

    m_stopsSorted = false;
    platformDestroy();
}

void Gradient::addColorStop(const Gradient::ColorStop& stop)
{
    m_stops.append(stop);

    m_stopsSorted = false;
    platformDestroy();
}

static inline bool compareStops(const Gradient::ColorStop& a, const Gradient::ColorStop& b)
{
    return a.stop < b.stop;
}

void Gradient::sortStopsIfNecessary()
{
    if (m_stopsSorted)
        return;

    m_stopsSorted = true;

    if (!m_stops.size())
        return;

    if (m_stops.size() == 2 && compareStops(*m_stops.begin(), *m_stops.end()))
        return;

    std::stable_sort(m_stops.begin(), m_stops.end(), compareStops);
}

void Gradient::getColor(float value, float* r, float* g, float* b, float* a) const
{
    ASSERT(value >= 0);
    ASSERT(value <= 1);

    if (m_stops.isEmpty()) {
        *r = 0;
        *g = 0;
        *b = 0;
        *a = 0;
        return;
    }
    if (!m_stopsSorted) {
        if (m_stops.size())
            std::stable_sort(m_stops.begin(), m_stops.end(), compareStops);
        m_stopsSorted = true;
    }
    if (value <= 0 || value <= m_stops.first().stop) {
        *r = m_stops.first().red;
        *g = m_stops.first().green;
        *b = m_stops.first().blue;
        *a = m_stops.first().alpha;
        return;
    }
    if (value >= 1 || value >= m_stops.last().stop) {
        *r = m_stops.last().red;
        *g = m_stops.last().green;
        *b = m_stops.last().blue;
        *a = m_stops.last().alpha;
        return;
    }

    // Find stop before and stop after and interpolate.
    int stop = findStop(value);
    const ColorStop& lastStop = m_stops[stop];
    const ColorStop& nextStop = m_stops[stop + 1];
    float stopFraction = (value - lastStop.stop) / (nextStop.stop - lastStop.stop);
    *r = lastStop.red + (nextStop.red - lastStop.red) * stopFraction;
    *g = lastStop.green + (nextStop.green - lastStop.green) * stopFraction;
    *b = lastStop.blue + (nextStop.blue - lastStop.blue) * stopFraction;
    *a = lastStop.alpha + (nextStop.alpha - lastStop.alpha) * stopFraction;
}

int Gradient::findStop(float value) const
{
    ASSERT(value >= 0);
    ASSERT(value <= 1);
    ASSERT(m_stopsSorted);

    int numStops = m_stops.size();
    ASSERT(numStops >= 2);
    ASSERT(m_lastStop < numStops - 1);

    int i = m_lastStop;
    if (value < m_stops[i].stop)
        i = 1;
    else
        i = m_lastStop + 1;

    for (; i < numStops - 1; ++i)
        if (value < m_stops[i].stop)
            break;

    m_lastStop = i - 1;
    return m_lastStop;
}

bool Gradient::hasAlpha() const
{
    for (size_t i = 0; i < m_stops.size(); i++) {
        if (m_stops[i].alpha < 1)
            return true;
    }

    return false;

void Gradient::setSpreadMethod(GradientSpreadMethod spreadMethod)
{
    // FIXME: Should it become necessary, allow calls to this method after m_gradient has been set.
    ASSERT(m_gradient == 0);
    m_spreadMethod = spreadMethod;
}

void Gradient::setGradientSpaceTransform(const AffineTransform& gradientSpaceTransformation)
{
    m_gradientSpaceTransformation = gradientSpaceTransformation;
    setPlatformGradientSpaceTransform(gradientSpaceTransformation);
}

#if !(USE(SKIA) && !PLATFORM(ANDROID)) && !USE(CAIRO)
void Gradient::setPlatformGradientSpaceTransform(const AffineTransform&)
{
}
#endif

} //namespace


/*
 *
 */

#include "config.h"
#include "Gradient.h"

#include "Color.h"
#include "FloatRect.h"
#include <wtf/UnusedParam.h>

namespace WebCore {

Gradient::Gradient(const FloatPoint& p0, const FloatPoint& p1)
    : m_radial(false)
    , m_p0(p0)
    , m_p1(p1)
    , m_r0(0)
    , m_r1(0)
    , m_aspectRatio(1)
    , m_stopsSorted(false)
    , m_lastStop(0)
    , m_spreadMethod(SpreadMethodPad)
{
    platformInit();
}

Gradient::Gradient(const FloatPoint& p0, float r0, const FloatPoint& p1, float r1, float aspectRatio)
    : m_radial(true)
    , m_p0(p0)
    , m_p1(p1)
    , m_r0(r0)
    , m_r1(r1)
    , m_aspectRatio(aspectRatio)
    , m_stopsSorted(false)
    , m_lastStop(0)
    , m_spreadMethod(SpreadMethodPad)
{
    platformInit();
}

Gradient::~Gradient()
{
    platformDestroy();
}

void Gradient::adjustParametersForTiledDrawing(IntSize& size, FloatRect& srcRect)
{
    if (m_radial)
        return;

    if (srcRect.isEmpty())
        return;

    if (m_p0.x() == m_p1.x()) {
        size.setWidth(1);
        srcRect.setWidth(1);
        srcRect.setX(0);
        return;
    }
    if (m_p0.y() != m_p1.y())
        return;

    size.setHeight(1);
    srcRect.setHeight(1);
    srcRect.setY(0);
}

void Gradient::addColorStop(float value, const Color& color)
{
    float r;
    float g;
    float b;
    float a;
    color.getRGBA(r, g, b, a);
    m_stops.append(ColorStop(value, r, g, b, a));

    m_stopsSorted = false;
    platformDestroy();
}

void Gradient::addColorStop(const Gradient::ColorStop& stop)
{
    m_stops.append(stop);

    m_stopsSorted = false;
    platformDestroy();
}

static inline bool compareStops(const Gradient::ColorStop& a, const Gradient::ColorStop& b)
{
    return a.stop < b.stop;
}

void Gradient::sortStopsIfNecessary()
{
    if (m_stopsSorted)
        return;

    m_stopsSorted = true;

    if (!m_stops.size())
        return;

    if (m_stops.size() == 2 && compareStops(*m_stops.begin(), *m_stops.end()))
        return;

    std::stable_sort(m_stops.begin(), m_stops.end(), compareStops);
}

void Gradient::getColor(float value, float* r, float* g, float* b, float* a) const
{
    ASSERT(value >= 0);
    ASSERT(value <= 1);

    if (m_stops.isEmpty()) {
        *r = 0;
        *g = 0;
        *b = 0;
        *a = 0;
        return;
    }
    if (!m_stopsSorted) {
        if (m_stops.size())
            std::stable_sort(m_stops.begin(), m_stops.end(), compareStops);
        m_stopsSorted = true;
    }
    if (value <= 0 || value <= m_stops.first().stop) {
        *r = m_stops.first().red;
        *g = m_stops.first().green;
        *b = m_stops.first().blue;
        *a = m_stops.first().alpha;
        return;
    }
    if (value >= 1 || value >= m_stops.last().stop) {
        *r = m_stops.last().red;
        *g = m_stops.last().green;
        *b = m_stops.last().blue;
        *a = m_stops.last().alpha;
        return;
    }

    // Find stop before and stop after and interpolate.
    int stop = findStop(value);
    const ColorStop& lastStop = m_stops[stop];
    const ColorStop& nextStop = m_stops[stop + 1];
    float stopFraction = (value - lastStop.stop) / (nextStop.stop - lastStop.stop);
    *r = lastStop.red + (nextStop.red - lastStop.red) * stopFraction;
    *g = lastStop.green + (nextStop.green - lastStop.green) * stopFraction;
    *b = lastStop.blue + (nextStop.blue - lastStop.blue) * stopFraction;
    *a = lastStop.alpha + (nextStop.alpha - lastStop.alpha) * stopFraction;
}

int Gradient::findStop(float value) const
{
    ASSERT(value >= 0);
    ASSERT(value <= 1)

    ASSERT(m_stopsSorted);

    int numStops = m_stops.size();
    ASSERT(numStops >= 2);
    ASSERT(m_lastStop < numStops - 1);

    int i = m_lastStop;
    if (value < m_stops[i].stop)
        i = 1;
    else
        i = m_lastStop + 1;

    for (; i < numStops - 1; ++i)
        if (value < m_stops[i].stop)
            break;

    m_lastStop = i - 1;
    return m_lastStop;
}

bool Gradient::hasAlpha() const
{
    for (size_t i = 0; i < m_stops.size(); i++) {
        if (m_stops[i].alpha < 1)
            return true;
    }

    return false;
}

void Gradient::setSpreadMethod(GradientSpreadMethod spreadMethod)
{
    // FIXME: Should it become necessary, allow calls to this method after m_gradient has been set.
    ASSERT(m_gradient == 0);
    m_spreadMethod = spreadMethod;
}

void Gradient::setGradientSpaceTransform(const AffineTransform& gradientSpaceTransformation)
{
    m_gradientSpaceTransformation = gradientSpaceTransformation;
    setPlatformGradientSpaceTransform(gradientSpaceTransformation);
}

#if !(USE(SKIA) && !PLATFORM(ANDROID)) && !USE(CAIRO)
void Gradient::setPlatformGradientSpaceTransform(const AffineTransform&)
{
}
#endif

} //namespace


if (value < m_stops[i].stop)
        i = 1;
    else
        i = m_lastStop + 1;

/*
 *
 */

#include "config.h"
#include "Gradient.h"

#include "Color.h"
#include "FloatRect.h"
#include <wtf/UnusedParam.h>

namespace WebCore {

Gradient::Gradient(const FloatPoint& p0, const FloatPoint& p1)
    : m_radial(false)
    , m_p0(p0)
    , m_p1(p1)
    , m_r0(0)
    , m_r1(0)
    , m_aspectRatio(1)
    , m_stopsSorted(false)
    , m_lastStop(0)
    , m_spreadMethod(SpreadMethodPad)
{
    platformInit();
}

Gradient::Gradient(const FloatPoint& p0, float r0, const FloatPoint& p1, float r1, float aspectRatio)
    : m_radial(true)
    , m_p0(p0)
    , m_p1(p1)
    , m_r0(r0)
    , m_r1(r1)
    , m_aspectRatio(aspectRatio)
    , m_stopsSorted(false)
    , m_lastStop(0)
    , m_spreadMethod(SpreadMethodPad)
{
    platformInit();
}

Gradient::~Gradient()
{
    platformDestroy();
}

void Gradient::adjustParametersForTiledDrawing(IntSize& size, FloatRect& srcRect)
{
    if (m_radial)
        return;

    if (srcRect.isEmpty())
        return;

    if (m_p0.x() == m_p1.x()) {
        size.setWidth(1);
        srcRect.setWidth(1);
        srcRect.setX(0);
        return;
    }
    if (m_p0.y() != m_p1.y())
        return;

    size.setHeight(1);
    srcRect.setHeight(1);
    srcRect.setY(0);
}

void Gradient::addColorStop(float value, const Color& color)
{
    float r;
    float g;
    float b;
    float a;
    color.getRGBA(r, g, b, a);
    m_stops.append(ColorStop(value, r, g, b, a));

    m_stopsSorted = false;
    platformDestroy();
}

void Gradient::addColorStop(const Gradient::ColorStop& stop)
{
    m_stops.append(stop);

    m_stopsSorted = false;
    platformDestroy();
}

static inline bool compareStops(const Gradient::ColorStop& a, const Gradient::ColorStop& b)
{
    return a.stop < b.stop;
}

void Gradient::sortStopsIfNecessary()
{
    if (m_stopsSorted)
        return;

    m_stopsSorted = true;

    if (!m_stops.size())
        return;

    if (m_stops.size() == 2 && compareStops(*m_stops.begin(), *m_stops.end()))
        return;

    std::stable_sort(m_stops.begin(), m_stops.end(), compareStops);
}

void Gradient::getColor(float value, float* r, float* g, float* b, float* a) const
{
    ASSERT(value >= 0);
    ASSERT(value <= 1);

    if (m_stops.isEmpty()) {
        *r = 0;
        *g = 0;
        *b = 0;
        *a = 0;
        return;
    }
    if (!m_stopsSorted) {
        if (m_stops.size())
            std::stable_sort(m_stops.begin(), m_stops.end(), compareStops);
        m_stopsSorted = true;
    }
    if (value <= 0 || value <= m_stops.first().stop) {
        *r = m_stops.first().red;
        *g = m_stops.first().green;
        *b = m_stops.first().blue;
        *a = m_stops.first().alpha;
        return;
    }
    if (value >= 1 || value >= m_stops.last().stop) {
        *r = m_stops.last().red;
        *g = m_stops.last().green;
        *b = m_stops.last().blue;
        *a = m_stops.last().alpha;
        return;
    }

    // Find stop before and stop after and interpolate.
    int stop = findStop(value);
    const ColorStop& lastStop = m_stops[stop];
    const ColorStop& nextStop = m_stops[stop + 1];
    float stopFraction = (value - lastStop.stop) / (nextStop.stop - lastStop.stop);
    *r = lastStop.red + (nextStop.red - lastStop.red) * stopFraction;
    *g = lastStop.green + (nextStop.green - lastStop.green) * stopFraction;
    *b = lastStop.blue + (nextStop.blue - lastStop.blue) * stopFraction;
    *a = lastStop.alpha + (nextStop.alpha - lastStop.alpha) * stopFraction;
}

int Gradient::findStop(float value) const
{
    ASSERT(value >= 0);
    ASSERT(value <= 1);
    ASSERT(m_stopsSorted);

    int numStops = m_stops.size();
    ASSERT(numStops >= 2);
    ASSERT(m_lastStop < numStops - 1);

    int i = m_lastStop;

    for (; i < numStops - 1; ++i)
        if (value < m_stops[i].stop)
            break;

    m_lastStop = i - 1;
    return m_lastStop;
}

bool Gradient::hasAlpha() const
{
    for (size_t i = 0; i < m_stops.size(); i++) {
        if (m_stops[i].alpha < 1)
            return true;
    }

    return false;
}

void Gradient::setSpreadMethod(GradientSpreadMethod spreadMethod)
{
    // FIXME: Should it become necessary, allow calls to this method after m_gradient has been set.
    ASSERT(m_gradient == 0);
    m_spreadMethod = spreadMethod;
}

void Gradient::setGradientSpaceTransform(const AffineTransform& gradientSpaceTransformation)
{
    m_gradientSpaceTransformation = gradientSpaceTransformation;
    setPlatformGradientSpaceTransform(gradientSpaceTransformation);
}

#if !(USE(SKIA) && !PLATFORM(ANDROID)) && !USE(CAIRO)
void Gradient::setPlatformGradientSpaceTransform(const AffineTransform&)
{
}
#endif

} //namespace


for (size_t i = 0; i < m_stops.size(); i++) {
        if (m_stops[i].alpha < 1)
            return true;
    }

/*
 *
 */

#include "config.h"
#include "Gradient.h"

#include "Color.h"
#include "FloatRect.h"
#include <wtf/UnusedParam.h>

namespace WebCore {

Gradient::Gradient(const FloatPoint& p0, const FloatPoint& p1)
    : m_radial(false)
    , m_p0(p0)
    , m_p1(p1)
    , m_r0(0)
    , m_r1(0)
    , m_aspectRatio(1)
    , m_stopsSorted(false)
    , m_lastStop(0)
    , m_spreadMethod(SpreadMethodPad)
{
    platformInit();
}

Gradient::Gradient(const FloatPoint& p0, float r0, const FloatPoint& p1, float r1, float aspectRatio)
    : m_radial(true)
    , m_p0(p0)
    , m_p1(p1)
    , m_r0(r0)
    , m_r1(r1)
    , m_aspectRatio(aspectRatio)
    , m_stopsSorted(false)
    , m_lastStop(0)
    , m_spreadMethod(SpreadMethodPad)
{
    platformInit();
}

Gradient::~Gradient()
{
    platformDestroy();
}

void Gradient::adjustParametersForTiledDrawing(IntSize& size, FloatRect& srcRect)
{
    if (m_radial)
        return;

    if (srcRect.isEmpty())
        return;

    if (m_p0.x() == m_p1.x()) {
        size.setWidth(1);
        srcRect.setWidth(1);
        srcRect.setX(0);
        return;
    }
    if (m_p0.y() != m_p1.y())
        return;

    size.setHeight(1);
    srcRect.setHeight(1);
    srcRect.setY(0);
}

void Gradient::addColorStop(float value, const Color& color)
{
    float r;
    float g;
    float b;
    float a;
    color.getRGBA(r, g, b, a);
    m_stops.append(ColorStop(value, r, g, b, a));

    m_stopsSorted = false;
    platformDestroy();
}

void Gradient::addColorStop(const Gradient::ColorStop& stop)
{
    m_stops.append(stop);

    m_stopsSorted = false;
    platformDestroy();
}

static inline bool compareStops(const Gradient::ColorStop& a, const Gradient::ColorStop& b)
{
    return a.stop < b.stop;
}

void Gradient::sortStopsIfNecessary()
{
    if (m_stopsSorted)
        return;

    m_stopsSorted = true;

    if (!m_stops.size())
        return;

    if (m_stops.size() == 2 && compareStops(*m_stops.begin(), *m_stops.end()))
        return;

    std::stable_sort(m_stops.begin(), m_stops.end(), compareStops);
}

void Gradient::getColor(float value, float* r, float* g, float* b, float* a) const
{
    ASSERT(value >= 0);
    ASSERT(value <= 1);

    if (m_stops.isEmpty()) {
        *r = 0;
        *g = 0;
        *b = 0;
        *a = 0;
        return;
    }
    if (!m_stopsSorted) {
        if (m_stops.size())
            std::stable_sort(m_stops.begin(), m_stops.end(), compareStops);
        m_stopsSorted = true;
    }
    if (value <= 0 || value <= m_stops.first().stop) {
        *r = m_stops.first().red;
        *g = m_stops.first().green;
        *b = m_stops.first().blue;
        *a = m_stops.first().alpha;
        return;
    }
    if (value >= 1 || value >= m_stops.last().stop) {
        *r = m_stops.last().red;
        *g = m_stops.last().green;
        *b = m_stops.last().blue;
        *a = m_stops.last().alpha;
        return;
    }

    // Find stop before and stop after and interpolate.
    int stop = findStop(value);
    const ColorStop& lastStop = m_stops[stop];
    const ColorStop& nextStop = m_stops[stop + 1];
    float stopFraction = (value - lastStop.stop) / (nextStop.stop - lastStop.stop);
    *r = lastStop.red + (nextStop.red - lastStop.red) * stopFraction;
    *g = lastStop.green + (nextStop.green - lastStop.green) * stopFraction;
    *b = lastStop.blue + (nextStop.blue - lastStop.blue) * stopFraction;
    *a = lastStop.alpha + (nextStop.alpha - lastStop.alpha) * stopFraction;
}

int Gradient::findStop(float value) const
{
    ASSERT(value >= 0);
    ASSERT(value <= 1);
    ASSERT(m_stopsSorted);

    int numStops = m_stops.size();
    ASSERT(numStops >= 2);
    ASSERT(m_lastStop < numStops - 1);

    int i = m_lastStop;
    if (value < m_stops[i].stop)
        i = 1;
    else
        i = m_lastStop + 1;

    for (; i < numStops - 1; ++i)
        if (value < m_stops[i].stop)
            break;

    m_lastStop = i - 1;
    return m_lastStop;
}

bool Gradient::hasAlpha() const
{

    return false;
}

void Gradient::setSpreadMethod(GradientSpreadMethod spreadMethod)
{
    // FIXME: Should it become necessary, allow calls to this method after m_gradient has been set.
    ASSERT(m_gradient == 0);
    m_spreadMethod = spreadMethod;
}

void Gradient::setGradientSpaceTransform(const AffineTransform& gradientSpaceTransformation)
{
    m_gradientSpaceTransformation = gradientSpaceTransformation;
    setPlatformGradientSpaceTransform(gradientSpaceTransformation);
}

#if !(USE(SKIA) && !PLATFORM(ANDROID)) && !USE(CAIRO)
void Gradient::setPlatformGradientSpaceTransform(const AffineTransform&)
{
}
#endif

} //namespace


/*

    This file is part of qTox, a Qt-based graphical interface for Tox.

*/

#include "friendlistmanager.h"
#include "src/widget/genericchatroomwidget.h"

FriendListManager::FriendListManager(int countContacts, QObject *parent) : QObject(parent)
{
    this->countContacts = countContacts;
}

QVector<FriendListManager::IFriendListItemPtr> FriendListManager::getItems() const
{
    return items;
}

bool FriendListManager::needHideCircles() const
{
    return hideCircles;
}

bool FriendListManager::getPositionsChanged() const
{
    return positionsChanged;
}

void FriendListManager::addFriendListItem(IFriendListItem *item)
{
    if (item->isGroup()) {
        items.push_back(IFriendListItemPtr(item, [](IFriendListItem* groupItem){
                            groupItem->getWidget()->deleteLater();}));
    } else {
        items.push_back(IFriendListItemPtr(item));
    }

    if (countContacts <= items.size()) {
        countContacts = 0;
        setSortRequired();
    }
}

void FriendListManager::removeFriendListItem(IFriendListItem *item)
{
    removeAll(item);
    setSortRequired();
}

{
    byName = true;
    updatePositions();
}

void FriendListManager::sortByActivity()
{
    byName = false;
    updatePositions();
}

void FriendListManager::resetParents()
{
    for (int i = 0; i < items.size(); ++i) {
        IFriendListItem* itemTmp = items[i].get();
        itemTmp->getWidget()->setParent(nullptr);
    }
}

void FriendListManager::setFilter(const QString &searchString, bool hideOnline, bool hideOffline,
                                  bool hideGroups)
{
    if (filterParams.searchString == searchString && filterParams.hideOnline == hideOnline &&
        filterParams.hideOffline == hideOffline && filterParams.hideGroups == hideGroups) {
        return;
    }
    filterParams.searchString = searchString;
    filterParams.hideOnline = hideOnline;
    filterParams.hideOffline = hideOffline;
    filterParams.hideGroups = hideGroups;

    setSortRequired();
}

void FriendListManager::applyFilter()
{
    QString searchString = filterParams.searchString;

    for (IFriendListItemPtr itemTmp : items) {
        if (searchString.isEmpty()) {
            itemTmp->getWidget()->setVisible(true);
        } else {
            QString tmp_name = itemTmp->getNameItem();
            itemTmp->getWidget()->setVisible(tmp_name.contains(searchString, Qt::CaseInsensitive));
        }

        if (filterParams.hideOnline && itemTmp->isOnline()) {
            if (itemTmp->isFriend()) {
                itemTmp->getWidget()->setVisible(false);
            }
        }

        if (filterParams.hideOffline && !itemTmp->isOnline()) {
            itemTmp->getWidget()->setVisible(false);
        }

        if (filterParams.hideGroups && itemTmp->isGroup()) {
            itemTmp->getWidget()->setVisible(false);
        }
    }

    if (filterParams.hideOnline && filterParams.hideOffline) {
        hideCircles = true;
    } else {
        hideCircles = false;
    }
}

void FriendListManager::updatePositions()
{
    positionsChanged = true;

    if (byName) {
        auto sortName = [&](const IFriendListItemPtr &a, const IFriendListItemPtr &b) {
                            return cmpByName(a, b, groupsOnTop);
                        };
        if (!needSort) {
            if (std::is_sorted(items.begin(), items.end(), sortName)) {
                positionsChanged = false;
                return;
            }
        }
        std::sort(items.begin(), items.end(),sortName);

    } else {
        auto sortActivity = [&](const IFriendListItemPtr &a, const IFriendListItemPtr &b) {
                                return cmpByActivity(a, b);
                            };
        if (!needSort) {
            if (std::is_sorted(items.begin(), items.end(), sortActivity)) {
                positionsChanged = false;
                return;
            }
        }
        std::sort(items.begin(), items.end(), sortActivity);
    }

    needSort = false;
}

void FriendListManager::setSortRequired()
{
    needSort = true;
    emit itemsChanged();
}

void FriendListManager::setGroupsOnTop(bool v)
{
    groupsOnTop = v;
}

void FriendListManager::removeAll(IFriendListItem* item)
{
    for (int i = 0; i < items.size(); ++i) {
        if (items[i].get() == item) {
            items.remove(i);
            --i;
        }
    }
}

bool FriendListManager::cmpByName(const IFriendListItemPtr &a, const IFriendListItemPtr &b,
                                  bool groupsOnTop)
{
    if (a->isGroup() && !b->isGroup()) {
        if (groupsOnTop) {
            return true;
        }
        return !b->isOnline();
    }

    if (!a->isGroup() && b->isGroup()) {
        if (groupsOnTop) {
            return false;
        }
        return a->isOnline();
    }

    if (a->isOnline() && !b->isOnline()) {
        return true;
    }

    if (!a->isOnline() && b->isOnline()) {
        return false;
    }

    return a->getNameItem().toUpper() < b->getNameItem().toUpper();
}

bool FriendListManager::cmpByActivity(const IFriendListItemPtr &a, const IFriendListItemPtr &b)
{
    if (a->isGroup() || b->isGroup()) {
        if (a->isGroup() && !b->isGroup()) {
            return true;
        }

        if (!a->isGroup() && b->isGroup()) {
            return false;
        }
        return a->getNameItem().toUpper() < b->getNameItem().toUpper();
    }

    QDateTime dateA = a->getLastActivity();
    QDateTime dateB = b->getLastActivity();
    if (dateA.date() == dateB.date()) {
        if (a->isOnline() && !b->isOnline()) {
            return true;
        }

        if (!a->isOnline() && b->isOnline()) {
            return false;
        }
        return a->getNameItem().toUpper() < b->getNameItem().toUpper();
    }

    return a->getLastActivity() > b->getLastActivity();
}


{
    for (int i = 0; i < items.size(); ++i) {
        IFriendListItem* itemTmp = items[i].get();
        itemTmp->getWidget()->setParent(nullptr);
    }
}

/*

    This file is part of qTox, a Qt-based graphical interface for Tox.

*/

#include "friendlistmanager.h"
#include "src/widget/genericchatroomwidget.h"

FriendListManager::FriendListManager(int countContacts, QObject *parent) : QObject(parent)
{
    this->countContacts = countContacts;
}

QVector<FriendListManager::IFriendListItemPtr> FriendListManager::getItems() const
{
    return items;
}

bool FriendListManager::needHideCircles() const
{
    return hideCircles;
}

bool FriendListManager::getPositionsChanged() const
{
    return positionsChanged;
}

void FriendListManager::addFriendListItem(IFriendListItem *item)
{
    if (item->isGroup()) {
        items.push_back(IFriendListItemPtr(item, [](IFriendListItem* groupItem){
                            groupItem->getWidget()->deleteLater();}));
    } else {
        items.push_back(IFriendListItemPtr(item));
    }

    if (countContacts <= items.size()) {
        countContacts = 0;
        setSortRequired();
    }
}

void FriendListManager::removeFriendListItem(IFriendListItem *item)
{
    removeAll(item);
    setSortRequired();
}

void FriendListManager::sortByName()
{
    byName = true;
    updatePositions();
}

void FriendListManager::sortByActivity()
{
    byName = false;
    updatePositions();
}

void FriendListManager::resetParents()

void FriendListManager::setFilter(const QString &searchString, bool hideOnline, bool hideOffline,
                                  bool hideGroups)
{
    if (filterParams.searchString == searchString && filterParams.hideOnline == hideOnline &&
        filterParams.hideOffline == hideOffline && filterParams.hideGroups == hideGroups) {
        return;
    }
    filterParams.searchString = searchString;
    filterParams.hideOnline = hideOnline;
    filterParams.hideOffline = hideOffline;
    filterParams.hideGroups = hideGroups;

    setSortRequired();
}

void FriendListManager::applyFilter()
{
    QString searchString = filterParams.searchString;

    for (IFriendListItemPtr itemTmp : items) {
        if (searchString.isEmpty()) {
            itemTmp->getWidget()->setVisible(true);
        } else {
            QString tmp_name = itemTmp->getNameItem();
            itemTmp->getWidget()->setVisible(tmp_name.contains(searchString, Qt::CaseInsensitive));
        }

        if (filterParams.hideOnline && itemTmp->isOnline()) {
            if (itemTmp->isFriend()) {
                itemTmp->getWidget()->setVisible(false);
            }
        }

        if (filterParams.hideOffline && !itemTmp->isOnline()) {
            itemTmp->getWidget()->setVisible(false);
        }

        if (filterParams.hideGroups && itemTmp->isGroup()) {
            itemTmp->getWidget()->setVisible(false);
        }
    }

    if (filterParams.hideOnline && filterParams.hideOffline) {
        hideCircles = true;
    } else {
        hideCircles = false;
    }
}

void FriendListManager::updatePositions()
{
    positionsChanged = true;

    if (byName) {
        auto sortName = [&](const IFriendListItemPtr &a, const IFriendListItemPtr &b) {
                            return cmpByName(a, b, groupsOnTop);
                        };
        if (!needSort) {
            if (std::is_sorted(items.begin(), items.end(), sortName)) {
                positionsChanged = false;
                return;
            }
        }
        std::sort(items.begin(), items.end(),sortName);

    } else {
        auto sortActivity = [&](const IFriendListItemPtr &a, const IFriendListItemPtr &b) {
                                return cmpByActivity(a, b);
                            };
        if (!needSort) {
            if (std::is_sorted(items.begin(), items.end(), sortActivity)) {
                positionsChanged = false;
                return;
            }
        }
        std::sort(items.begin(), items.end(), sortActivity);
    }

    needSort = false;
}

void FriendListManager::setSortRequired()
{
    needSort = true;
    emit itemsChanged();
}

void FriendListManager::setGroupsOnTop(bool v)
{
    groupsOnTop = v;
}

void FriendListManager::removeAll(IFriendListItem* item)
{
    for (int i = 0; i < items.size(); ++i) {
        if (items[i].get() == item) {
            items.remove(i);
            --i;
        }
    }
}

bool FriendListManager::cmpByName(const IFriendListItemPtr &a, const IFriendListItemPtr &b,
                                  bool groupsOnTop)
{
    if (a->isGroup() && !b->isGroup()) {
        if (groupsOnTop) {
            return true;
        }
        return !b->isOnline();
    }

    if (!a->isGroup() && b->isGroup()) {
        if (groupsOnTop) {
            return false;
        }
        return a->isOnline();
    }

    if (a->isOnline() && !b->isOnline()) {
        return true;
    }

    if (!a->isOnline() && b->isOnline()) {
        return false;
    }

    return a->getNameItem().toUpper() < b->getNameItem().toUpper();
}

bool FriendListManager::cmpByActivity(const IFriendListItemPtr &a, const IFriendListItemPtr &b)
{
    if (a->isGroup() || b->isGroup()) {
        if (a->isGroup() && !b->isGroup()) {
            return true;
        }

        if (!a->isGroup() && b->isGroup()) {
            return false;
        }
        return a->getNameItem().toUpper() < b->getNameItem().toUpper();
    }

    QDateTime dateA = a->getLastActivity();
    QDateTime dateB = b->getLastActivity();
    if (dateA.date() == dateB.date()) {
        if (a->isOnline() && !b->isOnline()) {
            return true;
        }

        if (!a->isOnline() && b->isOnline()) {
            return false;
        }
        return a->getNameItem().toUpper() < b->getNameItem().toUpper();
    }

    return a->getLastActivity() > b->getLastActivity();
}


{
    if (filterParams.searchString == searchString && filterParams.hideOnline == hideOnline &&
        filterParams.hideOffline == hideOffline && filterParams.hideGroups == hideGroups) {
        return;
    }
    filterParams.searchString = searchString;

/*

    This file is part of qTox, a Qt-based graphical interface for Tox.

*/

#include "friendlistmanager.h"
#include "src/widget/genericchatroomwidget.h"

FriendListManager::FriendListManager(int countContacts, QObject *parent) : QObject(parent)
{
    this->countContacts = countContacts;
}

QVector<FriendListManager::IFriendListItemPtr> FriendListManager::getItems() const
{
    return items;
}

bool FriendListManager::needHideCircles() const
{
    return hideCircles;
}

bool FriendListManager::getPositionsChanged() const
{
    return positionsChanged;
}

void FriendListManager::addFriendListItem(IFriendListItem *item)
{
    if (item->isGroup()) {
        items.push_back(IFriendListItemPtr(item, [](IFriendListItem* groupItem){
                            groupItem->getWidget()->deleteLater();}));
    } else {
        items.push_back(IFriendListItemPtr(item));
    }

    if (countContacts <= items.size()) {
        countContacts = 0;
        setSortRequired();
    }
}

void FriendListManager::removeFriendListItem(IFriendListItem *item)
{
    removeAll(item);
    setSortRequired();
}

void FriendListManager::sortByName()
{
    byName = true;
    updatePositions();
}

void FriendListManager::sortByActivity()
{
    byName = false;
    updatePositions();
}

void FriendListManager::resetParents()
{
    for (int i = 0; i < items.size(); ++i) {
        IFriendListItem* itemTmp = items[i].get();
        itemTmp->getWidget()->setParent(nullptr);
    }
}

void FriendListManager::setFilter(const QString &searchString, bool hideOnline, bool hideOffline,
                                  bool hideGroups)

    filterParams.hideOnline = hideOnline;
    filterParams.hideOffline = hideOffline;
    filterParams.hideGroups = hideGroups;

    setSortRequired();
}

void FriendListManager::applyFilter()
{
    QString searchString = filterParams.searchString;

    for (IFriendListItemPtr itemTmp : items) {
        if (searchString.isEmpty()) {
            itemTmp->getWidget()->setVisible(true);
        } else {
            QString tmp_name = itemTmp->getNameItem();
            itemTmp->getWidget()->setVisible(tmp_name.contains(searchString, Qt::CaseInsensitive));
        }

        if (filterParams.hideOnline && itemTmp->isOnline()) {
            if (itemTmp->isFriend()) {
                itemTmp->getWidget()->setVisible(false);
            }
        }

        if (filterParams.hideOffline && !itemTmp->isOnline()) {
            itemTmp->getWidget()->setVisible(false);
        }

        if (filterParams.hideGroups && itemTmp->isGroup()) {
            itemTmp->getWidget()->setVisible(false);
        }
    }

    if (filterParams.hideOnline && filterParams.hideOffline) {
        hideCircles = true;
    } else {
        hideCircles = false;
    }
}

void FriendListManager::updatePositions()
{
    positionsChanged = true;

    if (byName) {
        auto sortName = [&](const IFriendListItemPtr &a, const IFriendListItemPtr &b) {
                            return cmpByName(a, b, groupsOnTop);
                        };
        if (!needSort) {
            if (std::is_sorted(items.begin(), items.end(), sortName)) {
                positionsChanged = false;
                return;
            }
        }
        std::sort(items.begin(), items.end(),sortName);

    } else {
        auto sortActivity = [&](const IFriendListItemPtr &a, const IFriendListItemPtr &b) {
                                return cmpByActivity(a, b);
                            };
        if (!needSort) {
            if (std::is_sorted(items.begin(), items.end(), sortActivity)) {
                positionsChanged = false;
                return;
            }
        }
        std::sort(items.begin(), items.end(), sortActivity);
    }

    needSort = false;
}

void FriendListManager::setSortRequired()
{
    needSort = true;
    emit itemsChanged();
}

void FriendListManager::setGroupsOnTop(bool v)
{
    groupsOnTop = v;
}

void FriendListManager::removeAll(IFriendListItem* item)
{
    for (int i = 0; i < items.size(); ++i) {
        if (items[i].get() == item) {
            items.remove(i);
            --i;
        }
    }
}

bool FriendListManager::cmpByName(const IFriendListItemPtr &a, const IFriendListItemPtr &b,
                                  bool groupsOnTop)
{
    if (a->isGroup() && !b->isGroup()) {
        if (groupsOnTop) {
            return true;
        }
        return !b->isOnline();
    }

    if (!a->isGroup() && b->isGroup()) {
        if (groupsOnTop) {
            return false;
        }
        return a->isOnline();
    }

    if (a->isOnline() && !b->isOnline()) {
        return true;
    }

    if (!a->isOnline() && b->isOnline()) {
        return false;
    }

    return a->getNameItem().toUpper() < b->getNameItem().toUpper();
}

bool FriendListManager::cmpByActivity(const IFriendListItemPtr &a, const IFriendListItemPtr &b)
{
    if (a->isGroup() || b->isGroup()) {
        if (a->isGroup() && !b->isGroup()) {
            return true;
        }

        if (!a->isGroup() && b->isGroup()) {
            return false;
        }
        return a->getNameItem().toUpper() < b->getNameItem().toUpper();
    }

    QDateTime dateA = a->getLastActivity();
    QDateTime dateB = b->getLastActivity();
    if (dateA.date() == dateB.date()) {
        if (a->isOnline() && !b->isOnline()) {
            return true;
        }

        if (!a->isOnline() && b->isOnline()) {
            return false;
        }
        return a->getNameItem().toUpper() < b->getNameItem().toUpper();
    }

    return a->getLastActivity() > b->getLastActivity();
}


filterParams.hideOnline = hideOnline;
    filterParams.hideOffline = hideOffline;

/*

    This file is part of qTox, a Qt-based graphical interface for Tox.

*/

#include "friendlistmanager.h"
#include "src/widget/genericchatroomwidget.h"

FriendListManager::FriendListManager(int countContacts, QObject *parent) : QObject(parent)
{
    this->countContacts = countContacts;
}

QVector<FriendListManager::IFriendListItemPtr> FriendListManager::getItems() const
{
    return items;
}

bool FriendListManager::needHideCircles() const
{
    return hideCircles;
}

bool FriendListManager::getPositionsChanged() const
{
    return positionsChanged;
}

void FriendListManager::addFriendListItem(IFriendListItem *item)
{
    if (item->isGroup()) {
        items.push_back(IFriendListItemPtr(item, [](IFriendListItem* groupItem){
                            groupItem->getWidget()->deleteLater();}));
    } else {
        items.push_back(IFriendListItemPtr(item));
    }

    if (countContacts <= items.size()) {
        countContacts = 0;
        setSortRequired();
    }
}

void FriendListManager::removeFriendListItem(IFriendListItem *item)
{
    removeAll(item);
    setSortRequired();
}

void FriendListManager::sortByName()
{
    byName = true;
    updatePositions();
}

void FriendListManager::sortByActivity()
{
    byName = false;
    updatePositions();
}

void FriendListManager::resetParents()
{
    for (int i = 0; i < items.size(); ++i) {
        IFriendListItem* itemTmp = items[i].get();
        itemTmp->getWidget()->setParent(nullptr);
    }
}

void FriendListManager::setFilter(const QString &searchString, bool hideOnline, bool hideOffline,
                                  bool hideGroups)
{
    if (filterParams.searchString == searchString && filterParams.hideOnline == hideOnline &&
        filterParams.hideOffline == hideOffline && filterParams.hideGroups == hideGroups) {
        return;
    }
    filterParams.searchString = searchString;

    filterParams.hideGroups = hideGroups;

    setSortRequired();
}

void FriendListManager::applyFilter()
{
    QString searchString = filterParams.searchString;

    for (IFriendListItemPtr itemTmp : items) {
        if (searchString.isEmpty()) {
            itemTmp->getWidget()->setVisible(true);
        } else {
            QString tmp_name = itemTmp->getNameItem();
            itemTmp->getWidget()->setVisible(tmp_name.contains(searchString, Qt::CaseInsensitive));
        }

        if (filterParams.hideOnline && itemTmp->isOnline()) {
            if (itemTmp->isFriend()) {
                itemTmp->getWidget()->setVisible(false);
            }
        }

        if (filterParams.hideOffline && !itemTmp->isOnline()) {
            itemTmp->getWidget()->setVisible(false);
        }

        if (filterParams.hideGroups && itemTmp->isGroup()) {
            itemTmp->getWidget()->setVisible(false);
        }
    }

    if (filterParams.hideOnline && filterParams.hideOffline) {
        hideCircles = true;
    } else {
        hideCircles = false;
    }
}

void FriendListManager::updatePositions()
{
    positionsChanged = true;

    if (byName) {
        auto sortName = [&](const IFriendListItemPtr &a, const IFriendListItemPtr &b) {
                            return cmpByName(a, b, groupsOnTop);
                        };
        if (!needSort) {
            if (std::is_sorted(items.begin(), items.end(), sortName)) {
                positionsChanged = false;
                return;
            }
        }
        std::sort(items.begin(), items.end(),sortName);

    } else {
        auto sortActivity = [&](const IFriendListItemPtr &a, const IFriendListItemPtr &b) {
                                return cmpByActivity(a, b);
                            };
        if (!needSort) {
            if (std::is_sorted(items.begin(), items.end(), sortActivity)) {
                positionsChanged = false;
                return;
            }
        }
        std::sort(items.begin(), items.end(), sortActivity);
    }

    needSort = false;
}

void FriendListManager::setSortRequired()
{
    needSort = true;
    emit itemsChanged();
}

void FriendListManager::setGroupsOnTop(bool v)
{
    groupsOnTop = v;
}

void FriendListManager::removeAll(IFriendListItem* item)
{
    for (int i = 0; i < items.size(); ++i) {
        if (items[i].get() == item) {
            items.remove(i);
            --i;
        }
    }
}

bool FriendListManager::cmpByName(const IFriendListItemPtr &a, const IFriendListItemPtr &b,
                                  bool groupsOnTop)
{
    if (a->isGroup() && !b->isGroup()) {
        if (groupsOnTop) {
            return true;
        }
        return !b->isOnline();
    }

    if (!a->isGroup() && b->isGroup()) {
        if (groupsOnTop) {
            return false;
        }
        return a->isOnline();
    }

    if (a->isOnline() && !b->isOnline()) {
        return true;
    }

    if (!a->isOnline() && b->isOnline()) {
        return false;
    }

    return a->getNameItem().toUpper() < b->getNameItem().toUpper();
}

bool FriendListManager::cmpByActivity(const IFriendListItemPtr &a, const IFriendListItemPtr &b)
{
    if (a->isGroup() || b->isGroup()) {
        if (a->isGroup() && !b->isGroup()) {
            return true;
        }

        if (!a->isGroup() && b->isGroup()) {
            return false;
        }
        return a->getNameItem().toUpper() < b->getNameItem().toUpper();
    }

    QDateTime dateA = a->getLastActivity();
    QDateTime dateB = b->getLastActivity();
    if (dateA.date() == dateB.date()) {
        if (a->isOnline() && !b->isOnline()) {
            return true;
        }

        if (!a->isOnline() && b->isOnline()) {
            return false;
        }
        return a->getNameItem().toUpper() < b->getNameItem().toUpper();
    }

    return a->getLastActivity() > b->getLastActivity();
}


filterParams.hideGroups = hideGroups;

    setSortRequired();
}

/*

    This file is part of qTox, a Qt-based graphical interface for Tox.

*/

#include "friendlistmanager.h"
#include "src/widget/genericchatroomwidget.h"

FriendListManager::FriendListManager(int countContacts, QObject *parent) : QObject(parent)
{
    this->countContacts = countContacts;
}

QVector<FriendListManager::IFriendListItemPtr> FriendListManager::getItems() const
{
    return items;
}

bool FriendListManager::needHideCircles() const
{
    return hideCircles;
}

bool FriendListManager::getPositionsChanged() const
{
    return positionsChanged;
}

void FriendListManager::addFriendListItem(IFriendListItem *item)
{
    if (item->isGroup()) {
        items.push_back(IFriendListItemPtr(item, [](IFriendListItem* groupItem){
                            groupItem->getWidget()->deleteLater();}));
    } else {
        items.push_back(IFriendListItemPtr(item));
    }

    if (countContacts <= items.size()) {
        countContacts = 0;
        setSortRequired();
    }
}

void FriendListManager::removeFriendListItem(IFriendListItem *item)
{
    removeAll(item);
    setSortRequired();
}

void FriendListManager::sortByName()
{
    byName = true;
    updatePositions();
}

void FriendListManager::sortByActivity()
{
    byName = false;
    updatePositions();
}

void FriendListManager::resetParents()
{
    for (int i = 0; i < items.size(); ++i) {
        IFriendListItem* itemTmp = items[i].get();
        itemTmp->getWidget()->setParent(nullptr);
    }
}

void FriendListManager::setFilter(const QString &searchString, bool hideOnline, bool hideOffline,
                                  bool hideGroups)
{
    if (filterParams.searchString == searchString && filterParams.hideOnline == hideOnline &&
        filterParams.hideOffline == hideOffline && filterParams.hideGroups == hideGroups) {
        return;
    }
    filterParams.searchString = searchString;
    filterParams.hideOnline = hideOnline;
    filterParams.hideOffline = hideOffline;

void FriendListManager::applyFilter()
{
    QString searchString = filterParams.searchString;

    for (IFriendListItemPtr itemTmp : items) {
        if (searchString.isEmpty()) {
            itemTmp->getWidget()->setVisible(true);
        } else {
            QString tmp_name = itemTmp->getNameItem();
            itemTmp->getWidget()->setVisible(tmp_name.contains(searchString, Qt::CaseInsensitive));
        }

        if (filterParams.hideOnline && itemTmp->isOnline()) {
            if (itemTmp->isFriend()) {
                itemTmp->getWidget()->setVisible(false);
            }
        }

        if (filterParams.hideOffline && !itemTmp->isOnline()) {
            itemTmp->getWidget()->setVisible(false);
        }

        if (filterParams.hideGroups && itemTmp->isGroup()) {
            itemTmp->getWidget()->setVisible(false);
        }
    }

    if (filterParams.hideOnline && filterParams.hideOffline) {
        hideCircles = true;
    } else {
        hideCircles = false;
    }
}

void FriendListManager::updatePositions()
{
    positionsChanged = true;

    if (byName) {
        auto sortName = [&](const IFriendListItemPtr &a, const IFriendListItemPtr &b) {
                            return cmpByName(a, b, groupsOnTop);
                        };
        if (!needSort) {
            if (std::is_sorted(items.begin(), items.end(), sortName)) {
                positionsChanged = false;
                return;
            }
        }
        std::sort(items.begin(), items.end(),sortName);

    } else {
        auto sortActivity = [&](const IFriendListItemPtr &a, const IFriendListItemPtr &b) {
                                return cmpByActivity(a, b);
                            };
        if (!needSort) {
            if (std::is_sorted(items.begin(), items.end(), sortActivity)) {
                positionsChanged = false;
                return;
            }
        }
        std::sort(items.begin(), items.end(), sortActivity);
    }

    needSort = false;
}

void FriendListManager::setSortRequired()
{
    needSort = true;
    emit itemsChanged();
}

void FriendListManager::setGroupsOnTop(bool v)
{
    groupsOnTop = v;
}

void FriendListManager::removeAll(IFriendListItem* item)
{
    for (int i = 0; i < items.size(); ++i) {
        if (items[i].get() == item) {
            items.remove(i);
            --i;
        }
    }
}

bool FriendListManager::cmpByName(const IFriendListItemPtr &a, const IFriendListItemPtr &b,
                                  bool groupsOnTop)
{
    if (a->isGroup() && !b->isGroup()) {
        if (groupsOnTop) {
            return true;
        }
        return !b->isOnline();
    }

    if (!a->isGroup() && b->isGroup()) {
        if (groupsOnTop) {
            return false;
        }
        return a->isOnline();
    }

    if (a->isOnline() && !b->isOnline()) {
        return true;
    }

    if (!a->isOnline() && b->isOnline()) {
        return false;
    }

    return a->getNameItem().toUpper() < b->getNameItem().toUpper();
}

bool FriendListManager::cmpByActivity(const IFriendListItemPtr &a, const IFriendListItemPtr &b)
{
    if (a->isGroup() || b->isGroup()) {
        if (a->isGroup() && !b->isGroup()) {
            return true;
        }

        if (!a->isGroup() && b->isGroup()) {
            return false;
        }
        return a->getNameItem().toUpper() < b->getNameItem().toUpper();
    }

    QDateTime dateA = a->getLastActivity();
    QDateTime dateB = b->getLastActivity();
    if (dateA.date() == dateB.date()) {
        if (a->isOnline() && !b->isOnline()) {
            return true;
        }

        if (!a->isOnline() && b->isOnline()) {
            return false;
        }
        return a->getNameItem().toUpper() < b->getNameItem().toUpper();
    }

    return a->getLastActivity() > b->getLastActivity();
}


/*

    This file is part of qTox, a Qt-based graphical interface for Tox.

*/

#include "friendlistmanager.h"
#include "src/widget/genericchatroomwidget.h"

FriendListManager::FriendListManager(int countContacts, QObject *parent) : QObject(parent)
{
    this->countContacts = countContacts;
}

QVector<FriendListManager::IFriendListItemPtr> FriendListManager::getItems() const
{
    return items;
}

bool FriendListManager::needHideCircles() const
{
    return hideCircles;
}

bool FriendListManager::getPositionsChanged() const
{
    return positionsChanged;
}

void FriendListManager::addFriendListItem(IFriendListItem *item)
{
    if (item->isGroup()) {
        items.push_back(IFriendListItemPtr(item, [](IFriendListItem* groupItem){
                            groupItem->getWidget()->deleteLater();}));
    } else {
        items.push_back(IFriendListItemPtr(item));
    }

    if (countContacts <= items.size()) {
        countContacts = 0;
        setSortRequired();
    }
}

void FriendListManager::removeFriendListItem(IFriendListItem *item)
{
    removeAll(item);
    setSortRequired();
}

void FriendListManager::sortByName()
{
    byName = true;
    updatePosit

}

void FriendListManager::sortByActivity()
{
    byName = false;
    updatePositions();
}

void FriendListManager::resetParents()
{
    for (int i = 0; i < items.size(); ++i) {
        IFriendListItem* itemTmp = items[i].get();
        itemTmp->getWidget()->setParent(nullptr);
    }
}

void FriendListManager::setFilter(const QString &searchString, bool hideOnline, bool hideOffline,
                                  bool hideGroups)
{
    if (filterParams.searchString == searchString && filterParams.hideOnline == hideOnline &&
        filterParams.hideOffline == hideOffline && filterParams.hideGroups == hideGroups) {
        return;
    }
    filterParams.searchString = searchString;
    filterParams.hideOnline = hideOnline;
    filterParams.hideOffline = hideOffline;
    filterParams.hideGroups = hideGroups;

    setSortRequired();
}

void FriendListManager::applyFilter()
{
    QString searchString = filterParams.searchString;

    for (IFriendListItemPtr itemTmp : items) {
        if (searchString.isEmpty()) {
            itemTmp->getWidget()->setVisible(true);
        } else {
            QString tmp_name = itemTmp->getNameItem();
            itemTmp->getWidget()->setVisible(tmp_name.contains(searchString, Qt::CaseInsensitive));
        }

        if (filterParams.hideOnline && itemTmp->isOnline()) {
            if (itemTmp->isFriend()) {
                itemTmp->getWidget()->setVisible(false);
            }
        }

        if (filterParams.hideOffline && !itemTmp->isOnline()) {
            itemTmp->getWidget()->setVisible(false);
        }

        if (filterParams.hideGroups && itemTmp->isGroup()) {
            itemTmp->getWidget()->setVisible(false);
        }
    }

    if (filterParams.hideOnline && filterParams.hideOffline) {
        hideCircles = true;
    } else {
        hideCircles = false;
    }
}

void FriendListManager::updatePositions()
{
    positionsChanged = true;

    if (byName) {
        auto sortName = [&](const IFriendListItemPtr &a, const IFriendListItemPtr &b) {
                            return cmpByName(a, b, groupsOnTop);
                        };
        if (!needSort) {
            if (std::is_sorted(items.begin(), items.end(), sortName)) {
                positionsChanged = false;
                return;
            }
        }
        std::sort(items.begin(), items.end(),sortName);

    } else {
        auto sortActivity = [&](const IFriendListItemPtr &a, const IFriendListItemPtr &b) {
                                return cmpByActivity(a, b);
                            };
        if (!needSort) {
            if (std::is_sorted(items.begin(), items.end(), sortActivity)) {
                positionsChanged = false;
                return;
            }
        }
        std::sort(items.begin(), items.end(), sortActivity);
    }

    needSort = false;
}

void FriendListManager::setSortRequired()
{
    needSort = true;
    emit itemsChanged();
}

void FriendListManager::setGroupsOnTop(bool v)
{
    groupsOnTop = v;
}

void FriendListManager::removeAll(IFriendListItem* item)
{
    for (int i = 0; i < items.size(); ++i) {
        if (items[i].get() == item) {
            items.remove(i);
            --i;
        }
    }
}

bool FriendListManager::cmpByName(const IFriendListItemPtr &a, const IFriendListItemPtr &b,
                                  bool groupsOnTop)
{
    if (a->isGroup() && !b->isGroup()) {
        if (groupsOnTop) {
            return true;
        }
        return !b->isOnline();
    }

    if (!a->isGroup() && b->isGroup()) {
        if (groupsOnTop) {
            return false;
        }
        return a->isOnline();
    }

    if (a->isOnline() && !b->isOnline()) {
        return true;
    }

    if (!a->isOnline() && b->isOnline()) {
        return false;
    }

    return a->getNameItem().toUpper() < b->getNameItem().toUpper();
}

bool FriendListManager::cmpByActivity(const IFriendListItemPtr &a, const IFriendListItemPtr &b)
{
    if (a->isGroup() || b->isGroup()) {
        if (a->isGroup() && !b->isGroup()) {
            return true;
        }

        if (!a->isGroup() && b->isGroup()) {
            return false;
        }
        return a->getNameItem().toUpper() < b->getNameItem().toUpper();
    }

    QDateTime dateA = a->getLastActivity();
    QDateTime dateB = b->getLastActivity();
    if (dateA.date() == dateB.date()) {
        if (a->isOnline() && !b->isOnline()) {
            return true;
        }

        if (!a->isOnline() && b->isOnline()) {
            return false;
        }
        return a->getNameItem().toUpper() < b->getNameItem().toUpper();
    }

    return a->getLastActivity() > b->getLastActivity();
}


if (filterParams.hideOffline && !itemTmp->isOnline()) {
            itemTmp->getWidget()->setVisible(false);
        }

/*

    This file is part of qTox, a Qt-based graphical interface for Tox.

*/

#include "friendlistmanager.h"
#include "src/widget/genericchatroomwidget.h"

FriendListManager::FriendListManager(int countContacts, QObject *parent) : QObject(parent)
{
    this->countContacts = countContacts;
}

QVector<FriendListManager::IFriendListItemPtr> FriendListManager::getItems() const
{
    return items;
}

bool FriendListManager::needHideCircles() const
{
    return hideCircles;
}

bool FriendListManager::getPositionsChanged() const
{
    return positionsChanged;
}

void FriendListManager::addFriendListItem(IFriendListItem *item)
{
    if (item->isGroup()) {
        items.push_back(IFriendListItemPtr(item, [](IFriendListItem* groupItem){
                            groupItem->getWidget()->deleteLater();}));
    } else {
        items.push_back(IFriendListItemPtr(item));
    }

    if (countContacts <= items.size()) {
        countContacts = 0;
        setSortRequired();
    }
}

void FriendListManager::removeFriendListItem(IFriendListItem *item)
{
    removeAll(item);
    setSortRequired();
}

void FriendListManager::sortByName()
{
    byName = true;
    updatePositions();
}

void FriendListManager::sortByActivity()
{
    byName = false;
    updatePositions();
}

void FriendListManager::resetParents()
{
    for (int i = 0; i < items.size(); ++i) {
        IFriendListItem* itemTmp = items[i].get();
        itemTmp->getWidget()->setParent(nullptr);
    }
}

void FriendListManager::setFilter(const QString &searchString, bool hideOnline, bool hideOffline,
                                  bool hideGroups)
{
    if (filterParams.searchString == searchString && filterParams.hideOnline == hideOnline &&
        filterParams.hideOffline == hideOffline && filterParams.hideGroups == hideGroups) {
        return;
    }
    filterParams.searchString = searchString;
    filterParams.hideOnline = hideOnline;
    filterParams.hideOffline = hideOffline;
    filterParams.hideGroups = hideGroups;

    setSortRequired();
}

void FriendListManager::applyFilter()
{
    QString searchString = filterParams.searchString;

    for (IFriendListItemPtr itemTmp : items) {
        if (searchString.isEmpty()) {
            itemTmp->getWidget()->setVisible(true);
        } else {
            QString tmp_name = itemTmp->getNameItem();
            itemTmp->getWidget()->setVisible(tmp_name.contains(searchString, Qt::CaseInsensitive));
        }

        if (filterParams.hideOnline && itemTmp->isOnline()) {
            if (itemTmp->isFriend()) {
                itemTmp->getWidget()->setVisible(false);
            }
        }

        if (filterParams.hideGroups && itemTmp->isGroup()) {
            itemTmp->getWidget()->setVisible(false);
        }
    }

    if (filterParams.hideOnline && filterParams.hideOffline) {
        hideCircles = true;
    } else {
        hideCircles = false;
    }
}

void FriendListManager::updatePositions()
{
    positionsChanged = true;

    if (byName) {
        auto sortName = [&](const IFriendListItemPtr &a, const IFriendListItemPtr &b) {
                            return cmpByName(a, b, groupsOnTop);
                        };
        if (!needSort) {
            if (std::is_sorted(items.begin(), items.end(), sortName)) {
                positionsChanged = false;
                return;
            }
        }
        std::sort(items.begin(), items.end(),sortName);

    } else {
        auto sortActivity = [&](const IFriendListItemPtr &a, const IFriendListItemPtr &b) {
                                return cmpByActivity(a, b);
                            };
        if (!needSort) {
            if (std::is_sorted(items.begin(), items.end(), sortActivity)) {
                positionsChanged = false;
                return;
            }
        }
        std::sort(items.begin(), items.end(), sortActivity);
    }

    needSort = false;
}

void FriendListManager::setSortRequired()
{
    needSort = true;
    emit itemsChanged();
}

void FriendListManager::setGroupsOnTop(bool v)
{
    groupsOnTop = v;
}

void FriendListManager::removeAll(IFriendListItem* item)
{
    for (int i = 0; i < items.size(); ++i) {
        if (items[i].get() == item) {
            items.remove(i);
            --i;
        }
    }
}

bool FriendListManager::cmpByName(const IFriendListItemPtr &a, const IFriendListItemPtr &b,
                                  bool groupsOnTop)
{
    if (a->isGroup() && !b->isGroup()) {
        if (groupsOnTop) {
            return true;
        }
        return !b->isOnline();
    }

    if (!a->isGroup() && b->isGroup()) {
        if (groupsOnTop) {
            return false;
        }
        return a->isOnline();
    }

    if (a->isOnline() && !b->isOnline()) {
        return true;
    }

    if (!a->isOnline() && b->isOnline()) {
        return false;
    }

    return a->getNameItem().toUpper() < b->getNameItem().toUpper();
}

bool FriendListManager::cmpByActivity(const IFriendListItemPtr &a, const IFriendListItemPtr &b)
{
    if (a->isGroup() || b->isGroup()) {
        if (a->isGroup() && !b->isGroup()) {
            return true;
        }

        if (!a->isGroup() && b->isGroup()) {
            return false;
        }
        return a->getNameItem().toUpper() < b->getNameItem().toUpper();
    }

    QDateTime dateA = a->getLastActivity();
    QDateTime dateB = b->getLastActivity();
    if (dateA.date() == dateB.date()) {
        if (a->isOnline() && !b->isOnline()) {
            return true;
        }

        if (!a->isOnline() && b->isOnline()) {
            return false;
        }
        return a->getNameItem().toUpper() < b->getNameItem().toUpper();
    }

    return a->getLastActivity() > b->getLastActivity();
}


for (int i = 0; i < items.size(); ++i) {
        if (items[i].get() == item) {
            items.remove(i);
            --i;
        }
    }

/*

    This file is part of qTox, a Qt-based graphical interface for Tox.

*/

#include "friendlistmanager.h"
#include "src/widget/genericchatroomwidget.h"

FriendListManager::FriendListManager(int countContacts, QObject *parent) : QObject(parent)
{
    this->countContacts = countContacts;
}

QVector<FriendListManager::IFriendListItemPtr> FriendListManager::getItems() const
{
    return items;
}

bool FriendListManager::needHideCircles() const
{
    return hideCircles;
}

bool FriendListManager::getPositionsChanged() const
{
    return positionsChanged;
}

void FriendListManager::addFriendListItem(IFriendListItem *item)
{
    if (item->isGroup()) {
        items.push_back(IFriendListItemPtr(item, [](IFriendListItem* groupItem){
                            groupItem->getWidget()->deleteLater();}));
    } else {
        items.push_back(IFriendListItemPtr(item));
    }

    if (countContacts <= items.size()) {
        countContacts = 0;
        setSortRequired();
    }
}

void FriendListManager::removeFriendListItem(IFriendListItem *item)
{
    removeAll(item);
    setSortRequired();
}

void FriendListManager::sortByName()
{
    byName = true;
    updatePositions();
}

void FriendListManager::sortByActivity()
{
    byName = false;
    updatePositions();
}

void FriendListManager::resetParents()
{
    for (int i = 0; i < items.size(); ++i) {
        IFriendListItem* itemTmp = items[i].get();
        itemTmp->getWidget()->setParent(nullptr);
    }
}

void FriendListManager::setFilter(const QString &searchString, bool hideOnline, bool hideOffline,
                                  bool hideGroups)
{
    if (filterParams.searchString == searchString && filterParams.hideOnline == hideOnline &&
        filterParams.hideOffline == hideOffline && filterParams.hideGroups == hideGroups) {
        return;
    }
    filterParams.searchString = searchString;
    filterParams.hideOnline = hideOnline;
    filterParams.hideOffline = hideOffline;
    filterParams.hideGroups = hideGroups;

    setSortRequired();
}

void FriendListManager::applyFilter()
{
    QString searchString = filterParams.searchString;

    for (IFriendListItemPtr itemTmp : items) {
        if (searchString.isEmpty()) {
            itemTmp->getWidget()->setVisible(true);
        } else {
            QString tmp_name = itemTmp->getNameItem();
            itemTmp->getWidget()->setVisible(tmp_name.contains(searchString, Qt::CaseInsensitive));
        }

        if (filterParams.hideOnline && itemTmp->isOnline()) {
            if (itemTmp->isFriend()) {
                itemTmp->getWidget()->setVisible(false);
            }
        }

        if (filterParams.hideOffline && !itemTmp->isOnline()) {
            itemTmp->getWidget()->setVisible(false);
        }

        if (filterParams.hideGroups && itemTmp->isGroup()) {
            itemTmp->getWidget()->setVisible(false);
        }
    }

    if (filterParams.hideOnline && filterParams.hideOffline) {
        hideCircles = true;
    } else {
        hideCircles = false;
    }
}

void FriendListManager::updatePositions()
{
    positionsChanged = true;

    if (byName) {
        auto sortName = [&](const IFriendListItemPtr &a, const IFriendListItemPtr &b) {
                            return cmpByName(a, b, groupsOnTop);
                        };
        if (!needSort) {
            if (std::is_sorted(items.begin(), items.end(), sortName)) {
                positionsChanged = false;
                return;
            }
        }
        std::sort(items.begin(), items.end(),sortName);

    } else {
        auto sortActivity = [&](const IFriendListItemPtr &a, const IFriendListItemPtr &b) {
                                return cmpByActivity(a, b);
                            };
        if (!needSort) {
            if (std::is_sorted(items.begin(), items.end(), sortActivity)) {
                positionsChanged = false;
                return;
            }
        }
        std::sort(items.begin(), items.end(), sortActivity);
    }

    needSort = false;
}

void FriendListManager::setSortRequired()
{
    needSort = true;
    emit itemsChanged();
}

void FriendListManager::setGroupsOnTop(bool v)
{
    groupsOnTop = v;
}

void FriendListManager::removeAll(IFriendListItem* item)
{

}

bool FriendListManager::cmpByName(const IFriendListItemPtr &a, const IFriendListItemPtr &b,
                                  bool groupsOnTop)
{
    if (a->isGroup() && !b->isGroup()) {
        if (groupsOnTop) {
            return true;
        }
        return !b->isOnline();
    }

    if (!a->isGroup() && b->isGroup()) {
        if (groupsOnTop) {
            return false;
        }
        return a->isOnline();
    }

    if (a->isOnline() && !b->isOnline()) {
        return true;
    }

    if (!a->isOnline() && b->isOnline()) {
        return false;
    }

    return a->getNameItem().toUpper() < b->getNameItem().toUpper();
}

bool FriendListManager::cmpByActivity(const IFriendListItemPtr &a, const IFriendListItemPtr &b)
{
    if (a->isGroup() || b->isGroup()) {
        if (a->isGroup() && !b->isGroup()) {
            return true;
        }

        if (!a->isGroup() && b->isGroup()) {
            return false;
        }
        return a->getNameItem().toUpper() < b->getNameItem().toUpper();
    }

    QDateTime dateA = a->getLastActivity();
    QDateTime dateB = b->getLastActivity();
    if (dateA.date() == dateB.date()) {
        if (a->isOnline() && !b->isOnline()) {
            return true;
        }

        if (!a->isOnline() && b->isOnline()) {
            return false;
        }
        return a->getNameItem().toUpper() < b->getNameItem().toUpper();
    }

    return a->getLastActivity() > b->getLastActivity();
}


ChaCha20Poly1305Impl::ChaCha20Poly1305Impl(Key key)
      : key_{key},
        cipher_{EVP_chacha20_poly1305()},
        block_size_{EVP_CIPHER_block_size(cipher_)}

/**
 */

#include <gsl/gsl_util>
#include <libp2p/crypto/chachapoly/chachapoly_impl.hpp>
#include <libp2p/crypto/common_functions.hpp>
#include <libp2p/crypto/error.hpp>

namespace libp2p::crypto::chachapoly {

#define IF1(expr, err, result) \
  if (1 != (expr)) {           \
    log_->error((err));        \
    return (result);           \
  }

{}

  outcome::result<ByteArray> ChaCha20Poly1305Impl::encrypt(
      const Nonce &nonce, gsl::span<const uint8_t> plaintext,
      gsl::span<const uint8_t> aad) {
    const auto init_failure = OpenSslError::FAILED_INITIALIZE_OPERATION;
    std::vector<uint8_t> result;
    // just reserving the space, possibly without actual memory allocation:
    // ciphertext length equals to plaintext length
    // one block size for EncryptFinal_ex (usually not required)
    result.reserve(plaintext.size() + block_size_ + 16);
    auto *ctx = EVP_CIPHER_CTX_new();
    if (nullptr == ctx) {
      return OpenSslError::FAILED_INITIALIZE_CONTEXT;
    }
    auto free_ctx = gsl::finally([ctx] { EVP_CIPHER_CTX_free(ctx); });

    IF1(EVP_EncryptInit_ex(ctx, cipher_, nullptr, nullptr, nullptr),
        "Failed to initialize EVP encryption.", init_failure)

    IF1(EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, 12, nullptr),
        "Cannot set AEAD initialization vector length.", init_failure)

    IF1(EVP_EncryptInit_ex(ctx, nullptr, nullptr, key_.data(), nonce.data()),
        "Cannot finalize init of encryption engine.", init_failure)

    int len{0};
    IF1(EVP_EncryptUpdate(ctx, nullptr, &len, nullptr, plaintext.size()),
        "Failed to calculate cipher text length.",
        OpenSslError::FAILED_ENCRYPT_UPDATE)

    // does actual memory allocation
    result.resize(len + block_size_ + 16, 0u);
    if (not aad.empty()) {
      IF1(EVP_EncryptUpdate(ctx, nullptr, &len, aad.data(), aad.size()),
          "Failed to apply additional authentication data during encryption.",
          OpenSslError::FAILED_ENCRYPT_UPDATE)
    }

    IF1(EVP_EncryptUpdate(ctx, result.data(), &len, plaintext.data(),
                          plaintext.size()),
        "Plaintext encryption failed.", OpenSslError::FAILED_ENCRYPT_UPDATE)
    int ciphertext_len = len;  // without tag size
    IF1(EVP_EncryptFinal_ex(ctx, result.data() + len, &len),  // NOLINT
        "Unable to finalize encryption.", OpenSslError::FAILED_ENCRYPT_FINALIZE)

    ciphertext_len += len;
    IF1(EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, EVP_CTRL_AEAD_GET_TAG,
                            result.data() + ciphertext_len),  // NOLINT
        "Failed to write tag.", OpenSslError::FAILED_ENCRYPT_FINALIZE)

    // remove the last block_size bytes if those were not used
    result.resize(ciphertext_len + 16);
    return result;
  }

  outcome::result<ByteArray> ChaCha20Poly1305Impl::decrypt(
      const Nonce &nonce, gsl::span<const uint8_t> ciphertext,
      gsl::span<const uint8_t> aad) {
    ByteArray result;
    // plain text should take less bytes than cipher text,
    // at least it would not contain tag-length bytes (16).
    // single block size for the case when len(in) == len(out)
    result.resize(ciphertext.size() + block_size_, 0u);
    auto *ctx = EVP_CIPHER_CTX_new();
    auto free_ctx = gsl::finally([ctx] { EVP_CIPHER_CTX_free(ctx); });

    IF1(EVP_DecryptInit_ex(ctx, cipher_, nullptr, nullptr, nullptr),
        "Failed to initialize decryption engine.",
        OpenSslError::FAILED_INITIALIZE_OPERATION)

    IF1(EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, 12, nullptr),
        "Cannot set AEAD initialization vector length.",
        OpenSslError::FAILED_INITIALIZE_OPERATION)

    IF1(EVP_CIPHER_CTX_ctrl(
            ctx, EVP_CTRL_AEAD_SET_TAG, EVP_CTRL_AEAD_GET_TAG,
            (uint8_t *)ciphertext.data() + ciphertext.size() - 16),  // NOLINT
        "Failed to specify buffer for further tag reading.",
        OpenSslError::FAILED_DECRYPT_UPDATE)

    IF1(EVP_DecryptInit_ex(ctx, nullptr, nullptr, key_.data(), nonce.data()),
        "Cannot finalize init of decryption engine.",
        OpenSslError::FAILED_INITIALIZE_OPERATION)

    int len{0};
    if (not aad.empty()) {
      IF1(EVP_DecryptUpdate(ctx, nullptr, &len, aad.data(), aad.size()),
          "Failed to apply additional authentication data during decryption.",
          OpenSslError::FAILED_DECRYPT_UPDATE)
    }

    IF1(EVP_DecryptUpdate(ctx, result.data(), &len, ciphertext.data(),
                          ciphertext.size() - 16),
        "Ciphertext decryption failed.", OpenSslError::FAILED_DECRYPT_UPDATE)

    IF1(EVP_DecryptFinal_ex(ctx,
                            result.data() + len,  // NOLINT
                            &len),
        "Failed to finalize decryption.",
        OpenSslError::FAILED_DECRYPT_FINALIZE);

    result.resize(ciphertext.size() - 16);
    return result;
  }

}  // namespace libp2p::crypto::chachapoly


/**
 */

#include <gsl/gsl_util>
#include <libp2p/crypto/chachapoly/chachapoly_impl.hpp>
#include <libp2p/crypto/common_functions.hpp>
#include <libp2p/crypto/error.hpp>

namespace libp2p::crypto::chachapoly {

#define IF1(expr, err, result) \
  if (1 != (expr)) {           \
    log_->error((err));        \
    return (result);           \
  }

  ChaCha20Poly1305Impl::ChaCha20Poly1305Impl(Key key)
      : key_{key},
        cipher_{EVP_chacha20_poly1305()},
        block_size_{EVP_CIPHER_block_size(cipher_)}

  outcome::result<ByteArray> ChaCha20Poly1305Impl::encrypt(
      const Nonce &nonce, gsl::span<const uint8_t> plaintext,
      gsl::span<const uint8_t> aad) {
    const auto init_failure = OpenSslError::FAILED_INITIALIZE_OPERATION;
    std::vector<uint8_t> result;
    // just reserving the space, possibly without actual memory allocation:
    // ciphertext length equals to plaintext length
    // one block size for EncryptFinal_ex (usually not required)
    result.reserve(plaintext.size() + block_size_ + 16);
    auto *ctx = EVP_CIPHER_CTX_new();
    if (nullptr == ctx) {
      return OpenSslError::FAILED_INITIALIZE_CONTEXT;
    }
    auto free_ctx = gsl::finally([ctx] { EVP_CIPHER_CTX_free(ctx); });

    IF1(EVP_EncryptInit_ex(ctx, cipher_, nullptr, nullptr, nullptr),
        "Failed to initialize EVP encryption.", init_failure)

    IF1(EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, 12, nullptr),
        "Cannot set AEAD initialization vector length.", init_failure)

    IF1(EVP_EncryptInit_ex(ctx, nullptr, nullptr, key_.data(), nonce.data()),
        "Cannot finalize init of encryption engine.", init_failure)

    int len{0};
    IF1(EVP_EncryptUpdate(ctx, nullptr, &len, nullptr, plaintext.size()),
        "Failed to calculate cipher text length.",
        OpenSslError::FAILED_ENCRYPT_UPDATE)

    // does actual memory allocation
    result.resize(len + block_size_ + 16, 0u);
    if (not aad.empty()) {
      IF1(EVP_EncryptUpdate(ctx, nullptr, &len, aad.data(), aad.size()),
          "Failed to apply additional authentication data during encryption.",
          OpenSslError::FAILED_ENCRYPT_UPDATE)
    }

    IF1(EVP_EncryptUpdate(ctx, result.data(), &len, plaintext.data(),
                          plaintext.size()),
        "Plaintext encryption failed.", OpenSslError::FAILED_ENCRYPT_UPDATE)
    int ciphertext_len = len;  // without tag size
    IF1(EVP_EncryptFinal_ex(ctx, result.data() + len, &len),  // NOLINT
        "Unable to finalize encryption.", OpenSslError::FAILED_ENCRYPT_FINALIZE)

    ciphertext_len += len;
    IF1(EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, EVP_CTRL_AEAD_GET_TAG,
                            result.data() + ciphertext_len),  // NOLINT
        "Failed to write tag.", OpenSslError::FAILED_ENCRYPT_FINALIZE)

    // remove the last block_size bytes if those were not used
    result.resize(ciphertext_len + 16);
    return result;
  }

  outcome::result<ByteArray> ChaCha20Poly1305Impl::decrypt(
      const Nonce &nonce, gsl::span<const uint8_t> ciphertext,
      gsl::span<const uint8_t> aad) {
    ByteArray result;
    // plain text should take less bytes than cipher text,
    // at least it would not contain tag-length bytes (16).
    // single block size for the case when len(in) == len(out)
    result.resize(ciphertext.size() + block_size_, 0u);
    auto *ctx = EVP_CIPHER_CTX_new();
    auto free_ctx = gsl::finally([ctx] { EVP_CIPHER_CTX_free(ctx); });

    IF1(EVP_DecryptInit_ex(ctx, cipher_, nullptr, nullptr, nullptr),
        "Failed to initialize decryption engine.",
        OpenSslError::FAILED_INITIALIZE_OPERATION)

    IF1(EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, 12, nullptr),
        "Cannot set AEAD initialization vector length.",
        OpenSslError::FAILED_INITIALIZE_OPERATION)

    IF1(EVP_CIPHER_CTX_ctrl(
            ctx, EVP_CTRL_AEAD_SET_TAG, EVP_CTRL_AEAD_GET_TAG,
            (uint8_t *)ciphertext.data() + ciphertext.size() - 16),  // NOLINT
        "Failed to specify buffer for further tag reading.",
        OpenSslError::FAILED_DECRYPT_UPDATE)

    IF1(EVP_DecryptInit_ex(ctx, nullptr, nullptr, key_.data(), nonce.data()),
        "Cannot finalize init of decryption engine.",
        OpenSslError::FAILED_INITIALIZE_OPERATION)

    int len{0};
    if (not aad.empty()) {
      IF1(EVP_DecryptUpdate(ctx, nullptr, &len, aad.data(), aad.size()),
          "Failed to apply additional authentication data during decryption.",
          OpenSslError::FAILED_DECRYPT_UPDATE)
    }

    IF1(EVP_DecryptUpdate(ctx, result.data(), &len, ciphertext.data(),
                          ciphertext.size() - 16),
        "Ciphertext decryption failed.", OpenSslError::FAILED_DECRYPT_UPDATE)

    IF1(EVP_DecryptFinal_ex(ctx,
                            result.data() + len,  // NOLINT
                            &len),
        "Failed to finalize decryption.",
        OpenSslError::FAILED_DECRYPT_FINALIZE);

    result.resize(ciphertext.size() - 16);
    return result;
  }

}  // namespace libp2p::crypto::chachapoly


{
    ByteArray result;
    // plain text should take less bytes than cipher text,
    // at least it would not contain tag-length bytes (16).
    // single block size for the case when len(in) == len(out)
    result.resize(ciphertext.size() + block_size_, 0u);
    auto *ctx = EVP_CIPHER_CTX_new();

/**
 */

#include <gsl/gsl_util>
#include <libp2p/crypto/chachapoly/chachapoly_impl.hpp>
#include <libp2p/crypto/common_functions.hpp>
#include <libp2p/crypto/error.hpp>

namespace libp2p::crypto::chachapoly {

#define IF1(expr, err, result) \
  if (1 != (expr)) {           \
    log_->error((err));        \
    return (result);           \
  }

  ChaCha20Poly1305Impl::ChaCha20Poly1305Impl(Key key)
      : key_{key},
        cipher_{EVP_chacha20_poly1305()},
        block_size_{EVP_CIPHER_block_size(cipher_)} {}

  outcome::result<ByteArray> ChaCha20Poly1305Impl::encrypt(
      const Nonce &nonce, gsl::span<const uint8_t> plaintext,
      gsl::span<const uint8_t> aad) {
    const auto init_failure = OpenSslError::FAILED_INITIALIZE_OPERATION;
    std::vector<uint8_t> result;
    // just reserving the space, possibly without actual memory allocation:
    // ciphertext length equals to plaintext length
    // one block size for EncryptFinal_ex (usually not required)
    result.reserve(plaintext.size() + block_size_ + 16);
    auto *ctx = EVP_CIPHER_CTX_new();
    if (nullptr == ctx) {
      return OpenSslError::FAILED_INITIALIZE_CONTEXT;
    }
    auto free_ctx = gsl::finally([ctx] { EVP_CIPHER_CTX_free(ctx); });

    IF1(EVP_EncryptInit_ex(ctx, cipher_, nullptr, nullptr, nullptr),
        "Failed to initialize EVP encryption.", init_failure)

    IF1(EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, 12, nullptr),
        "Cannot set AEAD initialization vector length.", init_failure)

    IF1(EVP_EncryptInit_ex(ctx, nullptr, nullptr, key_.data(), nonce.data()),
        "Cannot finalize init of encryption engine.", init_failure)

    int len{0};
    IF1(EVP_EncryptUpdate(ctx, nullptr, &len, nullptr, plaintext.size()),
        "Failed to calculate cipher text length.",
        OpenSslError::FAILED_ENCRYPT_UPDATE)

    // does actual memory allocation
    result.resize(len + block_size_ + 16, 0u);
    if (not aad.empty()) {
      IF1(EVP_EncryptUpdate(ctx, nullptr, &len, aad.data(), aad.size()),
          "Failed to apply additional authentication data during encryption.",
          OpenSslError::FAILED_ENCRYPT_UPDATE)
    }

    IF1(EVP_EncryptUpdate(ctx, result.data(), &len, plaintext.data(),
                          plaintext.size()),
        "Plaintext encryption failed.", OpenSslError::FAILED_ENCRYPT_UPDATE)
    int ciphertext_len = len;  // without tag size
    IF1(EVP_EncryptFinal_ex(ctx, result.data() + len, &len),  // NOLINT
        "Unable to finalize encryption.", OpenSslError::FAILED_ENCRYPT_FINALIZE)

    ciphertext_len += len;
    IF1(EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, EVP_CTRL_AEAD_GET_TAG,
                            result.data() + ciphertext_len),  // NOLINT
        "Failed to write tag.", OpenSslError::FAILED_ENCRYPT_FINALIZE)

    // remove the last block_size bytes if those were not used
    result.resize(ciphertext_len + 16);
    return result;
  }

  outcome::result<ByteArray> ChaCha20Poly1305Impl::decrypt(
      const Nonce &nonce, gsl::span<const uint8_t> ciphertext,
      gsl::span<const uint8_t> aad)

    auto free_ctx = gsl::finally([ctx] { EVP_CIPHER_CTX_free(ctx); });

    IF1(EVP_DecryptInit_ex(ctx, cipher_, nullptr, nullptr, nullptr),
        "Failed to initialize decryption engine.",
        OpenSslError::FAILED_INITIALIZE_OPERATION)

    IF1(EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, 12, nullptr),
        "Cannot set AEAD initialization vector length.",
        OpenSslError::FAILED_INITIALIZE_OPERATION)

    IF1(EVP_CIPHER_CTX_ctrl(
            ctx, EVP_CTRL_AEAD_SET_TAG, EVP_CTRL_AEAD_GET_TAG,
            (uint8_t *)ciphertext.data() + ciphertext.size() - 16),  // NOLINT
        "Failed to specify buffer for further tag reading.",
        OpenSslError::FAILED_DECRYPT_UPDATE)

    IF1(EVP_DecryptInit_ex(ctx, nullptr, nullptr, key_.data(), nonce.data()),
        "Cannot finalize init of decryption engine.",
        OpenSslError::FAILED_INITIALIZE_OPERATION)

    int len{0};
    if (not aad.empty()) {
      IF1(EVP_DecryptUpdate(ctx, nullptr, &len, aad.data(), aad.size()),
          "Failed to apply additional authentication data during decryption.",
          OpenSslError::FAILED_DECRYPT_UPDATE)
    }

    IF1(EVP_DecryptUpdate(ctx, result.data(), &len, ciphertext.data(),
                          ciphertext.size() - 16),
        "Ciphertext decryption failed.", OpenSslError::FAILED_DECRYPT_UPDATE)

    IF1(EVP_DecryptFinal_ex(ctx,
                            result.data() + len,  // NOLINT
                            &len),
        "Failed to finalize decryption.",
        OpenSslError::FAILED_DECRYPT_FINALIZE);

    result.resize(ciphertext.size() - 16);
    return result;
  }

}  // namespace libp2p::crypto::chachapoly


auto free_ctx = gsl::finally([ctx] { EVP_CIPHER_CTX_free(ctx); });

    IF1(EVP_DecryptInit_ex(ctx, cipher_, nullptr, nullptr, nullptr),
        "Failed to initialize decryption engine.",
        OpenSslError::FAILED_INITIALIZE_OPERATION)

    IF1(EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, 12, nullptr),
        "Cannot set AEAD initialization vector length.",
        OpenSslError::FAILED_INITIALIZE_OPERATION)

    IF1(EVP_CIPHER_CTX_ctrl(
            ctx, EVP_CTRL_AEAD_SET_TAG, EVP_CTRL_AEAD_GET_TAG,
            (uint8_t *)ciphertext.data() + ciphertext.size() - 16),  // NOLINT
        "Failed to specify buffer for further tag reading.",
        OpenSslError::FAILED_DECRYPT_UPDATE)

    IF1(EVP_DecryptInit_ex(ctx, nullptr, nullptr, key_.data(), nonce.data()),
        "Cannot finalize init of decryption engine.",
        OpenSslError::FAILED_INITIALIZE_OPERATION)

    int len{0};

/**
 */

#include <gsl/gsl_util>
#include <libp2p/crypto/chachapoly/chachapoly_impl.hpp>
#include <libp2p/crypto/common_functions.hpp>
#include <libp2p/crypto/error.hpp>

namespace libp2p::crypto::chachapoly {

#define IF1(expr, err, result) \
  if (1 != (expr)) {           \
    log_->error((err));        \
    return (result);           \
  }

  ChaCha20Poly1305Impl::ChaCha20Poly1305Impl(Key key)
      : key_{key},
        cipher_{EVP_chacha20_poly1305()},
        block_size_{EVP_CIPHER_block_size(cipher_)} {}

  outcome::result<ByteArray> ChaCha20Poly1305Impl::encrypt(
      const Nonce &nonce, gsl::span<const uint8_t> plaintext,
      gsl::span<const uint8_t> aad) {
    const auto init_failure = OpenSslError::FAILED_INITIALIZE_OPERATION;
    std::vector<uint8_t> result;
    // just reserving the space, possibly without actual memory allocation:
    // ciphertext length equals to plaintext length
    // one block size for EncryptFinal_ex (usually not required)
    result.reserve(plaintext.size() + block_size_ + 16);
    auto *ctx = EVP_CIPHER_CTX_new();
    if (nullptr == ctx) {
      return OpenSslError::FAILED_INITIALIZE_CONTEXT;
    }
    auto free_ctx = gsl::finally([ctx] { EVP_CIPHER_CTX_free(ctx); });

    IF1(EVP_EncryptInit_ex(ctx, cipher_, nullptr, nullptr, nullptr),
        "Failed to initialize EVP encryption.", init_failure)

    IF1(EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, 12, nullptr),
        "Cannot set AEAD initialization vector length.", init_failure)

    IF1(EVP_EncryptInit_ex(ctx, nullptr, nullptr, key_.data(), nonce.data()),
        "Cannot finalize init of encryption engine.", init_failure)

    int len{0};
    IF1(EVP_EncryptUpdate(ctx, nullptr, &len, nullptr, plaintext.size()),
        "Failed to calculate cipher text length.",
        OpenSslError::FAILED_ENCRYPT_UPDATE)

    // does actual memory allocation
    result.resize(len + block_size_ + 16, 0u);
    if (not aad.empty()) {
      IF1(EVP_EncryptUpdate(ctx, nullptr, &len, aad.data(), aad.size()),
          "Failed to apply additional authentication data during encryption.",
          OpenSslError::FAILED_ENCRYPT_UPDATE)
    }

    IF1(EVP_EncryptUpdate(ctx, result.data(), &len, plaintext.data(),
                          plaintext.size()),
        "Plaintext encryption failed.", OpenSslError::FAILED_ENCRYPT_UPDATE)
    int ciphertext_len = len;  // without tag size
    IF1(EVP_EncryptFinal_ex(ctx, result.data() + len, &len),  // NOLINT
        "Unable to finalize encryption.", OpenSslError::FAILED_ENCRYPT_FINALIZE)

    ciphertext_len += len;
    IF1(EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, EVP_CTRL_AEAD_GET_TAG,
                            result.data() + ciphertext_len),  // NOLINT
        "Failed to write tag.", OpenSslError::FAILED_ENCRYPT_FINALIZE)

    // remove the last block_size bytes if those were not used
    result.resize(ciphertext_len + 16);
    return result;
  }

  outcome::result<ByteArray> ChaCha20Poly1305Impl::decrypt(
      const Nonce &nonce, gsl::span<const uint8_t> ciphertext,
      gsl::span<const uint8_t> aad) {
    ByteArray result;
    // plain text should take less bytes than cipher text,
    // at least it would not contain tag-length bytes (16).
    // single block size for the case when len(in) == len(out)
    result.resize(ciphertext.size() + block_size_, 0u);
    auto *ctx = EVP_CIPHER_CTX_new();

    if (not aad.empty()) {
      IF1(EVP_DecryptUpdate(ctx, nullptr, &len, aad.data(), aad.size()),
          "Failed to apply additional authentication data during decryption.",
          OpenSslError::FAILED_DECRYPT_UPDATE)
    }

    IF1(EVP_DecryptUpdate(ctx, result.data(), &len, ciphertext.data(),
                          ciphertext.size() - 16),
        "Ciphertext decryption failed.", OpenSslError::FAILED_DECRYPT_UPDATE)

    IF1(EVP_DecryptFinal_ex(ctx,
                            result.data() + len,  // NOLINT
                            &len),
        "Failed to finalize decryption.",
        OpenSslError::FAILED_DECRYPT_FINALIZE);

    result.resize(ciphertext.size() - 16);
    return result;
  }

}  // namespace libp2p::crypto::chachapoly


if (not aad.empty()) {
      IF1(EVP_DecryptUpdate(ctx, nullptr, &len, aad.data(), aad.size()),
          "Failed to apply additional authentication data during decryption.",
          OpenSslError::FAILED_DECRYPT_UPDATE)
    }

    IF1(EVP_DecryptUpdate(ctx, result.data(), &len, ciphertext.data(),
                          ciphertext.size() - 16),
        "Ciphertext decryption failed.", OpenSslError::FAILED_DECRYPT_UPDATE)

    IF1(EVP_DecryptFinal_ex(ctx,
                            result.data() + len,  // NOLINT
                            &len),
        "Failed to finalize decryption.",
        OpenSslError::FAILED_DECRYPT_FINALIZE);

    result.resize(ciphertext.size() - 16);
    return result;
  }

/**
 */

#include <gsl/gsl_util>
#include <libp2p/crypto/chachapoly/chachapoly_impl.hpp>
#include <libp2p/crypto/common_functions.hpp>
#include <libp2p/crypto/error.hpp>

namespace libp2p::crypto::chachapoly {

#define IF1(expr, err, result) \
  if (1 != (expr)) {           \
    log_->error((err));        \
    return (result);           \
  }

  ChaCha20Poly1305Impl::ChaCha20Poly1305Impl(Key key)
      : key_{key},
        cipher_{EVP_chacha20_poly1305()},
        block_size_{EVP_CIPHER_block_size(cipher_)} {}

  outcome::result<ByteArray> ChaCha20Poly1305Impl::encrypt(
      const Nonce &nonce, gsl::span<const uint8_t> plaintext,
      gsl::span<const uint8_t> aad) {
    const auto init_failure = OpenSslError::FAILED_INITIALIZE_OPERATION;
    std::vector<uint8_t> result;
    // just reserving the space, possibly without actual memory allocation:
    // ciphertext length equals to plaintext length
    // one block size for EncryptFinal_ex (usually not required)
    result.reserve(plaintext.size() + block_size_ + 16);
    auto *ctx = EVP_CIPHER_CTX_new();
    if (nullptr == ctx) {
      return OpenSslError::FAILED_INITIALIZE_CONTEXT;
    }
    auto free_ctx = gsl::finally([ctx] { EVP_CIPHER_CTX_free(ctx); });

    IF1(EVP_EncryptInit_ex(ctx, cipher_, nullptr, nullptr, nullptr),
        "Failed to initialize EVP encryption.", init_failure)

    IF1(EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, 12, nullptr),
        "Cannot set AEAD initialization vector length.", init_failure)

    IF1(EVP_EncryptInit_ex(ctx, nullptr, nullptr, key_.data(), nonce.data()),
        "Cannot finalize init of encryption engine.", init_failure)

    int len{0};
    IF1(EVP_EncryptUpdate(ctx, nullptr, &len, nullptr, plaintext.size()),
        "Failed to calculate cipher text length.",
        OpenSslError::FAILED_ENCRYPT_UPDATE)

    // does actual memory allocation
    result.resize(len + block_size_ + 16, 0u);
    if (not aad.empty()) {
      IF1(EVP_EncryptUpdate(ctx, nullptr, &len, aad.data(), aad.size()),
          "Failed to apply additional authentication data during encryption.",
          OpenSslError::FAILED_ENCRYPT_UPDATE)
    }

    IF1(EVP_EncryptUpdate(ctx, result.data(), &len, plaintext.data(),
                          plaintext.size()),
        "Plaintext encryption failed.", OpenSslError::FAILED_ENCRYPT_UPDATE)
    int ciphertext_len = len;  // without tag size
    IF1(EVP_EncryptFinal_ex(ctx, result.data() + len, &len),  // NOLINT
        "Unable to finalize encryption.", OpenSslError::FAILED_ENCRYPT_FINALIZE)

    ciphertext_len += len;
    IF1(EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, EVP_CTRL_AEAD_GET_TAG,
                            result.data() + ciphertext_len),  // NOLINT
        "Failed to write tag.", OpenSslError::FAILED_ENCRYPT_FINALIZE)

    // remove the last block_size bytes if those were not used
    result.resize(ciphertext_len + 16);
    return result;
  }

  outcome::result<ByteArray> ChaCha20Poly1305Impl::decrypt(
      const Nonce &nonce, gsl::span<const uint8_t> ciphertext,
      gsl::span<const uint8_t> aad) {
    ByteArray result;
    // plain text should take less bytes than cipher text,
    // at least it would not contain tag-length bytes (16).
    // single block size for the case when len(in) == len(out)
    result.resize(ciphertext.size() + block_size_, 0u);
    auto *ctx = EVP_CIPHER_CTX_new();
    auto free_ctx = gsl::finally([ctx] { EVP_CIPHER_CTX_free(ctx); });

    IF1(EVP_DecryptInit_ex(ctx, cipher_, nullptr, nullptr, nullptr),
        "Failed to initialize decryption engine.",
        OpenSslError::FAILED_INITIALIZE_OPERATION)

    IF1(EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, 12, nullptr),
        "Cannot set AEAD initialization vector length.",
        OpenSslError::FAILED_INITIALIZE_OPERATION)

    IF1(EVP_CIPHER_CTX_ctrl(
            ctx, EVP_CTRL_AEAD_SET_TAG, EVP_CTRL_AEAD_GET_TAG,
            (uint8_t *)ciphertext.data() + ciphertext.size() - 16),  // NOLINT
        "Failed to specify buffer for further tag reading.",
        OpenSslError::FAILED_DECRYPT_UPDATE)

    IF1(EVP_DecryptInit_ex(ctx, nullptr, nullptr, key_.data(), nonce.data()),
        "Cannot finalize init of decryption engine.",
        OpenSslError::FAILED_INITIALIZE_OPERATION)

    int len{0};

}  // namespace libp2p::crypto::chachapoly


/**
 */

#include <gsl/gsl_util>
#include <libp2p/crypto/chachapoly/chachapoly_impl.hpp>
#include <libp2p/crypto/common_functions.hpp>
#include <libp2p/crypto/error.hpp>

namespace libp2p::crypto::chachapoly {

#define IF1(expr, err, result) \
  if (1 != (expr)) {           \
    log_->error((err));        \
    return (result);           \
  }

  ChaCha20Poly1305Impl::ChaCha20Poly1305Impl(Key key)
      : key_{key},
        cipher_{EVP_chacha20_poly1305()},
        block_size_{EVP_CIPHER_block_size(cipher_)} {}

  outcome::result<ByteArray> ChaCha20Poly1305Impl::encrypt(
      const Nonce &nonce, gsl::span<const uint8_t> plaintext,
      gsl::span<const uint8_t> aad) {
    const auto init_failure = OpenSslError::FAILED_INITIALIZE_OPERATION;
    std::vector<uint8_t> result;
    // just reserving the space, possibly without actual memory allocation:
    // ciphertext length equals to plaintext length
    // one block size for EncryptFinal_ex (usually not required)
    result.reserve(plaintext.size() + block_size_ + 16);
    auto *ctx = EVP_CIPHER_CTX_new();
    if (nullptr == ctx) {
      return OpenSslError::FAILED_INITIALIZE_CONTEXT;
    }
    auto free_ctx = gsl::finally([ctx] { EVP_CIPHER_CTX_free(ctx); });

    IF1(EVP_EncryptInit_ex(ctx, cipher_, nullptr, nullptr, nullptr),
        "Failed to initialize EVP encryption.", init_failure)

    IF1(EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, 12, nullptr),
        "Cannot set AEAD initialization vector length.", init_failure)

    IF1(EVP_EncryptInit_ex(ctx, nullptr, nullptr, key_.data(), nonce.data()),
        "Cannot finalize init of encryption engine.", init_failure)

    int len{0};
    IF1(EVP_EncryptUpdate(ctx, nullptr, &len, nullptr, plaintext.size()),
        "Failed to calculate cipher text length.",
        OpenSslError::FAILED_ENCRYPT_UPDATE)

    // does actual memory allocation
    result.resize(len + block_size_ + 16, 0u);
    if (not aad.empty()) {
      IF1(EVP_EncryptUpdate(ctx, nullptr, &len, aad.data(), aad.size()),
          "Failed to apply additional authentication data during encryption.",
          OpenSslError::FAILED_ENCRYPT_UPDATE)
    }

    IF1(EVP_EncryptUpdate(ctx, result.data(), &len, plaintext.data(),
                          plaintext.size()),
        "Plaintext encryption failed.", OpenSslError::FAILED_ENCRYPT_UPDATE)
    int ciphertext_len = len;  // without tag size
    IF1(EVP_EncryptFinal_ex(ctx, result.data() + len, &len),  // NOLINT
        "Unable to finalize encryption.", OpenSslError::FAILED_ENCRYPT_FINALIZE)

    ciphertext_len += len;
    IF1(EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, EVP_CTRL_AEAD_GET_TAG,
                            result.data() + ciphertext_len),  // NOLINT
        "Failed to write tag.", OpenSslError::FAILED_ENCRYPT_FINALIZE)

    // remove the last block_size bytes if those were not used
    result.resize(ciphertext_len + 16);
    return result;
  }

  outcome::result<ByteArray> ChaCha20Poly1305Impl::decrypt(
      const Nonce &nonce, gsl::span<const uint8_t> ciphertext,
      gsl::span<const uint8_t> aad) {
    ByteArray result;
    // plain text should take less bytes than cipher text,
    // at least it would not contain tag-length bytes (16).
    // single block size for the case when len(in) == len(out)
    result.resize(ciphertext.size() + block_

    auto *ctx = EVP_CIPHER_CTX_new();
    auto free_ctx = gsl::finally([ctx] { EVP_CIPHER_CTX_free(ctx); });

    IF1(EVP_DecryptInit_ex(ctx, cipher_, nullptr, nullptr, nullptr),
        "Failed to initialize decryption engine.",
        OpenSslError::FAILED_INITIALIZE_OPERATION)

    IF1(EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, 12, nullptr),
        "Cannot set AEAD initialization vector length.",
        OpenSslError::FAILED_INITIALIZE_OPERATION)

    IF1(EVP_CIPHER_CTX_ctrl(
            ctx, EVP_CTRL_AEAD_SET_TAG, EVP_CTRL_AEAD_GET_TAG,
            (uint8_t *)ciphertext.data() + ciphertext.size() - 16),  // NOLINT
        "Failed to specify buffer for further tag reading.",
        OpenSslError::FAILED_DECRYPT_UPDATE)

    IF1(EVP_DecryptInit_ex(ctx, nullptr, nullptr, key_.data(), nonce.data()),
        "Cannot finalize init of decryption engine.",
        OpenSslError::FAILED_INITIALIZE_OPERATION)

    int len{0};
    if (not aad.empty()) {
      IF1(EVP_DecryptUpdate(ctx, nullptr, &len, aad.data(), aad.size()),
          "Failed to apply additional authentication data during decryption.",
          OpenSslError::FAILED_DECRYPT_UPDATE)
    }

    IF1(EVP_DecryptUpdate(ctx, result.data(), &len, ciphertext.data(),
                          ciphertext.size() - 16),
        "Ciphertext decryption failed.", OpenSslError::FAILED_DECRYPT_UPDATE)

    IF1(EVP_DecryptFinal_ex(ctx,
                            result.data() + len,  // NOLINT
                            &len),
        "Failed to finalize decryption.",
        OpenSslError::FAILED_DECRYPT_FINALIZE);

    result.resize(ciphertext.size() - 16);
    return result;
  }

}  // namespace libp2p::crypto::chachapoly