/*
 *   This file is part of Dianara
 *   Copyright 2012-2019  JanKusanagi JRR <jancoding@gmx.com>
 *
 *   This program 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 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program 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 this program; if not, write to the
 *   Free Software Foundation, Inc.,
 *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA .
 */

#include "mischelpers.h"

MiscHelpers::MiscHelpers(QObject *parent) : QObject(parent)
{
    // Creating object not required, all static functions
}




QString MiscHelpers::getCachedAvatarFilename(QString url)
{
    QString localFilename;

    if (!url.isEmpty())
    {
        localFilename = QStandardPaths::standardLocations(QStandardPaths::DataLocation).first();
        localFilename.append("/avatars/");
        localFilename.append(url.trimmed().toUtf8().toBase64());

        QString fileExtension = url;
        fileExtension.remove(QRegExp(".*\\.")); // remove all but the extension

        localFilename.append(".");
        localFilename.append(fileExtension);
    }

    return localFilename;
}







QString MiscHelpers::getCachedImageFilename(QString url)
{
    QString localFilename;

    if (!url.isEmpty())
    {
        url = url.trimmed();
        if (url.startsWith("http://"))
        {
            url.remove(0, 7); // remove http://
        }
        if (url.startsWith("https://"))
        {
            url.remove(0, 8); // remove https://
        }

        // Convert to percentEncoding before, to avoid having "/" and such in the filename
        QByteArray base64url = url.toUtf8().toPercentEncoding().toBase64();
        base64url.truncate(255); // Limit filename length! Just in case the URL is VERY long
                                 // 255 chars is the limit in Ext3 FS

        localFilename = QStandardPaths::standardLocations(QStandardPaths::DataLocation).first();
        localFilename.append("/images/");
        localFilename.append(base64url);
    }

    return localFilename;
}


/*
 * Generate suggested filename for an attachment,
 * based on author ID, original extension, etc.
 *
 */
QString MiscHelpers::getSuggestedFilename(QString authorId,
                                          QString postType,
                                          QString postTitle,
                                          QString fileUrl,
                                          QString mimeType)
{
    // Get original filename in server
    QString originalFilename = fileUrl.split("/").last();

    QString suggestedFilename = authorId;
    suggestedFilename.append("_" + postType);
    if (!postTitle.trimmed().isEmpty())
    {
        suggestedFilename.append("_" + postTitle);
    }

    suggestedFilename.replace("@", QStringLiteral("-")); // Avoid certain chars from author ID
    suggestedFilename.replace(".", QStringLiteral("-"));
    suggestedFilename.replace("/", QStringLiteral("-"));
    suggestedFilename.remove("?");
    suggestedFilename.remove("!");
    suggestedFilename.remove("*");
    suggestedFilename.remove(":");
    suggestedFilename.remove(";");
    suggestedFilename.remove("\"");

    suggestedFilename.replace(" ", QStringLiteral("_")); // Avoid spaces in filename

    suggestedFilename.append("_" + originalFilename); // Something like A53r2w.png


    // Extension for 'file' attachments is given as .bin by server, for now
    if (postType == QStringLiteral("file")) // so determine a proper one based on mime info
    {
        QMimeDatabase mimeDb;
        QMimeType mime = mimeDb.mimeTypeForName(mimeType);

        QString suggestedExtension = mime.preferredSuffix();
        if (!suggestedExtension.isEmpty())
        {
            if (suggestedFilename.endsWith(QStringLiteral(".bin")))
            {
                suggestedFilename.remove(-3, 3);
            }

            suggestedFilename.append(suggestedExtension);
        }
    }

    return suggestedFilename;
}



/*
 * Return MIME content type, like image/png, audio/ogg, etc.
 *
 */
QString MiscHelpers::getFileMimeType(QString fileUri)
{
    qDebug() << "getFileMimeType() file:" << fileUri;

    QMimeDatabase mimeDb;
    QMimeType mimeType = mimeDb.mimeTypeForFile(fileUri);
    qDebug() << mimeType.name();
    qDebug() << mimeType.aliases();

    return mimeType.name(); // mime.aliases() is also interesting
}



/*
 * Return width of an image
 *
 */
int MiscHelpers::getImageWidth(QString fileURI)
{
    QImageReader imageReader(fileURI);

    return imageReader.size().width();
}


bool MiscHelpers::isImageAnimated(QString fileUri)
{
    //qDebug() << "QMovie::supportedFormats()" << QMovie::supportedFormats();
    bool isAnimated = false;

    QImageReader imageReader(fileUri);
    if (imageReader.supportsAnimation())
    {
        QMovie movie;
        movie.setFileName(fileUri);
        qDebug() << "Image format SUPPORTS animation; Frames:" << movie.frameCount();
        if (movie.frameCount() > 1) // FIXME: doesn't work with .MNG, so it returns 0
        {
            qDebug() << "Image IS animated.";
            isAnimated = true;
        }
    }


    return isAnimated;
}

/*
 * Get a pair of icon-name + fallback-image for the given activity verb
 *
 */
QStringList MiscHelpers::iconsForActivity(QString verb)
{
    QVariantMap iconMap;
    iconMap.insert("like",            QStringList{ "emblem-favorite",
                                                   ":/images/button-like.png" });
    iconMap.insert("unlike",          QStringList{ "emblem-favorite",
                                                   ":/images/button-like.png" });
    iconMap.insert("favorite",        QStringList{ "emblem-favorite",
                                                   ":/images/button-like.png" });
    iconMap.insert("unfavorite",      QStringList{ "emblem-favorite",
                                                   ":/images/button-like.png" });

    iconMap.insert("unshare",         QStringList{ "dialog-cancel",
                                                   ":/images/button-cancel.png" });

    iconMap.insert("post",            QStringList{ "mail-send",
                                                   ":/images/button-post.png" });

    iconMap.insert("update",          QStringList{ "view-refresh",
                                                   ":/images/menu-refresh.png" });

    iconMap.insert("follow",          QStringList{ "list-add-user",
                                                   ":/images/list-add.png" });
    iconMap.insert("stop-following",  QStringList{ "list-remove-user",
                                                   ":/images/list-remove.png" });

    iconMap.insert("add",             QStringList{ "list-add",
                                                   ":/images/list-add.png" });
    iconMap.insert("remove",          QStringList{ "list-remove",
                                                   ":/images/list-remove.png" });

    iconMap.insert("create",         QStringList{ "document-new", // FIXME; also -edit?
                                                  ":/images/button-edit.png" });
    iconMap.insert("delete",         QStringList{ "edit-delete",
                                                  ":/images/button-delete.png" });

    iconMap.insert("join",           QStringList{ "user-group-new",
                                                  ":/images/button-online.png" });
    iconMap.insert("leave",          QStringList{ "user-group-delete",
                                                  ":/images/button-close.png" });

    const QStringList missingIconList = QStringList{ "dialog-information",
                                                     ":/images/image-missing.png" };


    return iconMap.value(verb, missingIconList).toStringList();
}


const QStringList MiscHelpers::imageExtensions()
{
    return QStringList{ "png", "jpg", "jpeg", "jpe", "gif",
                        "mng", "webp", "svg" };
}

const QStringList MiscHelpers::audioExtensions()
{
    return QStringList{ "ogg", "oga",  "flac", "opus", "mka",
                        "mp3", "mpga", "wav",  "wma",  "asf" };
}

const QStringList MiscHelpers::videoExtensions()
{
    return QStringList{ "mkv", "mpg", "mpeg", "mpe", "mpv",
                        "ogm", "ogg", "mp4",  "avi", "webm",
                        "mov", "flv", "3gp",  "wmv", "asf" };
}

/*
 * Return a file filter string, usable in file dialogs,
 * such as: (*.png *.jpg *.gif);;
 *
 */
const QString MiscHelpers::fileFilterString(QStringList extensions)
{
    QString filterString = " (";
    foreach (const QString extension, extensions)
    {
        filterString.append(" *." + extension + " ");
    }

    filterString.append(");;");

    return filterString;
}




QString MiscHelpers::fixLongName(QString name)
{
    // very TMP optimization of LOOONG names / FIXME
    if (name.length() > 16)
    {
        name.replace("@", "@ ");
        name.replace(".", ". ");
    }

    return name;
}



/*
 * Return a pretty string with the size of a file, like
 * "33 KiB", "512 bytes" or "3,2 MiB"
 *
 */
QString MiscHelpers::fileSizeString(QString fileURI)
{
    QFileInfo fileInfo(fileURI);
    double fileSize = fileInfo.size();

    QString sizeUnit = tr("bytes");
    if (fileSize > 1024) // if > 1024 bytes, transform to KiB
    {
        fileSize /= 1024.0;
        sizeUnit = QStringLiteral("KiB");
    }
    if (fileSize > 1024) // if > 1024 KiB, transform to MiB
    {
        fileSize /= 1024.0;
        sizeUnit = QStringLiteral("MiB");
    }


    // Return with 0 padding and 2 decimal precision
    return QString("%1 %2").arg(QLocale::system().toString(fileSize, 'f', 2))
                           .arg(sizeUnit);
}

/*
 * Localized resolution string
 *
 */
QString MiscHelpers::resolutionString(int width, int height)
{
    return QString("%1 x %2").arg(QLocale::system().toString(width))
                             .arg(QLocale::system().toString(height));
}




/*
 * Parse a string of HTML and replace the URL in each <img src=""> tag with
 * the corresponding locally cached filename.
 *
 * Return also the string list of the URL's to download
 *
 */
QStringList MiscHelpers::htmlWithReplacedImages(QString originalHtml, int postWidth)
{
    // if no <img tag is found, just return the original HTML
    if (!originalHtml.contains(QStringLiteral("<img"), Qt::CaseInsensitive))
    {
        //qDebug() << "MiscHelpers::htmlWithReplacedImages(); HTML does NOT contain <img> tags...";
        return QStringList(originalHtml);
    }

    //qDebug() << "MiscHelpers::htmlWithReplacedImages(); HTML contains some <img> tags...";
    QString newHtml = originalHtml;
    newHtml.remove(QStringLiteral("\n")); // Remove in case misbehaving applications added any

    QStringList imageList;
    QString imgSrc;

    QRegExp regExp(QStringLiteral("\\<img(.+)src=(\"|\\')([^\"\\']+)(\"|\\')(.*)\\>"));
    regExp.setMinimal(true);

    int matchedLength = 0;
    int stringPos = 0;
    while (matchedLength != -1)
    {
        stringPos = regExp.indexIn(newHtml, stringPos);
        matchedLength = regExp.matchedLength();


        //qDebug() << "#######\n\nregExp match = " << regExp.cap(0);
        //qDebug() << "Groups:" << regExp.cap(1) << " // " << regExp.cap(2)
        //         << " // " << regExp.cap(3) << " // " << regExp.cap(4)
        //         << " // " << regExp.cap(5);
        //qDebug() << "Matched length is:" << matchedLength;

        imgSrc = regExp.cap(3);

        // Protection: ensure the <img> tag doesn't point to a huge video
        foreach (const QString extension, videoExtensions())
        {
            if (imgSrc.endsWith(extension))
            {
                qDebug() << "*** Ignoring IMG tag that points to a video!! ***";
                qDebug() << "*** " << imgSrc;
                imgSrc.clear(); // Avoid getting it into the download list
                break;
            }
        }

        // If not an empty string, originally or due to video extension,
        // add to the list, and replace HTML
        if (!imgSrc.isEmpty())
        {
            // If the URL had parameters, they _might_ have &amp; in place of "&"
            imgSrc.replace(QStringLiteral("&amp;"),
                           QStringLiteral("&"));   // Put them back

            // Check if http part (scheme) is missing from URL, and add it
            if (!imgSrc.startsWith(QStringLiteral("http")))
            {
                imgSrc.prepend(QStringLiteral("http:"));
            }

            // Add URL to list
            imageList.append(imgSrc);

            const QString cachedImageFilename = getCachedImageFilename(imgSrc);


            // Determine optimal image size
            int imageWidth = getImageWidth(cachedImageFilename);
            if (imageWidth <= 0) // If we still don't have a cached file,
            {
                imageWidth = 64; // Set a typical icon size
            }

            // If width is bigger than the post, make it smaller to fit
            if (imageWidth > postWidth - 32)
            {
                // Some margins, to account for a scrollbar or a tab space before the image
                // FIXME
                imageWidth = postWidth - 32;
            }


            newHtml.replace(stringPos,
                            matchedLength,
                            QStringLiteral("<img src=\"")
                            + cachedImageFilename
                            + QStringLiteral("\" width=\"")
                            + QString("%1").arg(imageWidth)
                            + QStringLiteral("\" />"));
        }

        stringPos += matchedLength; // FIXME: error control
    }

    imageList.prepend(newHtml); // The modified HTML goes before the image list
    //qDebug() << "Returned HTML and images:\n" << imageList << "\n#################";

    return imageList;
}


/*
 * Basic cleanup of HTML stuff
 *
 */
QString MiscHelpers::cleanupHtml(QString originalHtml)
{
    QString cleanHtml = originalHtml;

    // Remove line breaks, as that results in server error 500
    cleanHtml.replace(QStringLiteral("\n"), QStringLiteral(" "));


    QRegExp doctypeRE(QStringLiteral("<!DOCTYPE.*>"));
    doctypeRE.setMinimal(true);
    cleanHtml.remove(doctypeRE);

    QRegExp headRE(QStringLiteral("<html><head>.*</head>"));
    headRE.setMinimal(true);
    cleanHtml.remove(headRE);

    QRegExp bodyRE(QStringLiteral("<body style.*>"));
    bodyRE.setMinimal(true);
    cleanHtml.remove(bodyRE);

    //////////////////////////////////////// Remove <span style=...> from links
    QRegExp linkStyleRE(QStringLiteral("<a href=.*>.*</a>"));
    linkStyleRE.setMinimal(true);
    QRegExp spanRE(QStringLiteral("<span style=.*>(.*)</span>")); // FIXME: remove ONLY color info
    spanRE.setMinimal(true);

    int pos = 0;
    while ((pos = linkStyleRE.indexIn(cleanHtml, pos)) != -1)
    {
        int removedTextOffset = 0;
        if (spanRE.indexIn(cleanHtml, pos) != -1)
        {
            // Replace the whole span tag by just what was inside
            cleanHtml.replace(spanRE.cap(0), spanRE.cap(1));
            //qDebug() << "spanRE capture: " << spanRE.capturedTexts();
            removedTextOffset = spanRE.cap(0).length() - spanRE.cap(1).length();
        }

        pos += linkStyleRE.matchedLength() - removedTextOffset;
    }

    ////////////////////////////////////// Remove style from <ul> <ol> and <li>
    QRegExp ulStyleRE(QStringLiteral("<ul style.*>"));
    ulStyleRE.setMinimal(true);
    cleanHtml.replace(ulStyleRE, QStringLiteral("<ul>"));

    QRegExp olStyleRE(QStringLiteral("<ol style.*>"));
    olStyleRE.setMinimal(true);
    cleanHtml.replace(olStyleRE, QStringLiteral("<ol>"));

    QRegExp liStyleRE(QStringLiteral("<li style.*>"));
    liStyleRE.setMinimal(true);
    cleanHtml.replace(liStyleRE, QStringLiteral("<li>"));


    // FIXME: Maybe try to remove background colors from <p> elements

    // Remove these elements created by some QTextBlocks
    cleanHtml.remove(QStringLiteral("<!--StartFragment-->"));
    cleanHtml.remove(QStringLiteral("<!--EndFragment-->"));

    cleanHtml.remove(QStringLiteral("</body></html>"));

    return cleanHtml.trimmed();
}

/*
 * Remove only <a href...> and </a> from a HTML text
 *
 */
QString MiscHelpers::htmlWithoutLinks(QString originalHtml)
{
    QString cleanHtml = originalHtml;

    QRegExp linksRE(QStringLiteral("<a href=.*>"));
    linksRE.setMinimal(true);
    linksRE.setCaseSensitivity(Qt::CaseInsensitive);
    cleanHtml.remove(linksRE);

    cleanHtml.remove(QStringLiteral("</a>"), Qt::CaseInsensitive);


    return cleanHtml;
}

QString MiscHelpers::htmlToPlainText(QString html, int charLimit)
{
    QTextDocument textDocument;
    textDocument.setHtml(html);

    QString plainText = textDocument.toPlainText().trimmed();
    plainText.replace(QStringLiteral("\n"), QStringLiteral(" "));
    plainText = plainText.trimmed();

    if (charLimit != 0 && plainText.length() > charLimit)
    {
        plainText = plainText.left(charLimit).trimmed() + QStringLiteral("...");
    }

    return plainText;
}



/*
 * Return some HTML with a blockquote, quote symbols, etc.
 *
 *
 */
QString MiscHelpers::quotedText(QString author, QString content)
{
    QTextDocument textDocument;
    textDocument.setHtml(content);

    content = textDocument.toPlainText().trimmed();
    content.replace("<", "&lt;"); // back to HTML entities
    content.replace(">", "&gt;");
    content.replace("\n", "<br>"); // Important to replace this AFTER < and >

    QString quoteHtml = "&raquo; <b><u>"+ author + "</u></b>:"  // >> + name
                        "<blockquote>"
                        "<small><i>&ldquo;" + content + "&rdquo;</i></small>"
                        "</blockquote> <br>";

    return quoteHtml;
}


/*
 * Limit length of a string to charLimit chars,
 * removing characters from the middle
 *
 */
QString MiscHelpers::elidedText(QString text, int charLimit)
{
    QString shortText = text;

    if (text.length() > charLimit)
    {
        charLimit -= 5;

        shortText = text.left(charLimit / 2);
        shortText.append(" ... ");
        shortText.append(text.right(charLimit / 2));
    }

    return shortText;
}



/*
 * Generate the first part of the HTML of a post
 * that includes media attachments
 *
 */
QString MiscHelpers::mediaHtmlBase(QString postType,
                                   QString attachmentFilename,
                                   QString tooltipMessage,
                                   QString belowMessage,
                                   int imageWidth)
{
    QString html = "<table cellpadding=4 width=100% "
                   "style=\" "
                   "border-style: solid; "
                   "border-width: 1px; "
                   "margin: 2px; \" >"
                   // First row, with gradient
                   "<tr style=\" "
                   "background-color: "
                   "qlineargradient(spread:reflect, "
                   "x1:0, y1:0, x2:0.5, y2:0, "
                   "stop:0 rgba(255, 255, 255, 0), "
                   "stop:1 palette(highlight) ); "
                   "\" >"
                   "<td align=center>"
                   "<a title=\"" + tooltipMessage + "\" href";

    if (postType == QStringLiteral("image"))
    {
        html.append("=\"image:/" + attachmentFilename + "\" >");
    }
    else
    {
        html.append("=\"attachment:/\" >");
    }

    html.append("<img src=\"" + attachmentFilename +  "\" "
                "width=\"" + QString("%1").arg(imageWidth) + "\" />"
                "</a></td></tr>");

    // Second row, to add a message related to the image or attachment
    html.append("<tr><td align=center><small>"
                + belowMessage +
                "</small></td></tr>"
                "</table>"
                "<br><br>"); // Double line-break, because the content might not start with <p>

    return html;
}

/*
 * Open URL in system's browser, showing a proper error message if that fails
 *
 */
bool MiscHelpers::openUrl(QUrl url, QWidget *parentWidget)
{
    bool browserLaunched = QDesktopServices::openUrl(url);

    if (!browserLaunched)
    {
        QMessageBox::warning(parentWidget,
                             tr("Error: Unable to launch browser"),
                             tr("The default system web browser could not "
                                "be executed.")
                             + "<br /><br />"
                             + tr("You might need to install the "
                                  "XDG utilities."));
    }

    return browserLaunched;
}
