/*
 *  SPDX-FileCopyrightText: 2005 Boudewijn Rempt <boud@valdyas.org>
 *  SPDX-FileCopyrightText: 2007 Thomas Zander <zander@kde.org>
 *  SPDX-FileCopyrightText: 2007 Cyrille Berger <cberger@cberger.net>
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 */

#include "KoColor.h"

#include <QColor>

#include <QDomDocument>

#include "DebugPigment.h"

#include "KoChannelInfo.h"
#include "KoColorModelStandardIds.h"
#include "KoColorProfile.h"
#include "KoColorSpace.h"
#include "KoColorSpaceRegistry.h"

class Q_DECL_HIDDEN KoColor::Private
{
public:
    Private()
        : data(nullptr)
        , colorSpace(nullptr)
    {
    }

    ~Private()
    {
        delete[] data;
    }

    quint8 *data;
    const KoColorSpace *colorSpace;
};

KoColor::KoColor()
    : d(new Private())
{
    d->colorSpace = KoColorSpaceRegistry::instance()->rgb16(nullptr);
    d->data = new quint8[d->colorSpace->pixelSize()];
    d->colorSpace->fromQColor(Qt::black, d->data);
    d->colorSpace->setOpacity(d->data, OPACITY_OPAQUE_U8, 1);
}

KoColor::KoColor(const KoColorSpace *colorSpace)
    : d(new Private())
{
    Q_ASSERT(colorSpace);
    d->colorSpace = KoColorSpaceRegistry::instance()->permanentColorspace(colorSpace);
    d->data = new quint8[d->colorSpace->pixelSize()];
    memset(d->data, 0, d->colorSpace->pixelSize());
}

KoColor::~KoColor()
{
    delete d;
}

KoColor::KoColor(const QColor &color, const KoColorSpace *colorSpace)
    : d(new Private())
{
    Q_ASSERT(color.isValid());
    Q_ASSERT(colorSpace);
    d->colorSpace = KoColorSpaceRegistry::instance()->permanentColorspace(colorSpace);

    d->data = new quint8[colorSpace->pixelSize()];
    memset(d->data, 0, d->colorSpace->pixelSize());

    d->colorSpace->fromQColor(color, d->data);
}

KoColor::KoColor(const quint8 *data, const KoColorSpace *colorSpace)
    : d(new Private())
{
    Q_ASSERT(colorSpace);
    Q_ASSERT(data);
    d->colorSpace = KoColorSpaceRegistry::instance()->permanentColorspace(colorSpace);
    d->data = new quint8[colorSpace->pixelSize()];
    memset(d->data, 0, d->colorSpace->pixelSize());
    memmove(d->data, data, colorSpace->pixelSize());
}

KoColor::KoColor(const KoColor &src, const KoColorSpace *colorSpace)
    : d(new Private())
{
    Q_ASSERT(colorSpace);
    d->colorSpace = KoColorSpaceRegistry::instance()->permanentColorspace(colorSpace);
    d->data = new quint8[colorSpace->pixelSize()];
    memset(d->data, 0, d->colorSpace->pixelSize());

    src.colorSpace()->convertPixelsTo(src.d->data,
                                      d->data,
                                      colorSpace,
                                      1,
                                      KoColorConversionTransformation::internalRenderingIntent(),
                                      KoColorConversionTransformation::internalConversionFlags());
}

KoColor::KoColor(const KoColor &rhs)
    : d(new Private())
{
    d->colorSpace = rhs.colorSpace();
    Q_ASSERT(*d->colorSpace == *KoColorSpaceRegistry::instance()->permanentColorspace(d->colorSpace));
    if (d->colorSpace && rhs.d->data) {
        d->data = new quint8[d->colorSpace->pixelSize()];
        memcpy(d->data, rhs.d->data, d->colorSpace->pixelSize());
    }
}

KoColor &KoColor::operator=(const KoColor &rhs)
{
    if (this == &rhs)
        return *this;

    delete[] d->data;
    d->data = nullptr;
    d->colorSpace = rhs.colorSpace();

    if (rhs.d->colorSpace && rhs.d->data) {
        Q_ASSERT(d->colorSpace
                 == KoColorSpaceRegistry::instance()->permanentColorspace(
                     d->colorSpace)); // here we want to do a check on pointer, since d->colorSpace is supposed to already be a permanent one
        d->data = new quint8[d->colorSpace->pixelSize()];
        memcpy(d->data, rhs.d->data, d->colorSpace->pixelSize());
    }
    return *this;
}

bool KoColor::operator==(const KoColor &other) const
{
    if (!(*colorSpace() == *other.colorSpace()))
        return false;
    return memcmp(d->data, other.d->data, d->colorSpace->pixelSize()) == 0;
}

void KoColor::convertTo(const KoColorSpace *cs,
                        KoColorConversionTransformation::Intent renderingIntent,
                        KoColorConversionTransformation::ConversionFlags conversionFlags)
{
    // dbgPigment <<"Our colormodel:" << d->colorSpace->id().name()
    //       << ", new colormodel: " << cs->id().name() << "\n";

    if (*d->colorSpace == *cs)
        return;

    quint8 *data = new quint8[cs->pixelSize()];
    memset(data, 0, cs->pixelSize());

    d->colorSpace->convertPixelsTo(d->data, data, cs, 1, renderingIntent, conversionFlags);

    delete[] d->data;
    d->data = data;
    d->colorSpace = KoColorSpaceRegistry::instance()->permanentColorspace(cs);
}

void KoColor::convertTo(const KoColorSpace *cs)
{
    convertTo(cs, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags());
}

void KoColor::setColor(const quint8 *data, const KoColorSpace *colorSpace)
{
    Q_ASSERT(data);
    Q_ASSERT(colorSpace);
    if (d->colorSpace->pixelSize() != colorSpace->pixelSize()) {
        delete[] d->data;
        d->data = new quint8[colorSpace->pixelSize()];
    }
    memcpy(d->data, data, colorSpace->pixelSize());
    d->colorSpace = KoColorSpaceRegistry::instance()->permanentColorspace(colorSpace);
}

// To save the user the trouble of doing color->colorSpace()->toQColor(color->data(), &c, &a, profile
void KoColor::toQColor(QColor *c) const
{
    Q_ASSERT(c);
    if (d->colorSpace && d->data) {
        d->colorSpace->toQColor(d->data, c);
    }
}

QColor KoColor::toQColor() const
{
    QColor c;
    toQColor(&c);
    return c;
}

void KoColor::fromQColor(const QColor &c) const
{
    if (d->colorSpace && d->data) {
        d->colorSpace->fromQColor(c, d->data);
    }
}

#ifndef NDEBUG
void KoColor::dump() const
{
    dbgPigment << "KoColor (" << this << ")," << d->colorSpace->id() << "";
    QList<KoChannelInfo *> channels = d->colorSpace->channels();

    QList<KoChannelInfo *>::const_iterator begin = channels.constBegin();
    QList<KoChannelInfo *>::const_iterator end = channels.constEnd();

    for (QList<KoChannelInfo *>::const_iterator it = begin; it != end; ++it) {
        KoChannelInfo *ch = (*it);
        // XXX: setNum always takes a byte.
        if (ch->size() == sizeof(quint8)) {
            // Byte
            dbgPigment << "Channel (byte):" << ch->name() << ":" << QString().setNum(d->data[ch->pos()]) << "";
        } else if (ch->size() == sizeof(quint16)) {
            // Short (may also by an nvidia half)
            dbgPigment << "Channel (short):" << ch->name() << ":" << QString().setNum(*((const quint16 *)(d->data + ch->pos()))) << "";
        } else if (ch->size() == sizeof(quint32)) {
            // Integer (may also be float... Find out how to distinguish these!)
            dbgPigment << "Channel (int):" << ch->name() << ":" << QString().setNum(*((const quint32 *)(d->data + ch->pos()))) << "";
        }
    }
}
#endif

void KoColor::fromKoColor(const KoColor &src)
{
    src.colorSpace()->convertPixelsTo(src.d->data,
                                      d->data,
                                      colorSpace(),
                                      1,
                                      KoColorConversionTransformation::internalRenderingIntent(),
                                      KoColorConversionTransformation::internalConversionFlags());
}

const KoColorProfile *KoColor::profile() const
{
    return d->colorSpace->profile();
}

quint8 *KoColor::data()
{
    return d->data;
}

const quint8 *KoColor::data() const
{
    return d->data;
}

const KoColorSpace *KoColor::colorSpace() const
{
    return d->colorSpace;
}

void KoColor::toXML(QDomDocument &doc, QDomElement &colorElt) const
{
    d->colorSpace->colorToXML(d->data, doc, colorElt);
}

void KoColor::setOpacity(quint8 alpha)
{
    d->colorSpace->setOpacity(d->data, alpha, 1);
}
void KoColor::setOpacity(qreal alpha)
{
    d->colorSpace->setOpacity(d->data, alpha, 1);
}
quint8 KoColor::opacityU8() const
{
    return d->colorSpace->opacityU8(d->data);
}
qreal KoColor::opacityF() const
{
    return d->colorSpace->opacityF(d->data);
}

KoColor KoColor::fromXML(const QDomElement &elt, const QString &bitDepthId, const QHash<QString, QString> &aliases)
{
    QString modelId;
    if (elt.tagName() == "CMYK") {
        modelId = CMYKAColorModelID.id();
    } else if (elt.tagName() == "RGB") {
        modelId = RGBAColorModelID.id();
    } else if (elt.tagName() == "sRGB") {
        modelId = RGBAColorModelID.id();
    } else if (elt.tagName() == "Lab") {
        modelId = LABAColorModelID.id();
    } else if (elt.tagName() == "XYZ") {
        modelId = XYZAColorModelID.id();
    } else if (elt.tagName() == "Gray") {
        modelId = GrayAColorModelID.id();
    } else if (elt.tagName() == "YCbCr") {
        modelId = YCbCrAColorModelID.id();
    }
    QString profileName;
    if (elt.tagName() != "sRGB") {
        profileName = elt.attribute("space", "");
        const QHash<QString, QString>::ConstIterator it = aliases.find(profileName);
        if (it != aliases.end()) {
            profileName = it.value();
        }
        if (!KoColorSpaceRegistry::instance()->profileByName(profileName)) {
            profileName.clear();
        }
    }
    const KoColorSpace *cs = KoColorSpaceRegistry::instance()->colorSpace(modelId, bitDepthId, profileName);
    if (cs == nullptr) {
        QList<KoID> list = KoColorSpaceRegistry::instance()->colorDepthList(modelId, KoColorSpaceRegistry::AllColorSpaces);
        if (!list.empty()) {
            cs = KoColorSpaceRegistry::instance()->colorSpace(modelId, list[0].id(), profileName);
        }
    }
    if (cs) {
        KoColor c(cs);
        cs->colorFromXML(c.data(), elt);
        return c;
    } else {
        return KoColor();
    }
}

QString KoColor::toQString(const KoColor &color)
{
    QStringList ls;
    foreach (KoChannelInfo *channel, KoChannelInfo::displayOrderSorted(color.colorSpace()->channels())) {
        int realIndex = KoChannelInfo::displayPositionToChannelIndex(channel->displayPosition(), color.colorSpace()->channels());
        ls << channel->name();
        ls << color.colorSpace()->channelValueText(color.data(), realIndex);
    }
    return ls.join(" ");
}
