/*
 * Copyright (C) 2010 Collabora Ltd.
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public License
 *  as published by the Free Software Foundation; either version 2.1 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
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free
 *  Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 *  Boston, MA 02110-1301 USA
 */

#include "config.h"
#include "RenderThemeClutter.h"

#include "CSSValueKeywords.h"
#include "FrameView.h"
#include "GraphicsContext.h"
#include "HostWindow.h"
#include "HTMLMediaElement.h"
#include "HTMLNames.h"
#include "MediaControlElements.h"
#include "NotImplemented.h"
#include "Page.h"
#include "PaintInfo.h"
#include "PlatformContextCairo.h"
#include "webkit/webkitwebpageprivate.h"
#include "webkit/webkitirendertheme.h"
#include "webkit/webkitiwebview.h"
#include <wtf/PassRefPtr.h>

namespace WebCore {

Color core(guint32 pixel)
{
    // In ClutterColor alpha represents transparency while in WebCore it represents opacity.
    ClutterColor color;
    clutter_color_from_pixel(&color, pixel);
    return Color(color);
}

PassRefPtr<RenderTheme> RenderTheme::themeForPage(Page* page)
{
    WebKitIRenderTheme* theme = 0;

    if (page && page->mainFrame() && page->mainFrame()->view() && page->mainFrame()->view()->hostWindow()) {
        WebKitIWebView* webView = WEBKIT_IWEB_VIEW(page->mainFrame()->view()->hostWindow()->platformPageClient());
        theme = webkit_iweb_view_get_render_theme(webView);
    }

    if (!theme)
        theme = webkit_irender_theme_get_default();

    return RenderThemeClutter::create(theme);
}

PassRefPtr<RenderTheme> RenderThemeClutter::create(WebKitIRenderTheme* theme)
{
    return adoptRef(new RenderThemeClutter(theme));
}

RenderThemeClutter::RenderThemeClutter(WebKitIRenderTheme* theme) :
        m_theme(theme)
{
    ASSERT(m_theme);
    updateMetrics();
}

RenderThemeClutter::~RenderThemeClutter()
{
}

void RenderThemeClutter::updateMetrics()
{
    WebKitIRenderThemeWidgetMetrics metrics;
    webkit_irender_theme_get_widget_metrics(m_theme, &metrics);
    m_caretBlinkInterval = metrics.blinkInterval;
    m_menulistButtonWidth = metrics.menulistButtonWidth;
    m_menulistButtonHeight = metrics.menulistButtonHeight;
}

Color RenderThemeClutter::platformActiveSelectionBackgroundColor() const
{
    return core(webkit_irender_theme_get_platform_color(m_theme, platformColorActiveSelectionBackground));
}

Color RenderThemeClutter::platformInactiveSelectionBackgroundColor() const
{
    return core(webkit_irender_theme_get_platform_color(m_theme, platformColorInactiveSelectionBackground));
}

Color RenderThemeClutter::platformActiveSelectionForegroundColor() const
{
    return core(webkit_irender_theme_get_platform_color(m_theme, platformColorActiveSelectionForeground));
}

Color RenderThemeClutter::platformInactiveSelectionForegroundColor() const
{
    return core(webkit_irender_theme_get_platform_color(m_theme, platformColorInactiveSelectionForeground));
}

Color RenderThemeClutter::activeListBoxSelectionBackgroundColor() const
{
    return core(webkit_irender_theme_get_platform_color(m_theme, platformColorActiveListBoxSelectionBackground));
}

Color RenderThemeClutter::activeListBoxSelectionForegroundColor() const
{
    return core(webkit_irender_theme_get_platform_color(m_theme, platformColorActiveListBoxSelectionForeground));
}

Color RenderThemeClutter::inactiveListBoxSelectionBackgroundColor() const
{
    return core(webkit_irender_theme_get_platform_color(m_theme, platformColorInactiveListBoxSelectionBackground));
}

Color RenderThemeClutter::inactiveListBoxSelectionForegroundColor() const
{
    return core(webkit_irender_theme_get_platform_color(m_theme, platformColorInactiveListBoxSelectionForeground));
}

void RenderThemeClutter::platformColorsDidChange()
{
    notImplemented();
}

void RenderThemeClutter::adjustSliderTrackStyle(StyleResolver*, RenderStyle* style, Element*) const
{
    style->setBoxShadow(nullptr);

    // FIXME: this is not right, GTK+ does not need to set values here, why
    // is out slider paint function not being called?
    style->setHeight(Length(14, Fixed));
    style->setWidth(Length(150, Fixed));
    style->setMinWidth(Length(50, Fixed));
}

void RenderThemeClutter::adjustSliderThumbStyle(StyleResolver* resolver, RenderStyle* style, Element* element) const
{
    RenderTheme::adjustSliderThumbStyle(resolver, style, element);

    // FIXME: see adjustSliderTrackStyle.
    style->setHeight(Length(10, Fixed));
    style->setWidth(Length(10, Fixed));
    style->setBoxShadow(nullptr);
}

void RenderThemeClutter::systemFont(int, FontDescription&) const
{
    notImplemented();
}

Color RenderThemeClutter::systemColor(int cssValueId) const
{
    return core(webkit_irender_theme_get_system_color(m_theme, cssValueId));
}

bool RenderThemeClutter::paintWidget(WebKitIRenderThemeWidgetType type,
                                     GraphicsContext* context,
                                     const IntRect& rect,
                                     WebKitIRenderThemeWidgetState* widgetState,
                                     int flags)
{
    // Painting is disabled so just claim to have succeeded
    if (context->paintingDisabled())
        return false;

    // FIXME: Cheat the type system to support cairo < 1.10. Remove
    // this when we are able to require cairo 1.10.
    cairo_rectangle_int_t paintRect(rect);
    ::cairo_rectangle_int_t hacktangle;

    hacktangle.x = paintRect.x;
    hacktangle.y = paintRect.y;
    hacktangle.width = paintRect.width;
    hacktangle.height = paintRect.height;

    bool success = webkit_irender_theme_paint_widget(m_theme, type, widgetState, flags, context->platformContext()->cr(), &hacktangle);

    return !success;
}

WebKitIRenderThemeWidgetState RenderThemeClutter::widgetStateFromRenderObject(WebKitIRenderThemeWidgetType type,
                                                                              RenderObject* renderObject)
{
    WebKitIRenderThemeWidgetState widgetState;
    widgetState.curpos = widgetState.maxpos = 0;

    widgetState.active = isPressed(renderObject);
    widgetState.focused = isFocused(renderObject);
    widgetState.inHover = isHovered(renderObject);

    // FIXME: Disabled does not always give the correct appearance for ReadOnly
    widgetState.disabled = !isEnabled(renderObject) || isReadOnlyControl(renderObject);
    widgetState.isDefault = false;
    widgetState.canDefault = false;

    // FIXME: The depressed value should probably apply for other theme parts too.
    // It must be used for range thumbs, because otherwise when the thumb is pressed,
    // the rendering is incorrect.
    if (type == widgetScaleThumbHorizontal || type == widgetScaleThumbVertical)
        widgetState.depressed = isPressed(renderObject);
    else
        widgetState.depressed = false;

    return widgetState;
}

bool RenderThemeClutter::paintRenderObject(WebKitIRenderThemeWidgetType type,
                                           RenderObject* renderObject,
                                           GraphicsContext* context,
                                           const IntRect& rect,
                                           int flags)
{
    // Painting is disabled so just claim to have succeeded
    if (context->paintingDisabled())
        return false;

    WebKitIRenderThemeWidgetState widgetState = widgetStateFromRenderObject(type, renderObject);
    return paintWidget(type, context, rect, &widgetState, flags);
}

bool RenderThemeClutter::paintButton(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{

    return paintRenderObject(widgetButton, renderObject, paintInfo.context, rect, 0);
}

bool RenderThemeClutter::paintCheckbox(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
    return paintRenderObject(widgetCheckButton, renderObject, paintInfo.context, rect, isChecked(renderObject));
}

bool RenderThemeClutter::paintRadio(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
    return paintRenderObject(widgetRadioButton, renderObject, paintInfo.context, rect, isChecked(renderObject));
}

bool RenderThemeClutter::paintInnerSpinButton(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
    if (paintInfo.context->paintingDisabled())
        return false;

    // Paint the background.
    paintRenderObject(widgetSpinButton, renderObject, paintInfo.context, rect, 0);

    // Now paint the buttons.
    ControlStates upStates = controlStatesForRenderer(renderObject);
    ControlStates downStates = upStates;
    if (upStates & SpinUpState)
        downStates &= ~(HoverState | PressedState);
    else
        upStates &= ~(HoverState | PressedState);

    WebKitIRenderThemeWidgetState widgetState = widgetStateFromRenderObject(widgetSpinButton, renderObject);
    widgetState.active = upStates & PressedState;
    widgetState.inHover = upStates & HoverState;

    IntRect half = rect;
    half.setHeight(rect.height() / 2);
    paintWidget(widgetSpinButtonUp, paintInfo.context, half, &widgetState, 0);

    widgetState.active = downStates & PressedState;
    widgetState.inHover = downStates & HoverState;

    half.setY(rect.y() + rect.height() / 2);
    paintWidget(widgetSpinButtonDown, paintInfo.context, half, &widgetState, 0);
    return false;
}

void RenderThemeClutter::adjustInnerSpinButtonStyle(StyleResolver*, RenderStyle* style, Element*) const
{
    style->setWidth(Length(m_menulistButtonWidth, Fixed));
    style->setMinWidth(Length(m_menulistButtonWidth, Fixed));
}

bool RenderThemeClutter::paintMenuList(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
    if (paintInfo.context->paintingDisabled())
        return false;

    paintRenderObject(widgetDropDown, renderObject, paintInfo.context, rect, 0);

    IntRect arrowRect = rect;
    arrowRect.setX(rect.x() + rect.width() - m_menulistButtonWidth);
    arrowRect.setWidth(m_menulistButtonWidth);
    return paintRenderObject(widgetDropDownArrow, renderObject, paintInfo.context, arrowRect, 0);
}

void RenderThemeClutter::adjustMenuListStyle(StyleResolver* resolver, RenderStyle* style, WebCore::Element* e) const
{
    // Height is locked to auto on all browsers.
    style->setLineHeight(RenderStyle::initialLineHeight());

    style->setColor(systemColor(CSSValueMenulistText));
}

bool RenderThemeClutter::paintMenuListButton(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
    return paintMenuList(renderObject, paintInfo, rect);
}

static void adjustSearchFieldIconStyle(RenderStyle* style)
{
    style->resetBorder();
    style->resetPadding();

    // Get the icon size based on the font size.
    int fontSize = style->fontSize();
    style->setWidth(Length(fontSize, Fixed));
    style->setHeight(Length(fontSize, Fixed));
}

bool RenderThemeClutter::paintSearchFieldIcon(WebKitIRenderThemeWidgetType type, RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
    if (paintInfo.context->paintingDisabled())
        return false;

    // Paint the background.
    paintRenderObject(type, renderObject, paintInfo.context, rect, 0);

    WebKitIRenderThemeWidgetState widgetState = widgetStateFromRenderObject(type, renderObject);
    paintWidget(type, paintInfo.context, rect, &widgetState, 0);

    return false;
}

void RenderThemeClutter::adjustSearchFieldResultsButtonStyle(StyleResolver* styleResolver, RenderStyle* style, Element* e) const
{
    adjustSearchFieldIconStyle(style);
}

bool RenderThemeClutter::paintSearchFieldResultsButton(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
    return paintSearchFieldIcon(widgetSearchFieldResultsButton, renderObject, paintInfo, rect);
}

void RenderThemeClutter::adjustSearchFieldResultsDecorationStyle(StyleResolver*, RenderStyle* style, Element*) const
{
    adjustSearchFieldIconStyle(style);
}

bool RenderThemeClutter::paintSearchFieldResultsDecoration(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
    return paintSearchFieldIcon(widgetSearchFieldDecoration, renderObject, paintInfo, rect);
}

void RenderThemeClutter::adjustSearchFieldCancelButtonStyle(StyleResolver*, RenderStyle* style, Element*) const
{
    adjustSearchFieldIconStyle(style);
}

bool RenderThemeClutter::paintSearchFieldCancelButton(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
    return paintSearchFieldIcon(widgetSearchFieldCancelButton, renderObject, paintInfo, rect);
}

void RenderThemeClutter::adjustSearchFieldStyle(StyleResolver*, RenderStyle* style, Element*) const
{
    // We cannot give a proper rendering when border radius is active, unfortunately.
    style->resetBorderRadius();
    style->setLineHeight(RenderStyle::initialLineHeight());
}

bool RenderThemeClutter::paintSearchField(RenderObject* o, const PaintInfo& i, const IntRect& rect)
{
    return paintTextField(o, i, rect);
}

void RenderThemeClutter::adjustMenuListButtonStyle(StyleResolver* resolver, RenderStyle* style, Element* e) const
{
    adjustMenuListStyle(resolver, style, e);
}

int RenderThemeClutter::popupInternalPaddingLeft(RenderStyle* style) const
{
    if (style->direction() == RTL)
        return 14;

    return 0;
}

int RenderThemeClutter::popupInternalPaddingRight(RenderStyle* style) const
{
    if (style->direction() == LTR)
        return 14;

    return 0;
}

int RenderThemeClutter::popupInternalPaddingTop(RenderStyle*) const
{
    return 0;
}

int RenderThemeClutter::popupInternalPaddingBottom(RenderStyle*) const
{
    return 0;
}

static void setToggleSize(RenderStyle* style)
{
    // The width and height are both specified, so we shouldn't change them.
    if (!style->width().isIntrinsicOrAuto() && !style->height().isAuto())
        return;

    int length = style->fontSize();
    if (style->width().isIntrinsicOrAuto())
        style->setWidth(Length(length, Fixed));

    if (style->height().isAuto())
        style->setHeight(Length(length, Fixed));
}

void RenderThemeClutter::setCheckboxSize(RenderStyle* style) const
{
    setToggleSize(style);
}

void RenderThemeClutter::setRadioSize(RenderStyle* style) const
{
    setToggleSize(style);
}

void RenderThemeClutter::adjustSliderThumbSize(RenderObject* renderObject) const
{
    ControlPart part = renderObject->style()->appearance();
#if ENABLE(VIDEO)
    if (part == MediaSliderThumbPart || part == MediaVolumeSliderThumbPart) {
        adjustMediaSliderThumbSize(renderObject);
        return;
    }
#endif

    RenderStyle* style = renderObject->style();
    int sliderLength = 150;
    int sliderWidth = 14;

    if (part == SliderThumbHorizontalPart) {
        style->setWidth(Length(sliderLength, Fixed));
        style->setHeight(Length(sliderWidth, Fixed));
        return;
    }

    ASSERT(part == SliderThumbVerticalPart);
    style->setWidth(Length(sliderWidth, Fixed));
    style->setHeight(Length(sliderLength, Fixed));
}

bool RenderThemeClutter::paintSliderTrack(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
    return paintRenderObject(widgetMediaSliderTrack, renderObject, paintInfo.context, rect, 0);
}

bool RenderThemeClutter::paintSliderThumb(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
    return paintRenderObject(widgetMediaSliderThumb, renderObject, paintInfo.context, rect, 0);
}

#if ENABLE(VIDEO)
static HTMLMediaElement* getMediaElementFromRenderObject(RenderObject* o)
{
    Node* node = o->node();
    Node* mediaNode = node ? node->shadowHost() : 0;
    if (!mediaNode || (!mediaNode->hasTagName(HTMLNames::videoTag) && !mediaNode->hasTagName(HTMLNames::audioTag)))
        return 0;

    return static_cast<HTMLMediaElement*>(mediaNode);
}

void RenderThemeClutter::adjustMediaSliderThumbSize(RenderObject* renderObject) const
{
    ControlPart part = renderObject->style()->appearance();

    if (part == MediaSliderThumbPart) {
        renderObject->style()->setWidth(Length(13, Fixed));
        renderObject->style()->setHeight(Length(13, Fixed));
    }
}

bool RenderThemeClutter::paintMediaFullscreenButton(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
    return paintRenderObject(widgetMediaFullscreenButton, renderObject, paintInfo.context, rect, 0);
}

bool RenderThemeClutter::paintMediaMuteButton(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
    HTMLMediaElement* mediaElement = getMediaElementFromRenderObject(renderObject);
    if (!mediaElement)
        return false;

    return paintRenderObject(widgetMediaMuteButton, renderObject, paintInfo.context, rect, mediaElement->muted() ? mediaMuted : 0);
}

bool RenderThemeClutter::paintMediaPlayButton(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
    Node* node = renderObject->node();
    if (!node)
        return false;

    MediaControlPlayButtonElement* button = static_cast<MediaControlPlayButtonElement*>(node);
    return paintRenderObject(widgetMediaPlayButton, renderObject, paintInfo.context, rect, button->displayType() == MediaPlayButton ? 0 : mediaPlaying);
}

bool RenderThemeClutter::paintMediaSeekBackButton(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
    return paintRenderObject(widgetMediaSeekBackButton, renderObject, paintInfo.context, rect, 0);
}

bool RenderThemeClutter::paintMediaSeekForwardButton(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
    return paintRenderObject(widgetMediaSeekForwardButton, renderObject, paintInfo.context, rect, 0);
}

bool RenderThemeClutter::paintMediaSliderTrack(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
    return paintRenderObject(widgetMediaSliderTrack, renderObject, paintInfo.context, rect, 0);
}

bool RenderThemeClutter::paintMediaSliderThumb(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
{
    return paintRenderObject(widgetMediaSliderThumb, renderObject, paintInfo.context, rect, 0);
}

#endif

#if ENABLE(DATALIST_ELEMENT)
IntSize RenderThemeClutter::sliderTickSize() const
{
    // FIXME: We need to set this to the size of one tick mark.
    return IntSize(0, 0);
}

int RenderThemeClutter::sliderTickOffsetFromTrackCenter() const
{
    // FIXME: We need to set this to the position of the tick marks.
    return 0;
}
#endif

}

