/************************************************************************
 **
 **  @file   def.cpp
 **  @author Roman Telezhynskyi <dismine(at)gmail.com>
 **  @date   11 6, 2015
 **
 **  @brief
 **  @copyright
 **  This source code is part of the Valentina project, a pattern making
 **  program, whose allow create and modeling patterns of clothing.
 **  Copyright (C) 2015 Valentina project
 **  <https://gitlab.com/smart-pattern/valentina> All Rights Reserved.
 **
 **  Valentina is free software: you can redistribute it and/or modify
 **  it under the terms of the GNU General Public License as published by
 **  the Free Software Foundation, either version 3 of the License, or
 **  (at your option) any later version.
 **
 **  Valentina is distributed in the hope that it will be useful,
 **  but WITHOUT ANY WARRANTY; without even the implied warranty of
 **  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 **  GNU General Public License for more details.
 **
 **  You should have received a copy of the GNU General Public License
 **  along with Valentina.  If not, see <http://www.gnu.org/licenses/>.
 **
 *************************************************************************/

#include "def.h"

#include <QApplication>
#include <QChar>
#include <QColor>
#include <QComboBox>
#include <QCursor>
#include <QDir>
#include <QDirIterator>
#include <QFileInfo>
#include <QGuiApplication>
#include <QImage>
#include <QLatin1Char>
#include <QList>
#include <QMap>
#include <QMessageLogger>
#include <QObject>
#include <QPixmap>
#include <QPrinterInfo>
#include <QProcess>
#include <QRgb>
#include <QStaticStringData>
#include <QStringData>
#include <QStringDataPtr>
#include <QtDebug>
#include <QPixmapCache>
#include <QGraphicsItem>
#include <QGlobalStatic>
#include <QDesktopServices>
#include <QUrl>

#include "vabstractapplication.h"
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
#   include "vdatastreamenum.h"
#endif
#include "../ifc/exception/vexception.h"
#include "literals.h"

#if QT_VERSION < QT_VERSION_CHECK(5, 5, 0)
#include "../vmisc/diagnostic.h"
#endif // QT_VERSION < QT_VERSION_CHECK(5, 5, 0)

//---------------------------------------------------------------------------------------------------------------------
QPixmap QPixmapFromCache(const QString &pixmapPath)
{
    QPixmap pixmap;

    if (not QPixmapCache::find(pixmapPath, &pixmap))
    {
        pixmap = QPixmap(pixmapPath);
        QPixmapCache::insert(pixmapPath, pixmap);
    }
    return pixmap;
}

//---------------------------------------------------------------------------------------------------------------------
void SetItemOverrideCursor(QGraphicsItem *item, const QString &pixmapPath, int hotX, int hotY)
{
#ifndef QT_NO_CURSOR
    SCASSERT(item != nullptr)
    item->setCursor(QCursor(QPixmapFromCache(pixmapPath), hotX, hotY));
#else
    Q_UNUSED(item)
    Q_UNUSED(pixmapPath)
    Q_UNUSED(hotX)
    Q_UNUSED(hotY)
#endif
}

//---------------------------------------------------------------------------------------------------------------------
QStringList SupportedLocales()
{
    return QStringList
    {
        "uk_UA",
        "de_DE",
        "cs_CZ",
        "he_IL",
        "fr_FR",
        "it_IT",
        "nl_NL",
        "id_ID",
        "es_ES",
        "fi_FI",
        "en_US",
        "en_CA",
        "en_IN",
        "ro_RO",
        "zh_CN",
        "pt_BR",
        "el_GR",
        "pl_PL"
    };
}

//---------------------------------------------------------------------------------------------------------------------
/**
 * @brief strippedName the function call around curFile to exclude the path to the file.
 * @param fullFileName full path to the file.
 * @return file name.
 */
QString StrippedName(const QString &fullFileName)
{
    return QFileInfo(fullFileName).fileName();
}

//---------------------------------------------------------------------------------------------------------------------
QString RelativeMPath(const QString &patternPath, const QString &absoluteMPath)
{
    if (patternPath.isEmpty() || absoluteMPath.isEmpty())
    {
        return absoluteMPath;
    }

    if (QFileInfo(absoluteMPath).isRelative())
    {
        return absoluteMPath;
    }

    return QFileInfo(patternPath).absoluteDir().relativeFilePath(absoluteMPath);
}

//---------------------------------------------------------------------------------------------------------------------
QString AbsoluteMPath(const QString &patternPath, const QString &relativeMPath)
{
    if (patternPath.isEmpty() || relativeMPath.isEmpty())
    {
        return relativeMPath;
    }

    if (QFileInfo(relativeMPath).isAbsolute())
    {
        return relativeMPath;
    }

    return QFileInfo(QFileInfo(patternPath).absoluteDir(), relativeMPath).absoluteFilePath();
}

//---------------------------------------------------------------------------------------------------------------------
QMarginsF GetMinPrinterFields(const QSharedPointer<QPrinter> &printer)
{
    QPageLayout layout = printer->pageLayout();
    layout.setUnits(QPageLayout::Millimeter);
    const QMarginsF minMargins = layout.minimumMargins();

    QMarginsF min;
    min.setLeft(UnitConvertor(minMargins.left(), Unit::Mm, Unit::Px));
    min.setRight(UnitConvertor(minMargins.right(), Unit::Mm, Unit::Px));
    min.setTop(UnitConvertor(minMargins.top(), Unit::Mm, Unit::Px));
    min.setBottom(UnitConvertor(minMargins.bottom(), Unit::Mm, Unit::Px));
    return min;
}

//---------------------------------------------------------------------------------------------------------------------
auto GetPrinterFields(const QSharedPointer<QPrinter> &printer) -> QMarginsF
{
    if (printer.isNull())
    {
        return {};
    }

    // We can't use Unit::Px because our dpi in most cases is different
    const QMarginsF m = printer->pageLayout().margins(QPageLayout::Millimeter);

    QMarginsF def;
    def.setLeft(UnitConvertor(m.left(), Unit::Mm, Unit::Px));
    def.setRight(UnitConvertor(m.right(), Unit::Mm, Unit::Px));
    def.setTop(UnitConvertor(m.top(), Unit::Mm, Unit::Px));
    def.setBottom(UnitConvertor(m.bottom(), Unit::Mm, Unit::Px));
    return def;
}

//---------------------------------------------------------------------------------------------------------------------
QPixmap darkenPixmap(const QPixmap &pixmap)
{
    QImage img = pixmap.toImage().convertToFormat(QImage::Format_ARGB32);
    const int imgh = img.height();
    const int imgw = img.width();
    for (int y = 0; y < imgh; ++y)
    {
        for (int x = 0; x < imgw; ++x)
        {
            int h, s, v;
            QRgb pixel = img.pixel(x, y);
            const int a = qAlpha(pixel);
            QColor hsvColor(pixel);
            hsvColor.getHsv(&h, &s, &v);
            s = qMin(100, s * 2);
            v = v / 2;
            hsvColor.setHsv(h, s, v);
            pixel = hsvColor.rgb();
            img.setPixel(x, y, qRgba(qRed(pixel), qGreen(pixel), qBlue(pixel), a));
        }
    }
    return QPixmap::fromImage(img);
}

//---------------------------------------------------------------------------------------------------------------------
void ShowInGraphicalShell(const QString &filePath)
{
#ifdef Q_OS_MAC
    QStringList args{
        "-e", "tell application \"Finder\"",
        "-e", "activate",
        "-e", "select POSIX file \""+filePath+"\"",
        "-e", "end tell"
    };
    QProcess::startDetached(QStringLiteral("osascript"), args);
#elif defined(Q_OS_WIN)
    QProcess::startDetached(QStringLiteral("explorer"), QStringList{"/select", QDir::toNativeSeparators(filePath)});
#else
    const int timeout = 1000; // ms
    QString command = QStringLiteral("dbus-send --reply-timeout=%1 --session --dest=org.freedesktop.FileManager1 "
                                     "--type=method_call /org/freedesktop/FileManager1 "
                                     "org.freedesktop.FileManager1.ShowItems array:string:\"%2\" string:\"\"")
                          .arg(timeout).arg(QUrl::fromLocalFile(filePath).toString());

    // Sending message through dbus will highlighting file
    QProcess dbus;
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
    QT_WARNING_PUSH
    QT_WARNING_DISABLE_CLANG("-Wdeprecated-declarations")
    QT_WARNING_DISABLE_GCC("-Wdeprecated-declarations")

    dbus.start(command); // clazy:exclude=qt6-deprecated-api-fixes

    QT_WARNING_POP
#else
    dbus.startCommand(command);
#endif
    if (not dbus.waitForStarted(timeout))
    {
        // This way will open only window without highlighting file
        QDesktopServices::openUrl(QUrl::fromLocalFile(QFileInfo(filePath).path()));
        return;
    }

    if (not dbus.waitForFinished(timeout))
    {
        // This way will open only window without highlighting file
        QDesktopServices::openUrl(QUrl::fromLocalFile(QFileInfo(filePath).path()));
        return;
    }
#endif
}

//---------------------------------------------------------------------------------------------------------------------
bool IsOptionSet(int argc, char *argv[], const char *option)
{
    for (int i = 1; i < argc; ++i)
    {
        if (!qstrcmp(argv[i], option))
        {
            return true;
        }
    }
    return false;
}

//---------------------------------------------------------------------------------------------------------------------
// See issue #624. https://bitbucket.org/dismine/valentina/issues/624
void InitHighDpiScaling(int argc, char *argv[])
{
    /* For more info see: http://doc.qt.io/qt-5/highdpi.html */
    if (IsOptionSet(argc, argv, qPrintable(QLatin1String("--") + LONG_OPTION_NO_HDPI_SCALING)))
    {
#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
        QCoreApplication::setAttribute(Qt::AA_DisableHighDpiScaling);
#else
        qputenv("QT_DEVICE_PIXEL_RATIO", QByteArray("1"));
#endif
    }
    else
    {
#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
        QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); // DPI support
#else
        qputenv("QT_AUTO_SCREEN_SCALE_FACTOR", QByteArray("1"));
#endif
    }
}

Q_GLOBAL_STATIC_WITH_ARGS(const QString, strTMark, (QLatin1String("tMark"))) // NOLINT
Q_GLOBAL_STATIC_WITH_ARGS(const QString, strVMark, (QLatin1String("vMark"))) // NOLINT
Q_GLOBAL_STATIC_WITH_ARGS(const QString, strVMark2, (QLatin1String("vMark2"))) // NOLINT
Q_GLOBAL_STATIC_WITH_ARGS(const QString, strUMark, (QLatin1String("uMark"))) // NOLINT
Q_GLOBAL_STATIC_WITH_ARGS(const QString, strBoxMark, (QLatin1String("boxMark"))) // NOLINT

//---------------------------------------------------------------------------------------------------------------------
QString PassmarkLineTypeToString(PassmarkLineType type)
{
    switch(type)
    {
        case PassmarkLineType::OneLine:
            return strOne;
        case PassmarkLineType::TwoLines:
            return strTwo;
        case PassmarkLineType::ThreeLines:
            return strThree;
        case PassmarkLineType::TMark:
            return *strTMark;
        case PassmarkLineType::VMark:
            return *strVMark;
        case PassmarkLineType::VMark2:
            return *strVMark2;
        case PassmarkLineType::UMark:
            return *strUMark;
        case PassmarkLineType::BoxMark:
            return *strBoxMark;
        default:
            break;
    }

    return strOne;
}

//---------------------------------------------------------------------------------------------------------------------
PassmarkLineType StringToPassmarkLineType(const QString &value)
{
    const QStringList values{strOne, strTwo, strThree, *strTMark, *strVMark, *strVMark2, *strUMark, *strBoxMark};

    switch(values.indexOf(value))
    {
        case 0: // strOne
            return PassmarkLineType::OneLine;
        case 1: // strTwo
            return PassmarkLineType::TwoLines;
        case 2: // strThree
            return PassmarkLineType::ThreeLines;
        case 3: // strTMark
            return PassmarkLineType::TMark;
        case 4: // strVMark
            return PassmarkLineType::VMark;
        case 5: // strVMark2
            return PassmarkLineType::VMark2;
        case 6: // strUMark
            return PassmarkLineType::UMark;
        case 7: // strBoxMark
            return PassmarkLineType::BoxMark;
        default:
            break;
    }
    return PassmarkLineType::OneLine;
}

//---------------------------------------------------------------------------------------------------------------------
QString PassmarkAngleTypeToString(PassmarkAngleType type)
{
    switch(type)
    {
        case PassmarkAngleType::Straightforward:
            return strStraightforward;
        case PassmarkAngleType::Bisector:
            return strBisector;
        case PassmarkAngleType::Intersection:
            return strIntersection;
        case PassmarkAngleType::IntersectionOnlyLeft:
            return strIntersectionOnlyLeft;
        case PassmarkAngleType::IntersectionOnlyRight:
            return strIntersectionOnlyRight;
        case PassmarkAngleType::Intersection2:
            return strIntersection2;
        case PassmarkAngleType::Intersection2OnlyLeft:
            return strIntersection2OnlyLeft;
        case PassmarkAngleType::Intersection2OnlyRight:
            return strIntersection2OnlyRight;
        default:
            break;
    }

    return strStraightforward;
}

//---------------------------------------------------------------------------------------------------------------------
PassmarkAngleType StringToPassmarkAngleType(const QString &value)
{
    const QStringList values = QStringList() << strStraightforward
                                             << strBisector
                                             << strIntersection
                                             << strIntersectionOnlyLeft
                                             << strIntersectionOnlyRight
                                             << strIntersection2
                                             << strIntersection2OnlyLeft
                                             << strIntersection2OnlyRight;

    switch(values.indexOf(value))
    {
        case 0:
            return PassmarkAngleType::Straightforward;
        case 1:
            return PassmarkAngleType::Bisector;
        case 2:
            return PassmarkAngleType::Intersection;
        case 3:
            return PassmarkAngleType::IntersectionOnlyLeft;
        case 4:
            return PassmarkAngleType::IntersectionOnlyRight;
        case 5:
            return PassmarkAngleType::Intersection2;
        case 6:
            return PassmarkAngleType::Intersection2OnlyLeft;
        case 7:
            return PassmarkAngleType::Intersection2OnlyRight;
        default:
            break;
    }
    return PassmarkAngleType::Straightforward;
}


//---------------------------------------------------------------------------------------------------------------------
Unit StrToUnits(const QString &unit)
{
    const QStringList units = QStringList() << unitMM << unitCM << unitINCH << unitPX;
    Unit result = Unit::Cm;
    switch (units.indexOf(unit))
    {
        case 0:// mm
            result = Unit::Mm;
            break;
        case 2:// inch
            result = Unit::Inch;
            break;
        case 3:// px
            result = Unit::Px;
            break;
        case 1:// cm
        default:
            result = Unit::Cm;
            break;
    }
    return result;
}

//---------------------------------------------------------------------------------------------------------------------
/**
 * @brief UnitsToStr translate unit to string.
 *
 * This method used when need write unit in xml file and for showing unit in dialogs.
 * @param unit curent unit
 * @param translate true if need show translated name. Default value false.
 * @return string reprezantation for unit.
 */
QString UnitsToStr(const Unit &unit, const bool translate)
{
    QString result;
    switch (unit)
    {
        case Unit::Mm:
            translate ? result = QObject::tr("mm") : result = unitMM;
            break;
        case Unit::Inch:
            translate ? result = QObject::tr("inch") : result = unitINCH;
            break;
        case Unit::Px:
            translate ? result = QObject::tr("px") : result = unitPX;
            break;
        case Unit::LAST_UNIT_DO_NOT_USE:
            break;
        case Unit::Cm:
        default:
            translate ? result = QObject::tr("cm") : result = unitCM;
            break;
    }
    return result;
}

//---------------------------------------------------------------------------------------------------------------------
void InitLanguages(QComboBox *combobox)
{
    SCASSERT(combobox != nullptr)
    combobox->clear();

    QStringList fileNames;
    QDirIterator it(VAbstractApplication::translationsPath(), QStringList("valentina_*.qm"), QDir::Files,
                    QDirIterator::Subdirectories);
    while (it.hasNext())
    {
        it.next();
        fileNames.append(it.fileName());
    }

    bool englishUS = false;
    const QString en_US = QStringLiteral("en_US");

    for (auto locale : fileNames)
    {
        // get locale extracted by filename           "valentina_de_De.qm"
        locale.truncate(locale.lastIndexOf('.'));  // "valentina_de_De"
        locale.remove(0, locale.indexOf('_') + 1); // "de_De"

        if (locale.startsWith(QLatin1String("ru")))
        {
            continue;
        }

        if (not englishUS)
        {
            englishUS = (en_US == locale);
        }

        QLocale loc = QLocale(locale);
        QString lang = loc.nativeLanguageName();
        // Since Qt 5.12 country names have spaces
        QIcon ico(QString("://flags/%1.png").arg(QLocale::countryToString(loc.country()).remove(' ')));

        combobox->addItem(ico, lang, locale);
    }

    if (combobox->count() == 0 || not englishUS)
    {
        // English language is internal and doens't have own *.qm file.
        // Since Qt 5.12 country names have spaces
        QIcon ico(QString("://flags/%1.png").arg(QLocale::countryToString(QLocale::UnitedStates).remove(' ')));
        QString lang = QLocale(en_US).nativeLanguageName();
        combobox->addItem(ico, lang, en_US);
    }

    // set default translators and language checked
    qint32 index = combobox->findData(VAbstractApplication::VApp()->Settings()->GetLocale());
    if (index != -1)
    {
        combobox->setCurrentIndex(index);
    }
}

const quint32 CustomSARecord::streamHeader = 0xEBFF7586; // CRC-32Q string "CustomSARecord"
const quint16 CustomSARecord::classVersion = 1;

// Friend functions
//---------------------------------------------------------------------------------------------------------------------
QDataStream &operator<<(QDataStream &out, const CustomSARecord &record)
{
    out << CustomSARecord::streamHeader << CustomSARecord::classVersion;

    // Added in classVersion = 1
    out << record.startPoint;
    out << record.path;
    out << record.endPoint;
    out << record.reverse;
    out << record.includeType;

    // Added in classVersion = 2

    return out;
}

//---------------------------------------------------------------------------------------------------------------------
QDataStream &operator>>(QDataStream &in, CustomSARecord &record)
{
    quint32 actualStreamHeader = 0;
    in >> actualStreamHeader;

    if (actualStreamHeader != CustomSARecord::streamHeader)
    {
        QString message = QCoreApplication::tr("CustomSARecord prefix mismatch error: actualStreamHeader = 0x%1 "
                                               "and streamHeader = 0x%2")
                .arg(actualStreamHeader, 8, 0x10, QChar('0'))
                .arg(CustomSARecord::streamHeader, 8, 0x10, QChar('0'));
        throw VException(message);
    }

    quint16 actualClassVersion = 0;
    in >> actualClassVersion;

    if (actualClassVersion > CustomSARecord::classVersion)
    {
        QString message = QCoreApplication::tr("CustomSARecord compatibility error: actualClassVersion = %1 and "
                                               "classVersion = %2")
                .arg(actualClassVersion).arg(CustomSARecord::classVersion);
        throw VException(message);
    }

    in >> record.startPoint;
    in >> record.path;
    in >> record.endPoint;
    in >> record.reverse;
    in >> record.includeType;

//    if (actualClassVersion >= 2)
//    {

//    }

    return in;
}

//---------------------------------------------------------------------------------------------------------------------
QString IncrementTypeToString(IncrementType type)
{
    switch(type)
    {
        case IncrementType::Increment:
            return strTypeIncrement;
        case IncrementType::Separator:
            return strTypeSeparator;
        default:
            break;
    }

    return strTypeIncrement;
}

//---------------------------------------------------------------------------------------------------------------------
IncrementType StringToIncrementType(const QString &value)
{
    const QStringList values { strTypeIncrement, strTypeSeparator };

    switch(values.indexOf(value))
    {
        case 0:
            return IncrementType::Increment;
        case 1:
            return IncrementType::Separator;
        default:
            break;
    }
    return IncrementType::Increment;
}

//---------------------------------------------------------------------------------------------------------------------
QString MeasurementTypeToString(MeasurementType type)
{
    switch(type)
    {
        case MeasurementType::Measurement:
            return strTypeMeasurement;
        case MeasurementType::Separator:
            return strTypeSeparator;
        default:
            break;
    }

    return strTypeIncrement;
}

//---------------------------------------------------------------------------------------------------------------------
MeasurementType StringToMeasurementType(const QString &value)
{
    const QStringList values { strTypeMeasurement, strTypeSeparator };

    switch(values.indexOf(value))
    {
        case 0:
            return MeasurementType::Measurement;
        case 1:
            return MeasurementType::Separator;
        default:
            break;
    }
    return MeasurementType::Measurement;
}

//---------------------------------------------------------------------------------------------------------------------
QStringList SplitFilePaths(const QString &path)
{
    QStringList result;
    QString subPath = QDir::cleanPath(path);
    QString lastFileName;

    do
    {
        QFileInfo fileInfo(subPath);
        lastFileName = fileInfo.fileName();
        if (not lastFileName.isEmpty())
        {
            result.prepend(lastFileName);
            subPath = fileInfo.path();
        }
    }
    while(not lastFileName.isEmpty());

    return result;
}
