/* ============================================================
 *
 * This file is a part of digiKam project
 * https://www.digikam.org
 *
 * Date        : 2023-05-15
 * Description : geolocation engine based on Marble.
 *               (c) 2007-2022 Marble Team
 *               https://invent.kde.org/education/marble/-/raw/master/data/credits_authors.html
 *
 * SPDX-FileCopyrightText: 2023-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 *
 * ============================================================ */

#include "GeoDataIconStyle.h"

// Qt includes

#include <QUrl>
#include <QImageReader>

// Local includes

#include "MarbleDirs.h"
#include "RemoteIconLoader.h"
#include "GeoDataTypes.h"
#include "digikam_debug.h"

namespace Marble
{

class Q_DECL_HIDDEN GeoDataIconStylePrivate
{
public:

    GeoDataIconStylePrivate()
        : m_scale(1.0),
          m_size(0, 0),
          m_aspectRatioMode(Qt::KeepAspectRatio),
          m_iconPath(),
          m_heading(0)
    {
    }

    GeoDataIconStylePrivate(const QString& iconPath, const QPointF& hotSpot)
        : m_scale(1.0),
          m_size(0, 0),
          m_aspectRatioMode(Qt::KeepAspectRatio),
          m_iconPath(iconPath),
          m_hotSpot(hotSpot),
          m_heading(0)
    {
    }

    RemoteIconLoader* remoteIconLoader() const
    {
        static RemoteIconLoader* remoteIconLoader = new RemoteIconLoader();
        return remoteIconLoader;
    }

    QSize scaledSize(const QSize& size) const
    {
        QSize iconSize = size.isNull() ? m_icon.size() : size;

        // Scaling the placemark's icon based on its size, scale, and maximum icon size.
        if (iconSize.width()*m_scale > s_maximumIconSize.width()
            || iconSize.height()*m_scale > s_maximumIconSize.height())
        {
            iconSize.scale(s_maximumIconSize, Qt::KeepAspectRatio);
        }

        else if (iconSize.width()*m_scale < s_minimumIconSize.width()
                 || iconSize.height()*m_scale < s_minimumIconSize.width())
        {
            iconSize.scale(s_minimumIconSize, Qt::KeepAspectRatio);
        }

        else
        {
            iconSize *= m_scale;
        }

        return QSize(iconSize.width() - iconSize.width() % 2,
                     iconSize.height() - iconSize.height() % 2);
    }

    QImage loadIcon(const QString& path, const QSize& size) const
    {
        if (!path.isEmpty())
        {
            // Icons from the local file system
            if (!size.isNull())
            {
                QImageReader imageReader;
                imageReader.setFileName(path);
                auto const imageSize = imageReader.size();
                auto const finalSize = imageSize.scaled(size, m_aspectRatioMode);
                imageReader.setScaledSize(finalSize);
                QImage icon = imageReader.read();

                if (icon.isNull())
                {
                    qCDebug(DIGIKAM_MARBLE_LOG) << "GeoDataIconStyle: Failed to read image " << path << ": " << imageReader.errorString();
                }

                return icon;
            }

            QImage icon = QImage(path);

            if (!icon.isNull())
            {
                return icon;
            }
        }

        if (QUrl(m_iconPath).isValid())
        {
            // if image is not found on disk, check whether the icon is
            // at remote location. If yes then go for remote icon loading
            return remoteIconLoader()->load(QUrl(m_iconPath));
        }

        qCDebug(DIGIKAM_MARBLE_LOG) << "Unable to open style icon at: " << path;
        return QImage();
    }

    float            m_scale;

    QImage           m_icon;
    QSize            m_size;
    Qt::AspectRatioMode m_aspectRatioMode;
    QImage           m_scaledIcon;
    QString          m_iconPath;
    GeoDataHotSpot   m_hotSpot;
    int              m_heading;
};

GeoDataIconStyle::GeoDataIconStyle() :
    d(new GeoDataIconStylePrivate())
{
}

GeoDataIconStyle::GeoDataIconStyle(const GeoDataIconStyle& other) :
    GeoDataColorStyle(other), d(new GeoDataIconStylePrivate(*other.d))
{
}

GeoDataIconStyle::GeoDataIconStyle(const QString& iconPath, const QPointF& hotSpot) :
    d(new GeoDataIconStylePrivate(iconPath, hotSpot))
{
}

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

GeoDataIconStyle& GeoDataIconStyle::operator=(const GeoDataIconStyle& other)
{
    GeoDataColorStyle::operator=(other);
    *d = *other.d;
    return *this;
}

bool GeoDataIconStyle::operator==(const GeoDataIconStyle& other) const
{
    if (GeoDataColorStyle::operator!=(other))
    {
        return false;
    }

    return d->m_scale == other.d->m_scale &&
           d->m_icon == other.d->m_icon &&
           d->m_size == other.d->m_size &&
           d->m_iconPath == other.d->m_iconPath &&
           d->m_hotSpot == other.d->m_hotSpot &&
           d->m_heading == other.d->m_heading;
}

bool GeoDataIconStyle::operator!=(const GeoDataIconStyle& other) const
{
    return !this->operator==(other);
}

const char* GeoDataIconStyle::nodeType() const
{
    return GeoDataTypes::GeoDataIconStyleType;
}

void GeoDataIconStyle::setIcon(const QImage& icon)
{
    d->m_icon = icon;
    d->m_scaledIcon = QImage();
}

void GeoDataIconStyle::setIconPath(const QString& filename)
{
    d->m_iconPath = filename;

    /**
     * Set the m_icon to be a default-constructed icon
     * so that m_icon is null and icon() doesn't return
     * previously loaded icon.
     */
    d->m_icon = QImage();
    d->m_scaledIcon = QImage();
}

QString GeoDataIconStyle::iconPath() const
{
    return d->m_iconPath;
}

QImage GeoDataIconStyle::icon() const
{
    if (!d->m_icon.isNull())
    {
        return d->m_icon;
    }

    else if (!d->m_iconPath.isEmpty())
    {
        d->m_icon = d->loadIcon(resolvePath(d->m_iconPath), d->m_size);
        return d->m_icon;
    }

    else
    {
        return QImage();
    }
}

void GeoDataIconStyle::setHotSpot(const QPointF& hotSpot,
                                  GeoDataHotSpot::Units xunits,
                                  GeoDataHotSpot::Units yunits)
{
    d->m_hotSpot.setHotSpot(hotSpot, xunits, yunits);
}

QPointF GeoDataIconStyle::hotSpot(GeoDataHotSpot::Units& xunits, GeoDataHotSpot::Units& yunits) const
{
    return d->m_hotSpot.hotSpot(xunits, yunits);
}

void GeoDataIconStyle::setSize(const QSize& size, Qt::AspectRatioMode aspectRatioMode)
{
    if (size == d->m_size && aspectRatioMode == d->m_aspectRatioMode)
    {
        return;
    }

    d->m_aspectRatioMode = aspectRatioMode;
    d->m_size = QSize(size.width() - size.width() % 2, size.height() - size.height() % 2);

    if (!d->m_size.isNull() && !d->m_icon.isNull())
    {
        // Resize existing icon that cannot be restored from an image path
        d->m_icon = d->m_icon.scaled(d->m_size);
    }

    else if (!d->m_iconPath.isEmpty())
    {
        // Lazily reload the icons
        d->m_icon = QImage();
        d->m_scaledIcon = QImage();
    }
}

QSize GeoDataIconStyle::size() const
{
    return d->m_size;
}

void GeoDataIconStyle::setScale(float scale)
{
    d->m_scale = scale;
    d->m_scaledIcon = QImage();
}

float GeoDataIconStyle::scale() const
{
    return d->m_scale;
}

QImage GeoDataIconStyle::scaledIcon() const
{
    if (!d->m_scaledIcon.isNull())
    {
        return d->m_scaledIcon;
    }

    // Invalid or trivial scale
    if (d->m_scale <= 0 || d->m_scale == 1.0)
    {
        return icon();
    }

    // Try to load it
    d->m_scaledIcon = d->loadIcon(resolvePath(d->m_iconPath), d->scaledSize(d->m_size));

    if (d->m_scaledIcon.isNull())
    {
        // Direct loading failed, try to scale the icon as a last resort
        QImage const image = icon();

        if (!image.isNull())
        {
            QSize iconSize = d->scaledSize(image.size());
            d->m_scaledIcon = image.scaled(iconSize, Qt::KeepAspectRatio, Qt::SmoothTransformation) ;
        }
    }

    return d->m_scaledIcon;
}

int GeoDataIconStyle::heading() const
{
    return d->m_heading;
}

void GeoDataIconStyle::setHeading(int heading)
{
    d->m_heading = heading;
}

RemoteIconLoader* GeoDataIconStyle::remoteIconLoader() const
{
    return d->remoteIconLoader();
}

void GeoDataIconStyle::pack(QDataStream& stream) const
{
    GeoDataColorStyle::pack(stream);

    stream << d->m_scale;
    stream << d->m_icon;
    d->m_hotSpot.pack(stream);
}

void GeoDataIconStyle::unpack(QDataStream& stream)
{
    GeoDataColorStyle::unpack(stream);

    stream >> d->m_scale;
    stream >> d->m_icon;
    d->m_hotSpot.unpack(stream);
}

} // namespace Marble
