/*
 * Copyright (C) 2005, 2006, 2007, 2008 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer. 
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution. 
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
#include "UIDelegate.h"

#include "DumpRenderTree.h"
#include "DraggingInfo.h"
#include "EventSender.h"
#include "LayoutTestController.h"
#include "DRTDesktopNotificationPresenter.h"

#include <WebCore/COMPtr.h>
#include <wtf/Platform.h>
#include <wtf/Vector.h>
#include <JavaScriptCore/Assertions.h>
#include <JavaScriptCore/JavaScriptCore.h>
#include <WebKit/WebKit.h>
#include <stdio.h>

using std::wstring;

class DRTUndoObject {
public:
    DRTUndoObject(IWebUndoTarget* target, BSTR actionName, IUnknown* obj)
        : m_target(target)
        , m_actionName(SysAllocString(actionName))
        , m_obj(obj)
    {
    }

    ~DRTUndoObject()
    {
        SysFreeString(m_actionName);
    }

    void invoke()
    {
        m_target->invoke(m_actionName, m_obj.get());
    }

private:
    IWebUndoTarget* m_target;
    BSTR m_actionName;
    COMPtr<IUnknown> m_obj;
};

class DRTUndoStack {
public:
    ~DRTUndoStack() { deleteAllValues(m_undoVector); }

    bool isEmpty() const { return m_undoVector.isEmpty(); }
    void clear() { deleteAllValues(m_undoVector); m_undoVector.clear(); }

    void push(DRTUndoObject* undoObject) { m_undoVector.append(undoObject); }
    DRTUndoObject* pop() { DRTUndoObject* top = m_undoVector.last(); m_undoVector.removeLast(); return top; }

private:
    Vector<DRTUndoObject*> m_undoVector;
};

class DRTUndoManager {
public:
    DRTUndoManager();

    void removeAllActions();
    void registerUndoWithTarget(IWebUndoTarget* target, BSTR actionName, IUnknown* obj);
    void redo();
    void undo();
    bool canRedo() { return !m_redoStack->isEmpty(); }
    bool canUndo() { return !m_undoStack->isEmpty(); }

private:
    OwnPtr<DRTUndoStack> m_redoStack;
    OwnPtr<DRTUndoStack> m_undoStack;
    bool m_isRedoing;
    bool m_isUndoing;
};

DRTUndoManager::DRTUndoManager()
    : m_redoStack(new DRTUndoStack)
    , m_undoStack(new DRTUndoStack)
    , m_isRedoing(false)
    , m_isUndoing(false)
{
}

void DRTUndoManager::removeAllActions()
{
    m_redoStack->clear();
    m_undoStack->clear();
}

void DRTUndoManager::registerUndoWithTarget(IWebUndoTarget* target, BSTR actionName, IUnknown* obj)
{
    if (!m_isUndoing && !m_isRedoing)
        m_redoStack->clear();

    DRTUndoStack* stack = m_isUndoing ? m_redoStack.get() : m_undoStack.get();
    stack->push(new DRTUndoObject(target, actionName, obj));
}

void DRTUndoManager::redo()
{
    if (!canRedo())
        return;

    m_isRedoing = true;

    DRTUndoObject* redoObject = m_redoStack->pop();
    redoObject->invoke();
    delete redoObject;

    m_isRedoing = false;
}

void DRTUndoManager::undo()
{
    if (!canUndo())
        return;

    m_isUndoing = true;

    DRTUndoObject* undoObject = m_undoStack->pop();
    undoObject->invoke();
    delete undoObject;

    m_isUndoing = false;
}

UIDelegate::UIDelegate()
    : m_refCount(1)
    , m_undoManager(new DRTUndoManager)
    , m_desktopNotifications(new DRTDesktopNotificationPresenter)
{
    m_frame.bottom = 0;
    m_frame.top = 0;
    m_frame.left = 0;
    m_frame.right = 0;
}

void UIDelegate::resetUndoManager()
{
    m_undoManager.set(new DRTUndoManager);
}

HRESULT STDMETHODCALLTYPE UIDelegate::QueryInterface(REFIID riid, void** ppvObject)
{
    *ppvObject = 0;
    if (IsEqualGUID(riid, IID_IUnknown))
        *ppvObject = static_cast<IWebUIDelegate*>(this);
    else if (IsEqualGUID(riid, IID_IWebUIDelegate))
        *ppvObject = static_cast<IWebUIDelegate*>(this);
    else if (IsEqualGUID(riid, IID_IWebUIDelegate2))
        *ppvObject = static_cast<IWebUIDelegate2*>(this);
    else if (IsEqualGUID(riid, IID_IWebUIDelegatePrivate))
        *ppvObject = static_cast<IWebUIDelegatePrivate*>(this);
    else
        return E_NOINTERFACE;

    AddRef();
    return S_OK;
}

ULONG STDMETHODCALLTYPE UIDelegate::AddRef()
{
    return ++m_refCount;
}

ULONG STDMETHODCALLTYPE UIDelegate::Release()
{
    ULONG newRef = --m_refCount;
    if (!newRef)
        delete(this);

    return newRef;
}

HRESULT STDMETHODCALLTYPE UIDelegate::hasCustomMenuImplementation( 
        /* [retval][out] */ BOOL *hasCustomMenus)
{
    *hasCustomMenus = TRUE;

    return S_OK;
}

HRESULT STDMETHODCALLTYPE UIDelegate::trackCustomPopupMenu( 
        /* [in] */ IWebView *sender,
        /* [in] */ OLE_HANDLE menu,
        /* [in] */ LPPOINT point)
{
    // Do nothing
    return S_OK;
}

HRESULT STDMETHODCALLTYPE UIDelegate::registerUndoWithTarget(
        /* [in] */ IWebUndoTarget* target,
        /* [in] */ BSTR actionName,
        /* [in] */ IUnknown* actionArg)
{
    m_undoManager->registerUndoWithTarget(target, actionName, actionArg);
    return S_OK;
}

HRESULT STDMETHODCALLTYPE UIDelegate::removeAllActionsWithTarget(
        /* [in] */ IWebUndoTarget*)
{
    m_undoManager->removeAllActions();
    return S_OK;
}

HRESULT STDMETHODCALLTYPE UIDelegate::setActionTitle(
        /* [in] */ BSTR actionTitle)
{
    // It is not neccessary to implement this for DRT because there is
    // menu to write out the title to.
    return S_OK;
}

HRESULT STDMETHODCALLTYPE UIDelegate::undo()
{
    m_undoManager->undo();
    return S_OK;
}

HRESULT STDMETHODCALLTYPE UIDelegate::redo()
{
    m_undoManager->redo();
    return S_OK;
}

HRESULT STDMETHODCALLTYPE UIDelegate::canUndo(
        /* [retval][out] */ BOOL* result)
{
    if (!result)
        return E_POINTER;

    *result = m_undoManager->canUndo();
    return S_OK;
}

HRESULT STDMETHODCALLTYPE UIDelegate::canRedo(
        /* [retval][out] */ BOOL* result)
{
    if (!result)
        return E_POINTER;

    *result = m_undoManager->canRedo();
    return S_OK;
}

HRESULT STDMETHODCALLTYPE UIDelegate::printFrame( 
    /* [in] */ IWebView *webView,
    /* [in] */ IWebFrame *frame)
{
    return E_NOTIMPL;
}

HRESULT STDMETHODCALLTYPE UIDelegate::ftpDirectoryTemplatePath( 
    /* [in] */ IWebView *webView,
    /* [retval][out] */ BSTR *path)
{
    if (!path)
        return E_POINTER;
    *path = 0;
    return E_NOTIMPL;
}


HRESULT STDMETHODCALLTYPE UIDelegate::webViewHeaderHeight( 
    /* [in] */ IWebView *webView,
    /* [retval][out] */ float *result)
{
    if (!result)
        return E_POINTER;
    *result = 0;
    return E_NOTIMPL;
}

HRESULT STDMETHODCALLTYPE UIDelegate::webViewFooterHeight( 
    /* [in] */ IWebView *webView,
    /* [retval][out] */ float *result)
{
    if (!result)
        return E_POINTER;
    *result = 0;
    return E_NOTIMPL;
}

HRESULT STDMETHODCALLTYPE UIDelegate::drawHeaderInRect( 
    /* [in] */ IWebView *webView,
    /* [in] */ RECT *rect,
    /* [in] */ OLE_HANDLE drawingContext)
{
    return E_NOTIMPL;
}

HRESULT STDMETHODCALLTYPE UIDelegate::drawFooterInRect( 
    /* [in] */ IWebView *webView,
    /* [in] */ RECT *rect,
    /* [in] */ OLE_HANDLE drawingContext,
    /* [in] */ UINT pageIndex,
    /* [in] */ UINT pageCount)
{
    return E_NOTIMPL;
}

HRESULT STDMETHODCALLTYPE UIDelegate::webViewPrintingMarginRect( 
    /* [in] */ IWebView *webView,
    /* [retval][out] */ RECT *rect)
{
    return E_NOTIMPL;
}

HRESULT STDMETHODCALLTYPE UIDelegate::canRunModal( 
    /* [in] */ IWebView *webView,
    /* [retval][out] */ BOOL *canRunBoolean)
{
    return E_NOTIMPL;
}

HRESULT STDMETHODCALLTYPE UIDelegate::createModalDialog( 
    /* [in] */ IWebView *sender,
    /* [in] */ IWebURLRequest *request,
    /* [retval][out] */ IWebView **newWebView)
{
    return E_NOTIMPL;
}

HRESULT STDMETHODCALLTYPE UIDelegate::runModal( 
    /* [in] */ IWebView *webView)
{
    return E_NOTIMPL;
}

HRESULT STDMETHODCALLTYPE UIDelegate::isMenuBarVisible( 
    /* [in] */ IWebView *webView,
    /* [retval][out] */ BOOL *visible)
{
    if (!visible)
        return E_POINTER;
    *visible = false;
    return E_NOTIMPL;
}

HRESULT STDMETHODCALLTYPE UIDelegate::setMenuBarVisible( 
    /* [in] */ IWebView *webView,
    /* [in] */ BOOL visible)
{
    return E_NOTIMPL;
}

HRESULT STDMETHODCALLTYPE UIDelegate::runDatabaseSizeLimitPrompt( 
    /* [in] */ IWebView *webView,
    /* [in] */ BSTR displayName,
    /* [in] */ IWebFrame *initiatedByFrame,
    /* [retval][out] */ BOOL *allowed)
{
    if (!allowed)
        return E_POINTER;
    *allowed = false;
    return E_NOTIMPL;
}

HRESULT STDMETHODCALLTYPE UIDelegate::paintCustomScrollbar( 
    /* [in] */ IWebView *webView,
    /* [in] */ HDC hDC,
    /* [in] */ RECT rect,
    /* [in] */ WebScrollBarControlSize size,
    /* [in] */ WebScrollbarControlState state,
    /* [in] */ WebScrollbarControlPart pressedPart,
    /* [in] */ BOOL vertical,
    /* [in] */ float value,
    /* [in] */ float proportion,
    /* [in] */ WebScrollbarControlPartMask parts)
{
    return E_NOTIMPL;
}

HRESULT STDMETHODCALLTYPE UIDelegate::paintCustomScrollCorner( 
    /* [in] */ IWebView *webView,
    /* [in] */ HDC hDC,
    /* [in] */ RECT rect)
{
    return E_NOTIMPL;
}

HRESULT STDMETHODCALLTYPE UIDelegate::setFrame( 
        /* [in] */ IWebView* /*sender*/,
        /* [in] */ RECT* frame)
{
    m_frame = *frame;
    return S_OK;
}

HRESULT STDMETHODCALLTYPE UIDelegate::webViewFrame( 
        /* [in] */ IWebView* /*sender*/,
        /* [retval][out] */ RECT* frame)
{
    *frame = m_frame;
    return S_OK;
}

HRESULT STDMETHODCALLTYPE UIDelegate::runJavaScriptAlertPanelWithMessage( 
        /* [in] */ IWebView* /*sender*/,
        /* [in] */ BSTR message)
{
    printf("ALERT: %S\n", message ? message : L"");

    return S_OK;
}

HRESULT STDMETHODCALLTYPE UIDelegate::runJavaScriptConfirmPanelWithMessage( 
    /* [in] */ IWebView* sender,
    /* [in] */ BSTR message,
    /* [retval][out] */ BOOL* result)
{
    printf("CONFIRM: %S\n", message ? message : L"");
    *result = TRUE;

    return S_OK;
}

HRESULT STDMETHODCALLTYPE UIDelegate::runJavaScriptTextInputPanelWithPrompt( 
    /* [in] */ IWebView *sender,
    /* [in] */ BSTR message,
    /* [in] */ BSTR defaultText,
    /* [retval][out] */ BSTR *result)
{
    printf("PROMPT: %S, default text: %S\n", message ? message : L"", defaultText ? defaultText : L"");
    *result = SysAllocString(defaultText);

    return S_OK;
}

HRESULT STDMETHODCALLTYPE UIDelegate::runBeforeUnloadConfirmPanelWithMessage( 
    /* [in] */ IWebView* /*sender*/,
    /* [in] */ BSTR /*message*/,
    /* [in] */ IWebFrame* /*initiatedByFrame*/,
    /* [retval][out] */ BOOL* result)
{
    if (!result)
        return E_POINTER;
    *result = TRUE;
    return E_NOTIMPL;
}

HRESULT STDMETHODCALLTYPE UIDelegate::webViewAddMessageToConsole( 
    /* [in] */ IWebView* sender,
    /* [in] */ BSTR message,
    /* [in] */ int lineNumber,
    /* [in] */ BSTR url,
    /* [in] */ BOOL isError)
{
    wstring newMessage;
    if (message) {
        newMessage = message;
        size_t fileProtocol = newMessage.find(L"file://");
        if (fileProtocol != wstring::npos)
            newMessage = newMessage.substr(0, fileProtocol) + urlSuitableForTestResult(newMessage.substr(fileProtocol));
    }

    printf("CONSOLE MESSAGE: line %d: %S\n", lineNumber, newMessage.c_str());
    return S_OK;
}

HRESULT STDMETHODCALLTYPE UIDelegate::doDragDrop( 
    /* [in] */ IWebView* sender,
    /* [in] */ IDataObject* object,
    /* [in] */ IDropSource* source,
    /* [in] */ DWORD okEffect,
    /* [retval][out] */ DWORD* performedEffect)
{
    if (!performedEffect)
        return E_POINTER;

    *performedEffect = 0;

    draggingInfo = new DraggingInfo(object, source);
    HRESULT oleDragAndDropReturnValue = DRAGDROP_S_CANCEL;
    replaySavedEvents(&oleDragAndDropReturnValue);
    if (draggingInfo) {
        *performedEffect = draggingInfo->performedDropEffect();
        delete draggingInfo;
        draggingInfo = 0;
    }
    return oleDragAndDropReturnValue;
}

HRESULT STDMETHODCALLTYPE UIDelegate::webViewGetDlgCode( 
    /* [in] */ IWebView* /*sender*/,
    /* [in] */ UINT /*keyCode*/,
    /* [retval][out] */ LONG_PTR *code)
{
    if (!code)
        return E_POINTER;
    *code = 0;
    return E_NOTIMPL;
}

HRESULT STDMETHODCALLTYPE UIDelegate::createWebViewWithRequest( 
        /* [in] */ IWebView *sender,
        /* [in] */ IWebURLRequest *request,
        /* [retval][out] */ IWebView **newWebView)
{
    if (!::gLayoutTestController->canOpenWindows())
        return E_FAIL;
    *newWebView = createWebViewAndOffscreenWindow();
    return S_OK;
}

HRESULT STDMETHODCALLTYPE UIDelegate::webViewClose(
        /* [in] */ IWebView *sender)
{
    HWND hostWindow;
    sender->hostWindow(reinterpret_cast<OLE_HANDLE*>(&hostWindow));
    DestroyWindow(hostWindow);
    return S_OK;
}

HRESULT STDMETHODCALLTYPE UIDelegate::webViewFocus( 
        /* [in] */ IWebView *sender)
{
    HWND hostWindow;
    sender->hostWindow(reinterpret_cast<OLE_HANDLE*>(&hostWindow));
    SetForegroundWindow(hostWindow);
    return S_OK; 
}

HRESULT STDMETHODCALLTYPE UIDelegate::webViewUnfocus( 
        /* [in] */ IWebView *sender)
{
    SetForegroundWindow(GetDesktopWindow());
    return S_OK; 
}

HRESULT STDMETHODCALLTYPE UIDelegate::webViewPainted( 
        /* [in] */ IWebView *sender)
{
    return S_OK;
}

HRESULT STDMETHODCALLTYPE UIDelegate::exceededDatabaseQuota( 
        /* [in] */ IWebView *sender,
        /* [in] */ IWebFrame *frame,
        /* [in] */ IWebSecurityOrigin *origin,
        /* [in] */ BSTR databaseIdentifier)
{
    BSTR protocol;
    BSTR host;
    unsigned short port;

    origin->protocol(&protocol);
    origin->host(&host);
    origin->port(&port);

    if (!done && gLayoutTestController->dumpDatabaseCallbacks())
        printf("UI DELEGATE DATABASE CALLBACK: exceededDatabaseQuotaForSecurityOrigin:{%S, %S, %i} database:%S\n", protocol, host, port, databaseIdentifier);

    SysFreeString(protocol);
    SysFreeString(host);

    static const unsigned long long defaultQuota = 5 * 1024 * 1024;
    origin->setQuota(defaultQuota);

    return S_OK;
}

HRESULT STDMETHODCALLTYPE UIDelegate::embeddedViewWithArguments( 
    /* [in] */ IWebView *sender,
    /* [in] */ IWebFrame *frame,
    /* [in] */ IPropertyBag *arguments,
    /* [retval][out] */ IWebEmbeddedView **view)
{
    if (!view)
        return E_POINTER;
    *view = 0;
    return E_NOTIMPL;
}

HRESULT STDMETHODCALLTYPE UIDelegate::webViewClosing( 
    /* [in] */ IWebView *sender)
{
    return E_NOTIMPL;
}

HRESULT STDMETHODCALLTYPE UIDelegate::webViewSetCursor( 
    /* [in] */ IWebView *sender,
    /* [in] */ OLE_HANDLE cursor)
{
    return E_NOTIMPL;
}

HRESULT STDMETHODCALLTYPE UIDelegate::webViewDidInvalidate( 
    /* [in] */ IWebView *sender)
{
    return E_NOTIMPL;
}

HRESULT STDMETHODCALLTYPE UIDelegate::setStatusText(IWebView*, BSTR text)
{ 
    if (gLayoutTestController->dumpStatusCallbacks())
        printf("UI DELEGATE STATUS CALLBACK: setStatusText:%S\n", text ? text : L"");
    return S_OK;
}

HRESULT STDMETHODCALLTYPE UIDelegate::desktopNotificationsDelegate(IWebDesktopNotificationsDelegate** result)
{
    m_desktopNotifications.copyRefTo(result);
    return S_OK;
}
