/*
  Copyright (c) 2008 Laurent Montel <montel@kde.org>
  Copyright (C) 2006 Daniele Galdi <daniele.galdi@gmail.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.
*/

/* Project related */
#include "adblock.h"
#include "adblockdialog.h"

/* Kde related */
#include <kpluginfactory.h>
#include <kdebug.h>
#include <kiconloader.h>
#include <klibloader.h>
#include <kparts/statusbarextension.h>
#include <khtml_part.h>
#include <khtml_settings.h>
#include <kstatusbar.h>
#include <kurllabel.h>
#include <kurl.h>
#include <kconfig.h>
#include <kmessagebox.h>
#include <kcmultidialog.h>
#include <klocale.h>
#include <KActionCollection>
#include <KActionMenu>
#include <KMenu>
#include <dom/html_document.h>
#include <dom/html_image.h>
#include <dom/html_inline.h>
#include <dom/html_misc.h>
#include <dom/html_element.h>
#include <dom/dom_doc.h>

using namespace DOM;

#include <qpixmap.h>
#include <qcursor.h>
#include <qregexp.h>

K_PLUGIN_FACTORY(AdBlockFactory, registerPlugin< AdBlock >();)
K_EXPORT_PLUGIN(AdBlockFactory("adblock"))

AdBlock::AdBlock(QObject *parent, const QVariantList & /*args*/) :
    Plugin(parent),
    m_menu(0), m_elements(0)
{
    m_part = dynamic_cast<KHTMLPart *>(parent);
    if (!m_part) {
        kDebug() << "couldn't get KHTMLPart";
        return;
    }
    m_menu = new KActionMenu(KIcon("preferences-web-browser-adblock"), i18n("Adblock"),
                             actionCollection());
    actionCollection()->addAction("action adblock", m_menu);
    m_menu->setDelayed(false);

    QAction *a = actionCollection()->addAction("show_elements");
    a->setText(i18n("Show Blockable Elements..."));
    connect(a, SIGNAL(triggered()), this, SLOT(slotConfigure()));
    m_menu->addAction(a);

    a = actionCollection()->addAction("configure");
    a->setText(i18n("Configure Filters..."));
    connect(a, SIGNAL(triggered()), this, SLOT(showKCModule()));
    m_menu->addAction(a);

    a = actionCollection()->addAction("separator");
    a->setSeparator(true);
    m_menu->addAction(a);

    a = actionCollection()->addAction("disable_for_this_page");
    a->setText(i18n("No blocking for this page"));
    connect(a, SIGNAL(triggered()), this, SLOT(slotDisableForThisPage()));
    m_menu->addAction(a);

    a = actionCollection()->addAction("disable_for_this_site");
    a->setText(i18n("No blocking for this site"));
    connect(a, SIGNAL(triggered()), this, SLOT(slotDisableForThisSite()));
    m_menu->addAction(a);

    connect(m_part, SIGNAL(completed()), this, SLOT(initLabel()));
}

AdBlock::~AdBlock()
{
    KParts::StatusBarExtension *statusBarEx = KParts::StatusBarExtension::childObject(m_part);

    if (statusBarEx && m_label) {
        statusBarEx->removeStatusBarItem(m_label.data());
    }
    delete m_label.data();
    m_label.clear();
    delete m_menu;
    m_menu = 0;
    delete m_elements;
    m_elements = 0;
}

void AdBlock::initLabel()
{
    if (m_label) {
        return;
    }

    KParts::StatusBarExtension *statusBarEx = KParts::StatusBarExtension::childObject(m_part);

    if (!statusBarEx) {
        kDebug() << "couldn't get KParts::StatusBarExtension";
        return;
    }

    KUrlLabel *label = new KUrlLabel(statusBarEx->statusBar());

    KIconLoader *loader = KIconLoader::global();

    label->setFixedHeight(loader->currentSize(KIconLoader::Small));
    label->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
    label->setUseCursor(false);
    label->setPixmap(loader->loadIcon("preferences-web-browser-adblock", KIconLoader::Small));

    statusBarEx->addStatusBarItem(label, 0, false);
    connect(label, SIGNAL(leftClickedUrl()), this, SLOT(slotConfigure()));
    connect(label, SIGNAL(rightClickedUrl()), this, SLOT(contextMenu()));

    m_label = label;
}

void AdBlock::disableForUrl(KUrl url)
{
    url.setQuery(QString());
    url.setRef(QString());

    KHTMLSettings *settings = const_cast<KHTMLSettings *>(m_part->settings());
    settings->addAdFilter("@@" + url.url());
}

void AdBlock::slotDisableForThisPage()
{
    disableForUrl(m_part->toplevelURL().url());
}

void AdBlock::slotDisableForThisSite()
{
    KUrl u(m_part->toplevelURL().url());
    u.setPath("/*");
    disableForUrl(u);
}

void AdBlock::slotConfigure()
{
    if (!m_part->settings()->isAdFilterEnabled()) {
        KMessageBox::error(0,
                           i18n("Please enable Konqueror's Adblock"),
                           i18nc("@title:window", "Adblock disabled"));

        return;
    }

    m_elements = new AdElementList;
    fillBlockableElements();

    AdBlockDlg *dlg = new AdBlockDlg(m_part->widget(), m_elements, m_part);
    connect(dlg, SIGNAL(notEmptyFilter(QString)), this, SLOT(addAdFilter(QString)));
    connect(dlg, SIGNAL(configureFilters()), this, SLOT(showKCModule()));
    dlg->exec();
    delete dlg;
}

void AdBlock::showKCModule()
{
    KCMultiDialog *dialogue = new KCMultiDialog(m_part->widget());
    dialogue->addModule("khtml_filter");
    connect(dialogue, SIGNAL(cancelClicked()), dialogue, SLOT(delayedDestruct()));
    connect(dialogue, SIGNAL(closeClicked()), dialogue, SLOT(delayedDestruct()));
    dialogue->show();
}

void AdBlock::contextMenu()
{
    m_menu->menu()->exec(QCursor::pos());
}

void AdBlock::fillBlockableElements()
{
    fillWithHtmlTag("script", "src", i18n("script"));
    fillWithHtmlTag("embed", "src", i18n("object"));
    fillWithHtmlTag("object", "src", i18n("object"));
    // TODO: iframe's are not blocked by KHTML yet
    fillWithHtmlTag("iframe", "src", i18n("frame"));
    fillWithImages();

    updateFilters();
}

void AdBlock::fillWithImages()
{
    HTMLDocument htmlDoc = m_part->htmlDocument();

    HTMLCollection images = htmlDoc.images();

    for (unsigned int i = 0; i < images.length(); i++) {
        HTMLImageElement image = static_cast<HTMLImageElement>(images.item(i));

        DOMString src = image.src();

        QString url = htmlDoc.completeURL(src).string();
        if (!url.isEmpty() && url != m_part->baseURL().url()) {
            AdElement element(url, i18n("image"), "IMG", false, image);
            if (!m_elements->contains(element)) {
                m_elements->append(element);
            }
        }
    }
}

void AdBlock::fillWithHtmlTag(const DOMString &tagName,
                              const DOMString &attrName,
                              const QString &category)
{
    Document doc = m_part->document();

    NodeList nodes = doc.getElementsByTagName(tagName);

    for (unsigned int i = 0; i < nodes.length(); i++) {
        Node node = nodes.item(i);
        Node attr = node.attributes().getNamedItem(attrName);

        DOMString src = attr.nodeValue();
        if (src.isNull()) {
            continue;
        }

        QString url = doc.completeURL(src).string();
        if (!url.isEmpty() && url != m_part->baseURL().url()) {
            AdElement element(url, category, tagName.string().toUpper(), false, attr);
            if (!m_elements->contains(element)) {
                m_elements->append(element);
            }
        }
    }
}

void AdBlock::addAdFilter(const QString &url)
{
    //FIXME hackish
    KHTMLSettings *settings = const_cast<KHTMLSettings *>(m_part->settings());
    settings->addAdFilter(url);
    updateFilters();
}

void AdBlock::updateFilters()
{
    const KHTMLSettings *settings = m_part->settings();

    AdElementList::iterator it;
    for (it = m_elements->begin(); it != m_elements->end(); ++it) {
        AdElement &element = (*it);

        bool isWhitelist;
        QString filter = settings->adFilteredBy(element.url(), &isWhitelist);
        if (!filter.isEmpty()) {
            if (!isWhitelist) {
                element.setBlocked(true);
                element.setBlockedBy(i18n("Blocked by %1", filter));
            } else {
                element.setBlockedBy(i18n("Allowed by %1", filter));
            }
        }
    }
}

// ----------------------------------------------------------------------------

AdElement::AdElement() :
    m_blocked(false) {}

AdElement::AdElement(const QString &url, const QString &category,
                     const QString &type, bool blocked, const DOM::Node &node) :
    m_url(url), m_category(category), m_type(type), m_blocked(blocked), m_node(node) {}

AdElement &AdElement::operator=(const AdElement &obj)
{
    m_blocked = obj.m_blocked;
    m_blockedBy = obj.m_blockedBy;
    m_url = obj.m_url;
    m_category = obj.m_category;
    m_type = obj.m_type;
    m_node = obj.m_node;

    return *this;
}

bool AdElement::operator==(const AdElement &obj)
{
    return m_url == obj.m_url;
}

bool AdElement::isBlocked() const
{
    return m_blocked;
}

QString AdElement::blockedBy() const
{
    return m_blockedBy;
}

void AdElement::setBlockedBy(const QString &by)
{
    m_blockedBy = by;
}

void AdElement::setBlocked(bool blocked)
{
    m_blocked = blocked;
}

QString AdElement::url() const
{
    return m_url;
}

QString AdElement::category() const
{
    return m_category;
}

QString AdElement::type() const
{
    return m_type;
}

DOM::Node AdElement::node() const
{
    return m_node;
}

#include "adblock.moc"
