/*
 * Copyright (C) 2010 Google 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:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * 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.
 *     * Neither the name of Google Inc. 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 THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT
 * OWNER OR 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 "WebScrollbarImpl.h"

#include "GraphicsContext.h"
#include "KeyboardCodes.h"
#include "PlatformContextSkia.h"
#include "Scrollbar.h"
#include "ScrollbarTheme.h"
#include "ScrollTypes.h"
#include "WebCanvas.h"
#include "WebInputEvent.h"
#include "WebInputEventConversion.h"
#include "WebRect.h"
#include "WebScrollbarClient.h"
#include "WebVector.h"
#include "WebViewImpl.h"

using namespace std;
using namespace WebCore;

namespace WebKit {

WebScrollbar* WebScrollbar::create(WebScrollbarClient* client, Orientation orientation)
{
    return new WebScrollbarImpl(client, orientation);
}

int WebScrollbar::defaultThickness()
{
    return ScrollbarTheme::nativeTheme()->scrollbarThickness();
}

WebScrollbarImpl::WebScrollbarImpl(WebScrollbarClient* client, Orientation orientation)
    : m_client(client)
{
    m_scrollbar = Scrollbar::createNativeScrollbar(
        static_cast<ScrollbarClient*>(this),
        static_cast<ScrollbarOrientation>(orientation),
        RegularScrollbar);
}

WebScrollbarImpl::~WebScrollbarImpl()
{
}

void WebScrollbarImpl::setLocation(const WebRect& rect)
{
    WebCore::IntRect oldRect = m_scrollbar->frameRect();
    m_scrollbar->setFrameRect(rect);
    if (WebRect(oldRect) != rect)
      m_scrollbar->invalidate();

    int length = m_scrollbar->orientation() == HorizontalScrollbar ? m_scrollbar->width() : m_scrollbar->height();
    int pageStep = max(max<int>(length * Scrollbar::minFractionToStepWhenPaging(), length - Scrollbar::maxOverlapBetweenPages()), 1);
    m_scrollbar->setSteps(Scrollbar::pixelsPerLineStep(), pageStep);
    m_scrollbar->setEnabled(m_scrollbar->totalSize() > length);
    m_scrollbar->setProportion(length, m_scrollbar->totalSize());
}

int WebScrollbarImpl::value() const
{
    return m_scrollbar->value();
}

void WebScrollbarImpl::setValue(int position)
{
    m_scrollbar->setValue(position);
}

void WebScrollbarImpl::setDocumentSize(int size)
{
    int length = m_scrollbar->orientation() == HorizontalScrollbar ? m_scrollbar->width() : m_scrollbar->height();
    m_scrollbar->setEnabled(size > length);
    m_scrollbar->setProportion(length, size);
}

void WebScrollbarImpl::scroll(ScrollDirection direction, ScrollGranularity granularity, float multiplier)
{
    WebCore::ScrollDirection dir;
    bool horizontal = m_scrollbar->orientation() == HorizontalScrollbar;
    if (direction == ScrollForward)
        dir = horizontal ? ScrollRight : ScrollDown;
    else
        dir = horizontal ? ScrollLeft : ScrollUp;
    m_scrollbar->scroll(dir, static_cast<WebCore::ScrollGranularity>(granularity), multiplier);
}

void WebScrollbarImpl::paint(WebCanvas* canvas, const WebRect& rect)
{
#if WEBKIT_USING_CG
    GraphicsContext gc(canvas);
#elif WEBKIT_USING_SKIA
    PlatformContextSkia context(canvas);

    // PlatformGraphicsContext is actually a pointer to PlatformContextSkia
    GraphicsContext gc(reinterpret_cast<PlatformGraphicsContext*>(&context));
#else
    notImplemented();
#endif

    m_scrollbar->paint(&gc, rect);
}

bool WebScrollbarImpl::handleInputEvent(const WebInputEvent& event)
{
    switch (event.type) {
    case WebInputEvent::MouseDown:
        return onMouseDown(event);
    case WebInputEvent::MouseUp:
        return onMouseUp(event);
    case WebInputEvent::MouseMove:
        return onMouseMove(event);
    case WebInputEvent::MouseLeave:
        return onMouseLeave(event);
    case WebInputEvent::MouseWheel:
        return onMouseWheel(event);
    case WebInputEvent::KeyDown:
        return onKeyDown(event);
    case WebInputEvent::Undefined:
    case WebInputEvent::MouseEnter:
    case WebInputEvent::RawKeyDown:
    case WebInputEvent::KeyUp:
    case WebInputEvent::Char:
    case WebInputEvent::TouchStart:
    case WebInputEvent::TouchMove:
    case WebInputEvent::TouchEnd:
    case WebInputEvent::TouchCancel:
    default:
         break;
    }
    return false;
}

bool WebScrollbarImpl::onMouseDown(const WebInputEvent& event)
{
    WebMouseEvent mousedown = *static_cast<const WebMouseEvent*>(&event);
    if (!m_scrollbar->frameRect().contains(mousedown.x, mousedown.y))
        return false;

            mousedown.x -= m_scrollbar->x();
            mousedown.y -= m_scrollbar->y();
            m_scrollbar->mouseDown(PlatformMouseEventBuilder(m_scrollbar.get(), mousedown));
            return true;
        }

bool WebScrollbarImpl::onMouseUp(const WebInputEvent& event)
{
    if (m_scrollbar->pressedPart() == NoPart)
        return false;

    return m_scrollbar->mouseUp();
}

bool WebScrollbarImpl::onMouseMove(const WebInputEvent& event)
{
        WebMouseEvent mousemove = *static_cast<const WebMouseEvent*>(&event);
        if (m_scrollbar->frameRect().contains(mousemove.x, mousemove.y)
            || m_scrollbar->pressedPart() != NoPart) {
            mousemove.x -= m_scrollbar->x();
            mousemove.y -= m_scrollbar->y();
            return m_scrollbar->mouseMoved(PlatformMouseEventBuilder(m_scrollbar.get(), mousemove));
        }

        if (m_scrollbar->hoveredPart() != NoPart)
            m_scrollbar->mouseExited();
    return false;
}

bool WebScrollbarImpl::onMouseLeave(const WebInputEvent& event)
{
    if (m_scrollbar->hoveredPart() == NoPart)
        return false;

    return m_scrollbar->mouseExited();
}

bool WebScrollbarImpl::onMouseWheel(const WebInputEvent& event)
{
        // Same logic as in Scrollview.cpp.  If we can move at all, we'll accept the event.
        WebMouseWheelEvent mousewheel = *static_cast<const WebMouseWheelEvent*>(&event);
        int maxScrollDelta = m_scrollbar->maximum() - m_scrollbar->value();
        float delta = m_scrollbar->orientation() == HorizontalScrollbar ? mousewheel.deltaX : mousewheel.deltaY;
        if ((delta < 0 && maxScrollDelta > 0) || (delta > 0 && m_scrollbar->value() > 0)) {
            if (mousewheel.scrollByPage) {
                ASSERT(m_scrollbar->orientation() == VerticalScrollbar);
                bool negative = delta < 0;
                delta = max(max<int>(m_scrollbar->visibleSize() * Scrollbar::minFractionToStepWhenPaging(), m_scrollbar->visibleSize() - Scrollbar::maxOverlapBetweenPages()), 1);
                if (negative)
                    delta *= -1;
            }
            m_scrollbar->setValue(m_scrollbar->value() - delta);
            return true;
        }

    return false;
    }

bool WebScrollbarImpl::onKeyDown(const WebInputEvent& event)
{
        WebKeyboardEvent keyboard = *static_cast<const WebKeyboardEvent*>(&event);
        int keyCode;
        // We have to duplicate this logic from WebViewImpl because there it uses
        // Char and RawKeyDown events, which don't exist at this point.
        if (keyboard.windowsKeyCode == VKEY_SPACE)
            keyCode = ((keyboard.modifiers & WebInputEvent::ShiftKey) ? VKEY_PRIOR : VKEY_NEXT);
        else {
            if (keyboard.modifiers == WebInputEvent::ControlKey) {
                // Match FF behavior in the sense that Ctrl+home/end are the only Ctrl
                // key combinations which affect scrolling. Safari is buggy in the
                // sense that it scrolls the page for all Ctrl+scrolling key
                // combinations. For e.g. Ctrl+pgup/pgdn/up/down, etc.
                switch (keyboard.windowsKeyCode) {
                case VKEY_HOME:
                case VKEY_END:
                    break;
                default:
                    return false;
                }
            }

            if (keyboard.isSystemKey || (keyboard.modifiers & WebInputEvent::ShiftKey))
                return false;

            keyCode = keyboard.windowsKeyCode;
        }
        WebCore::ScrollDirection scrollDirection;
        WebCore::ScrollGranularity scrollGranularity;
        if (WebViewImpl::mapKeyCodeForScroll(keyCode, &scrollDirection, &scrollGranularity)) {
            // Will return false if scroll direction wasn't compatible with this scrollbar.
            return m_scrollbar->scroll(scrollDirection, scrollGranularity);
        }
    return false;
}

void WebScrollbarImpl::valueChanged(WebCore::Scrollbar*)
{
    m_client->valueChanged(this);
}

void WebScrollbarImpl::invalidateScrollbarRect(WebCore::Scrollbar*, const WebCore::IntRect& rect)
{
    WebRect webrect(rect);
    webrect.x += m_scrollbar->x();
    webrect.y += m_scrollbar->y();
    m_client->invalidateScrollbarRect(this, webrect);
}

bool WebScrollbarImpl::isActive() const
{
    return true;
}

bool WebScrollbarImpl::scrollbarCornerPresent() const
{
    return false;
}

void WebScrollbarImpl::getTickmarks(Vector<WebCore::IntRect>& tickmarks) const
{
    WebVector<WebRect> ticks;
    m_client->getTickmarks(const_cast<WebScrollbarImpl*>(this), &ticks);
    tickmarks.resize(ticks.size());
    for (size_t i = 0; i < ticks.size(); ++i)
        tickmarks[i] = ticks[i];
}

} // namespace WebKit
