/*
    Copyright (C) 2007 Trolltech ASA
    Copyright (C) 2007 Staikos Computing Services Inc.
    Copyright (C) 2007 Apple Inc.

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library 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
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to
    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
    Boston, MA 02110-1301, USA.

    This class provides all functionality needed for loading images, style sheets and html
    pages from the web. It has a memory cache for these objects.
*/

#include "qwebpage.h"
#include "qwebframe.h"
#include "qwebpage_p.h"
#include "qwebframe_p.h"
#include "qwebnetworkinterface.h"
#include "qwebpagehistory.h"
#include "qwebpagehistory_p.h"
#include "qwebsettings.h"

#include "Frame.h"
#include "ChromeClientQt.h"
#include "ContextMenuClientQt.h"
#include "DragClientQt.h"
#include "DragController.h"
#include "DragData.h"
#include "EditorClientQt.h"
#include "Settings.h"
#include "Page.h"
#include "FrameLoader.h"
#include "KURL.h"
#include "Image.h"
#include "IconDatabase.h"
#include "InspectorClientQt.h"
#include "FocusController.h"
#include "Editor.h"
#include "PlatformScrollBar.h"
#include "PlatformKeyboardEvent.h"
#include "ProgressTracker.h"

#include <QDebug>
#include <QDragEnterEvent>
#include <QDragLeaveEvent>
#include <QDragMoveEvent>
#include <QDropEvent>
#include <QFileDialog>
#include <QHttpRequestHeader>
#include <QInputDialog>
#include <QMessageBox>
#include <QNetworkProxy>
#include <QUndoStack>
#include <QUrl>
#include <QPainter>

using namespace WebCore;

QWebPagePrivate::QWebPagePrivate(QWebPage *qq)
    : q(qq), modified(false)
{
    q->setMouseTracking(true);
    q->setFocusPolicy(Qt::ClickFocus);
    chromeClient = new ChromeClientQt(q);
    contextMenuClient = new ContextMenuClientQt();
    editorClient = new EditorClientQt(q);
    page = new Page(chromeClient, contextMenuClient, editorClient,
                    new DragClientQt(q), new InspectorClientQt());

    undoStack = 0;
    mainFrame = 0;
    networkInterface = 0;
    insideOpenCall = false;
}

QWebPagePrivate::~QWebPagePrivate()
{
    delete undoStack;
    delete page;
}

QWebPage::NavigationRequestResponse QWebPagePrivate::navigationRequested(QWebFrame *frame, const QWebNetworkRequest &request, QWebPage::NavigationType type)
{
    if (insideOpenCall
        && frame == mainFrame)
        return QWebPage::AcceptNavigationRequest;
    return q->navigationRequested(frame, request, type);
}

void QWebPagePrivate::createMainFrame()
{
    if (!mainFrame) {
        QWebFrameData frameData;
        frameData.ownerElement = 0;
        frameData.allowsScrolling = true;
        frameData.marginWidth = 0;
        frameData.marginHeight = 0;
        mainFrame = q->createFrame(0, &frameData);
        QObject::connect(mainFrame, SIGNAL(titleChanged(const QString&)),
                q, SIGNAL(titleChanged(const QString&)));
        QObject::connect(mainFrame, SIGNAL(hoveringOverLink(const QString&, const QString&)),
                q, SIGNAL(hoveringOverLink(const QString&, const QString&)));
        
        mainFrame->d->frameView->setFrameGeometry(q->geometry());
    }
}


QWebPage::QWebPage(QWidget *parent)
    : QWidget(parent)
    , d(new QWebPagePrivate(this))
{
    setSettings(QWebSettings::global());
    QPalette pal = palette();
    pal.setBrush(QPalette::Background, Qt::white);

    setAttribute(Qt::WA_OpaquePaintEvent);

    setPalette(pal);
    setAcceptDrops(true);
    connect(this, SIGNAL(loadProgressChanged(int)), this, SLOT(onLoadProgressChanged(int)));
}

QWebPage::~QWebPage()
{
    FrameLoader *loader = d->mainFrame->d->frame->loader();
    if (loader)
        loader->detachFromParent();
    delete d;
}

QWebFrame *QWebPage::createFrame(QWebFrame *parentFrame, QWebFrameData *frameData)
{
    if (parentFrame)
        return new QWebFrame(parentFrame, frameData);
    QWebFrame *f = new QWebFrame(this, frameData);
    return f;
}

void QWebPage::open(const QUrl &url)
{
    open(QWebNetworkRequest(url));
}

void QWebPage::open(const QWebNetworkRequest &req)
{
    d->insideOpenCall = true;

    QUrl url = req.url();
    QHttpRequestHeader httpHeader = req.httpHeader();
    QByteArray postData = req.postData();

    WebCore::ResourceRequest request(KURL(url.toString()));

    QString method = httpHeader.method();
    if (!method.isEmpty())
        request.setHTTPMethod(method);

    QList<QPair<QString, QString> > values = httpHeader.values();
    for (int i = 0; i < values.size(); ++i) {
        const QPair<QString, QString> &val = values.at(i);
        request.addHTTPHeaderField(val.first, val.second);
    }

    if (!postData.isEmpty()) {
        WTF::RefPtr<WebCore::FormData> formData = new WebCore::FormData(postData.constData(), postData.size());
        request.setHTTPBody(formData);
    }

    mainFrame()->d->frame->loader()->load(request);
    d->insideOpenCall = false;
}

QUrl QWebPage::url() const
{
    return QUrl((QString)mainFrame()->d->frame->loader()->url().url());
}

QString QWebPage::title() const
{
    return mainFrame()->title();
}

QWebFrame *QWebPage::mainFrame() const
{
    d->createMainFrame();
    return d->mainFrame;
}

QSize QWebPage::sizeHint() const
{
    return QSize(800, 600);
}

void QWebPage::stop()
{
    FrameLoader *f = mainFrame()->d->frame->loader();
    f->stopForUserCancel();
}

QWebPageHistory QWebPage::history() const
{
    WebCore::BackForwardList *lst = d->page->backForwardList();
    QWebPageHistoryPrivate *priv = new QWebPageHistoryPrivate(lst);
    return QWebPageHistory(priv);
}

void QWebPage::goBack()
{
    d->page->goBack();
}

void QWebPage::goForward()
{
    d->page->goForward();
}

void QWebPage::goToHistoryItem(const QWebHistoryItem &item)
{
    d->page->goToItem(item.d->item, FrameLoadTypeIndexedBackForward);
}

void QWebPage::javaScriptConsoleMessage(const QString& message, unsigned int lineNumber, const QString& sourceID)
{
}

void QWebPage::javaScriptAlert(QWebFrame *frame, const QString& msg)
{
    //FIXME frame pos...
    QMessageBox::information(this, title(), msg, QMessageBox::Ok);
}

bool QWebPage::javaScriptConfirm(QWebFrame *frame, const QString& msg)
{
    //FIXME frame pos...
    return 0 == QMessageBox::information(this, title(), msg, QMessageBox::Yes, QMessageBox::No);
}

bool QWebPage::javaScriptPrompt(QWebFrame *frame, const QString& msg, const QString& defaultValue, QString* result)
{
    //FIXME frame pos...
    bool ok = false;
#ifndef QT_NO_INPUTDIALOG
    QString x = QInputDialog::getText(this, title(), msg, QLineEdit::Normal, defaultValue, &ok);
    if (ok && result) {
        *result = x;
    }
#endif
    return ok;
}

QWebPage *QWebPage::createWindow()
{
    return 0;
}

QWebPage *QWebPage::createModalDialog()
{
    return 0;
}

QObject *QWebPage::createPlugin(const QString &classid, const QUrl &url, const QStringList &paramNames, const QStringList &paramValues)
{
    Q_UNUSED(classid)
    Q_UNUSED(url)
    Q_UNUSED(paramNames)
    Q_UNUSED(paramValues)
    return 0;
}

QWebPage::NavigationRequestResponse QWebPage::navigationRequested(QWebFrame *frame, const QWebNetworkRequest &request, QWebPage::NavigationType type)
{
    Q_UNUSED(request)
    return AcceptNavigationRequest;
}

void QWebPage::setWindowGeometry(const QRect& geom)
{
    Q_UNUSED(geom)
}

bool QWebPage::canCut() const
{
    return d->page->focusController()->focusedOrMainFrame()->editor()->canCut();
}

bool QWebPage::canCopy() const
{
    return d->page->focusController()->focusedOrMainFrame()->editor()->canCopy();
}

bool QWebPage::canPaste() const
{
    return d->page->focusController()->focusedOrMainFrame()->editor()->canPaste();
}

void QWebPage::cut()
{
    d->page->focusController()->focusedOrMainFrame()->editor()->cut();
}

void QWebPage::copy()
{
    d->page->focusController()->focusedOrMainFrame()->editor()->copy();
}

void QWebPage::paste()
{
    d->page->focusController()->focusedOrMainFrame()->editor()->paste();
}

/*!
  Returns true if the page contains unsubmitted form data.
*/
bool QWebPage::isModified() const
{
    return d->modified;
}


QUndoStack *QWebPage::undoStack()
{
    if (!d->undoStack)
        d->undoStack = new QUndoStack(this);

    return d->undoStack;
}

static inline DragOperation dropActionToDragOp(Qt::DropActions actions)
{
    unsigned result = 0;
    if (actions & Qt::CopyAction)
        result |= DragOperationCopy;
    if (actions & Qt::MoveAction)
        result |= DragOperationMove;
    if (actions & Qt::LinkAction)
        result |= DragOperationLink;
    return (DragOperation)result;    
}

static inline Qt::DropAction dragOpToDropAction(unsigned actions)
{
    Qt::DropAction result = Qt::IgnoreAction;
    if (actions & DragOperationCopy)
        result = Qt::CopyAction;
    else if (actions & DragOperationMove)
        result = Qt::MoveAction;
    else if (actions & DragOperationLink)
        result = Qt::LinkAction;
    return result;    
}

void QWebPage::resizeEvent(QResizeEvent *e)
{
    QWidget::resizeEvent(e);
    if (mainFrame()->d->frame && mainFrame()->d->frameView) {
        mainFrame()->d->frameView->setFrameGeometry(rect());
        mainFrame()->d->frame->forceLayout();
        mainFrame()->d->frame->view()->adjustViewSize();
    }
}

void QWebPage::paintEvent(QPaintEvent *ev)
{
#ifdef QWEBKIT_TIME_RENDERING
    QTime time;
    time.start();
#endif

    QPainter p(this);

    QVector<QRect> vector = ev->region().rects();
    if (!vector.isEmpty()) {
        for (int i = 0; i < vector.size(); ++i) {
            mainFrame()->render(&p, vector.at(i));
        }
    } else {
        mainFrame()->render(&p, ev->rect());
    }

#ifdef    QWEBKIT_TIME_RENDERING
    int elapsed = time.elapsed();
    qDebug()<<"paint event on "<<ev->region()<<", took to render =  "<<elapsed;
#endif
}

void QWebPage::mouseMoveEvent(QMouseEvent *ev)
{
    mainFrame()->mouseMoveEvent(ev);
}

void QWebPage::mousePressEvent(QMouseEvent *ev)
{
    mainFrame()->mousePressEvent(ev);
}

void QWebPage::mouseDoubleClickEvent(QMouseEvent *ev)
{
    mainFrame()->mouseDoubleClickEvent(ev);
}

void QWebPage::mouseReleaseEvent(QMouseEvent *ev)
{
    mainFrame()->mouseReleaseEvent(ev);
}

void QWebPage::wheelEvent(QWheelEvent *ev)
{
    mainFrame()->wheelEvent(ev);
    if (!ev->isAccepted())
        QWidget::wheelEvent(ev);
}

void QWebPage::keyPressEvent(QKeyEvent *ev)
{
    PlatformKeyboardEvent kevent(ev, false);

    if (!mainFrame()->d->eventHandler)
        return;

    bool handled = mainFrame()->d->eventHandler->keyEvent(kevent);
    if (handled) {
    } else {
        handled = true;
        PlatformScrollbar *h, *v;
        h = mainFrame()->d->horizontalScrollBar();
        v = mainFrame()->d->verticalScrollBar();

        if (ev == QKeySequence::MoveToNextPage) {
            if (v)
                v->setValue(v->value() + height());
        } else if (ev == QKeySequence::MoveToPreviousPage) {
            if (v)
                v->setValue(v->value() - height());
        } else {
            switch (ev->key()) {
                case Qt::Key_Up:
                    if (v)
                        v->setValue(v->value() - 10);
                    break;
                case Qt::Key_Down:
                    if (v)
                        v->setValue(v->value() + 10);
                    break;
                case Qt::Key_Left:
                    if (h)
                        h->setValue(h->value() - 10);
                    break;
                case Qt::Key_Right:
                    if (h)
                        h->setValue(h->value() + 10);
                    break;
                default:
                    handled = false;
                    break;
            }
        }
    }

    ev->setAccepted(handled);
}

void QWebPage::keyReleaseEvent(QKeyEvent *ev)
{
    if (ev->isAutoRepeat()) {
        ev->setAccepted(true);
        return;
    }

    PlatformKeyboardEvent kevent(ev, true);

    if (!mainFrame()->d->eventHandler)
        return;

    bool handled = mainFrame()->d->eventHandler->keyEvent(kevent);
    ev->setAccepted(handled);
}

void QWebPage::focusInEvent(QFocusEvent *ev)
{
    if (ev->reason() != Qt::PopupFocusReason) {
        mainFrame()->d->frame->page()->focusController()->setFocusedFrame(mainFrame()->d->frame);
        mainFrame()->d->frame->setIsActive(true);
    }
    QWidget::focusInEvent(ev);
}

void QWebPage::focusOutEvent(QFocusEvent *ev)
{
    QWidget::focusOutEvent(ev);
    if (ev->reason() != Qt::PopupFocusReason) {
        mainFrame()->d->frame->selectionController()->clear();
        mainFrame()->d->frame->setIsActive(false);
    }
}

bool QWebPage::focusNextPrevChild(bool next)
{
    Q_UNUSED(next)
    return false;
}

void QWebPage::dragEnterEvent(QDragEnterEvent *ev)
{
#ifndef QT_NO_DRAGANDDROP
    DragData dragData(ev->mimeData(), ev->pos(), QCursor::pos(), 
                      dropActionToDragOp(ev->possibleActions()));
    Qt::DropAction action = dragOpToDropAction(d->page->dragController()->dragEntered(&dragData));
    ev->setDropAction(action);
    ev->accept();
#endif
}

void QWebPage::dragLeaveEvent(QDragLeaveEvent *ev)
{
#ifndef QT_NO_DRAGANDDROP
    DragData dragData(0, IntPoint(), QCursor::pos(), DragOperationNone);
    d->page->dragController()->dragExited(&dragData);
    ev->accept();
#endif
}

void QWebPage::dragMoveEvent(QDragMoveEvent *ev)
{
#ifndef QT_NO_DRAGANDDROP
    DragData dragData(ev->mimeData(), ev->pos(), QCursor::pos(), 
                      dropActionToDragOp(ev->possibleActions()));
    Qt::DropAction action = dragOpToDropAction(d->page->dragController()->dragUpdated(&dragData));
    ev->setDropAction(action);
    ev->accept();
#endif
}

void QWebPage::dropEvent(QDropEvent *ev)
{
#ifndef QT_NO_DRAGANDDROP
    DragData dragData(ev->mimeData(), ev->pos(), QCursor::pos(), 
                      dropActionToDragOp(ev->possibleActions()));
    Qt::DropAction action = dragOpToDropAction(d->page->dragController()->performDrag(&dragData));
    ev->accept();
#endif
}

void QWebPage::setNetworkInterface(QWebNetworkInterface *interface)
{
    d->networkInterface = interface;
}

QWebNetworkInterface *QWebPage::networkInterface() const
{
    if (d->networkInterface)
        return d->networkInterface;
    else
        return QWebNetworkInterface::defaultInterface();
}

QPixmap QWebPage::icon() const
{
    Image* image = iconDatabase()->iconForPageURL(url().toString(), IntSize(16, 16));
    if (!image || image->isNull()) {
        image = iconDatabase()->defaultIcon(IntSize(16, 16));
    }

    if (!image) {
        return QPixmap();
    }

    QPixmap *icon = image->getPixmap();
    if (!icon) {
        return QPixmap();
    }
    return *icon;
}

void QWebPage::setSettings(const QWebSettings &settings)
{
    WebCore::Settings *wSettings = d->page->settings();

    wSettings->setStandardFontFamily(
        settings.fontFamily(QWebSettings::StandardFont));
    wSettings->setFixedFontFamily(
        settings.fontFamily(QWebSettings::FixedFont));
    wSettings->setSerifFontFamily(
        settings.fontFamily(QWebSettings::SerifFont));
    wSettings->setSansSerifFontFamily(
        settings.fontFamily(QWebSettings::SansSerifFont));
    wSettings->setCursiveFontFamily(
        settings.fontFamily(QWebSettings::CursiveFont));
    wSettings->setFantasyFontFamily(
        settings.fontFamily(QWebSettings::FantasyFont));

    wSettings->setMinimumFontSize(settings.minimumFontSize());
    wSettings->setMinimumLogicalFontSize(settings.minimumLogicalFontSize());
    wSettings->setDefaultFontSize(settings.defaultFontSize());
    wSettings->setDefaultFixedFontSize(settings.defaultFixedFontSize());

    wSettings->setLoadsImagesAutomatically(
        settings.testAttribute(QWebSettings::AutoLoadImages));
    wSettings->setJavaScriptEnabled(
        settings.testAttribute(QWebSettings::JavascriptEnabled));
    wSettings->setJavaScriptCanOpenWindowsAutomatically(
        settings.testAttribute(QWebSettings::JavascriptCanOpenWindows));
    wSettings->setJavaEnabled(
        settings.testAttribute(QWebSettings::JavaEnabled));
    wSettings->setPluginsEnabled(
        settings.testAttribute(QWebSettings::PluginsEnabled));
    wSettings->setPrivateBrowsingEnabled(
        settings.testAttribute(QWebSettings::PrivateBrowsingEnabled));

    wSettings->setUserStyleSheetLocation(KURL(settings.userStyleSheetLocation()));

    // ### should be configurable
    wSettings->setDefaultTextEncodingName("iso-8859-1");
}

QWebSettings QWebPage::settings() const
{
    QWebSettings settings;
    WebCore::Settings *wSettings = d->page->settings();

    settings.setFontFamily(QWebSettings::StandardFont,
                           wSettings->standardFontFamily());
    settings.setFontFamily(QWebSettings::FixedFont,
                           wSettings->fixedFontFamily());
    settings.setFontFamily(QWebSettings::SerifFont,
                           wSettings->serifFontFamily());
    settings.setFontFamily(QWebSettings::SansSerifFont,
                           wSettings->sansSerifFontFamily());
    settings.setFontFamily(QWebSettings::CursiveFont,
                           wSettings->cursiveFontFamily());
    settings.setFontFamily(QWebSettings::FantasyFont,
                           wSettings->fantasyFontFamily());

    settings.setMinimumFontSize(wSettings->minimumFontSize());
    settings.setMinimumLogicalFontSize(wSettings->minimumLogicalFontSize());
    settings.setDefaultFontSize(wSettings->defaultFontSize());
    settings.setDefaultFixedFontSize(wSettings->defaultFixedFontSize());

    settings.setAttribute(QWebSettings::AutoLoadImages,
                          wSettings->loadsImagesAutomatically());
    settings.setAttribute(QWebSettings::JavascriptEnabled,
                          wSettings->isJavaScriptEnabled());
    settings.setAttribute(QWebSettings::JavascriptCanOpenWindows,
                          wSettings->JavaScriptCanOpenWindowsAutomatically());
    settings.setAttribute(QWebSettings::JavaEnabled,
                          wSettings->isJavaEnabled());
    settings.setAttribute(QWebSettings::PluginsEnabled,
                          wSettings->arePluginsEnabled());
    settings.setAttribute(QWebSettings::PrivateBrowsingEnabled,
                          wSettings->privateBrowsingEnabled());

    settings.setUserStyleSheetLocation(
        wSettings->userStyleSheetLocation().url());

    return settings;
}

QString QWebPage::chooseFile(QWebFrame *parentFrame, const QString& oldFile)
{
    //FIXME frame pos...
#ifndef QT_NO_FILEDIALOG
    return QFileDialog::getOpenFileName(this, QString::null, oldFile);
#else
    return QString::null;
#endif
}

#ifndef QT_NO_NETWORKPROXY
void QWebPage::setNetworkProxy(const QNetworkProxy& proxy)
{
    d->networkProxy = proxy;
}

QNetworkProxy QWebPage::networkProxy() const
{
    return d->networkProxy;
}
#endif

QString QWebPage::userAgentStringForUrl(const QUrl& forUrl) const {
    Q_UNUSED(forUrl)
    return QLatin1String("Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en) AppleWebKit/418.9.1 (KHTML, like Gecko) Safari/419.3 Qt");
}


void QWebPage::onLoadProgressChanged(int) {
    d->m_totalBytes = d->page->progress()->totalPageAndResourceBytesToLoad();
    d->m_bytesReceived = d->page->progress()->totalBytesReceived();
}


quint64 QWebPage::totalBytes() const {
    return d->m_bytesReceived;
}


quint64 QWebPage::bytesReceived() const {
    return d->m_totalBytes;
}
