/*
 * urlwatcherplugin.cpp - Psi plugin to log URLs to a widget
 * Copyright (C) 2006  Kevin Smith
 *
 * 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.
 *
 * You can also redistribute and/or modify this program under the
 * terms of the Psi License, specified in the accompanied COPYING
 * file, as published by the Psi Project; either dated January 1st,
 * 2005, 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, see <https://www.gnu.org/licenses/>.
 *
 */

#include "eventfilter.h"
#include "psiplugin.h"
#include "urlevent.h"

#include <QString>
#include <QtCore>
#include <QtGui>

class URLWatcherPlugin : public QObject, public PsiPlugin, public EventFilter {
    Q_OBJECT
    Q_INTERFACES(PsiPlugin EventFilter)

public:
    URLWatcherPlugin();
    ~URLWatcherPlugin();

    virtual QString  name() const;
    virtual QString  shortName() const;
    virtual QString  version() const;
    virtual QWidget *options() const;
    virtual bool     enable();
    virtual bool     disable();

    virtual bool processEvent(int account, const QDomElement &e);
    virtual bool processMessage(int account, const QString &fromJid, const QString &body, const QString &subject);

private:
    bool            enabled_;
    QList<URLEvent> urls_;
    QWidget *       viewer_;
    QTextEdit *     viewerText_;
};

Q_EXPORT_PLUGIN(URLWatcherPlugin);

URLWatcherPlugin::URLWatcherPlugin()
{
    enabled_ = false;
    viewer_  = new QWidget();
    QVBoxLayout *vboxLayout;
    QFrame *     frame;
    QLabel *     label;
    vboxLayout = new QVBoxLayout(viewer_);
    vboxLayout->setObjectName(QString::fromUtf8("vboxLayout"));
    frame = new QFrame(viewer_);
    frame->setObjectName(QString::fromUtf8("frame"));
    frame->setFrameShape(QFrame::StyledPanel);
    frame->setFrameShadow(QFrame::Raised);
    viewerText_ = new QTextEdit(frame);
    viewerText_->setObjectName(QString::fromUtf8("text"));
    viewerText_->setReadOnly(true);

    label = new QLabel(viewer_);
    label->setObjectName(QString::fromUtf8("label"));
    label->setText("URLs");
    vboxLayout->addWidget(label);
    vboxLayout->addWidget(viewerText_);
    // viewer_->show();
}

URLWatcherPlugin::~URLWatcherPlugin() { delete viewer_; }

QString URLWatcherPlugin::name() const { return "URL Watcher Plugin"; }

QString URLWatcherPlugin::shortName() const { return "urlWatcher"; }

QString URLWatcherPlugin::version() const { return "0.2"; }

QWidget *URLWatcherPlugin::options() const { return 0; }

bool URLWatcherPlugin::enable()
{
    viewer_->show();
    enabled_ = true;
    return true;
}

bool URLWatcherPlugin::disable()
{
    viewer_->hide();
    enabled_ = false;
    return true;
}

QString resolveEntities(const QString &in)
{
    QString out;

    for (int i = 0; i < (int)in.length(); ++i) {
        if (in[i] == '&') {
            // find a semicolon
            ++i;
            int n = in.indexOf(';', i);
            if (n == -1)
                break;
            QString type = in.mid(i, (n - i));
            i            = n; // should be n+1, but we'll let the loop increment do it

            if (type == "amp")
                out += '&';
            else if (type == "lt")
                out += '<';
            else if (type == "gt")
                out += '>';
            else if (type == "quot")
                out += '\"';
            else if (type == "apos")
                out += '\'';
        } else {
            out += in[i];
        }
    }

    return out;
}

static bool linkify_pmatch(const QString &str1, int at, const QString &str2)
{
    if (str2.length() > (str1.length() - at))
        return false;

    for (int n = 0; n < (int)str2.length(); ++n) {
        if (str1.at(n + at).toLower() != str2.at(n).toLower())
            return false;
    }

    return true;
}

static bool linkify_isOneOf(const QChar &c, const QString &charlist)
{
    for (int i = 0; i < (int)charlist.length(); ++i) {
        if (c == charlist.at(i))
            return true;
    }

    return false;
}

// encodes a few dangerous html characters
static QString linkify_htmlsafe(const QString &in)
{
    QString out;

    for (int n = 0; n < in.length(); ++n) {
        if (linkify_isOneOf(in.at(n), "\"\'`<>")) {
            // hex encode
            QString hex;
            hex.sprintf("%%%02X", in.at(n).toLatin1());
            out.append(hex);
        } else {
            out.append(in.at(n));
        }
    }

    return out;
}

static bool linkify_okUrl(const QString &url)
{
    if (url.at(url.length() - 1) == '.')
        return false;

    return true;
}

static bool linkify_okEmail(const QString &addy)
{
    // this makes sure that there is an '@' and a '.' after it, and that there is
    // at least one char for each of the three sections
    int n = addy.indexOf('@');
    if (n == -1 || n == 0)
        return false;
    int d = addy.indexOf('.', n + 1);
    if (d == -1 || d == 0)
        return false;
    if ((addy.length() - 1) - d <= 0)
        return false;
    if (addy.indexOf("..") != -1)
        return false;

    return true;
}

bool URLWatcherPlugin::processEvent(int account, const QDomElement &e)
{
    Q_UNUSED(account);
    Q_UNUSED(e);
    return false;
}

bool URLWatcherPlugin::processMessage(int account, const QString &fromJid, const QString &body, const QString &subject)
{
    Q_UNUSED(account);
    Q_UNUSED(subject);

    QString newUrl;
    QString out = body;
    int     x1, x2;
    bool    isUrl, isEmail;
    QString linked, link, href;

    for (int n = 0; n < (int)out.length(); ++n) {
        isUrl   = false;
        isEmail = false;
        x1      = n;

        if (linkify_pmatch(out, n, "http://")) {
            n += 7;
            isUrl = true;
            href  = "";
        } else if (linkify_pmatch(out, n, "https://")) {
            n += 8;
            isUrl = true;
            href  = "";
        } else if (linkify_pmatch(out, n, "ftp://")) {
            n += 6;
            isUrl = true;
            href  = "";
        } else if (linkify_pmatch(out, n, "news://")) {
            n += 7;
            isUrl = true;
            href  = "";
        } else if (linkify_pmatch(out, n, "ed2k://")) {
            n += 7;
            isUrl = true;
            href  = "";
        } else if (linkify_pmatch(out, n, "www.")) {
            isUrl = true;
            href  = "http://";
        } else if (linkify_pmatch(out, n, "ftp.")) {
            isUrl = true;
            href  = "ftp://";
        } else if (linkify_pmatch(out, n, "@")) {
            isEmail = true;
            href    = "mailto:";
        }

        if (isUrl) {
            // make sure the previous char is not alphanumeric
            if (x1 > 0 && out.at(x1 - 1).isLetterOrNumber())
                continue;

            // find whitespace (or end)
            for (x2 = n; x2 < (int)out.length(); ++x2) {
                if (out.at(x2).isSpace() || out.at(x2) == '<')
                    break;
            }
            int     len = x2 - x1;
            QString pre = resolveEntities(out.mid(x1, x2 - x1));

            // go backward hacking off unwanted punctuation
            int cutoff;
            for (cutoff = pre.length() - 1; cutoff >= 0; --cutoff) {
                if (!linkify_isOneOf(pre.at(cutoff), "!?,.()[]{}<>\""))
                    break;
            }
            ++cutoff;
            //++x2;

            link = pre.mid(0, cutoff);
            if (!linkify_okUrl(link)) {
                n = x1 + link.length();
                continue;
            }
            href += link;
            href = linkify_htmlsafe(href);
            // printf("link: [%s], href=[%s]\n", link.latin1(), href.latin1());
            linked = QString("<a href=\"%1\">").arg(href) + Qt::escape(link) + "</a>" + Qt::escape(pre.mid(cutoff));
            out.replace(x1, len, linked);
            n = x1 + linked.length() - 1;
        } else if (isEmail) {
            // go backward till we find the beginning
            if (x1 == 0)
                continue;
            --x1;
            for (; x1 >= 0; --x1) {
                if (!linkify_isOneOf(out.at(x1), "_.-") && !out.at(x1).isLetterOrNumber())
                    break;
            }
            ++x1;

            // go forward till we find the end
            x2 = n + 1;
            for (; x2 < (int)out.length(); ++x2) {
                if (!linkify_isOneOf(out.at(x2), "_.-") && !out.at(x2).isLetterOrNumber())
                    break;
            }

            int len = x2 - x1;
            link    = out.mid(x1, len);
            // link = resolveEntities(link);

            if (!linkify_okEmail(link)) {
                n = x1 + link.length();
                continue;
            }

            href += link;
            // printf("link: [%s], href=[%s]\n", link.latin1(), href.latin1());
            linked = QString("<a href=\"%1\">").arg(href) + link + "</a>";
            out.replace(x1, len, linked);
            n = x1 + linked.length() - 1;
        }
    }
    newUrl = out;
    qWarning("string parsed");
    if (isUrl) {
        qWarning(qPrintable(QString("it's a url %1").arg(newUrl)));
        newUrl = QString("&lt;%1> <a href='%2'>%2</a><br />").arg(fromJid).arg(newUrl);
        viewerText_->append(newUrl);
    }

    return false;
}

#include "urlwatcherplugin.moc"
