/*
 * Copyright (C) 2007 Kevin Ollivier <kevino@theolliviers.com>
 *
 * 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.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``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 COMPUTER, INC. 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 "RenderTheme.h"

#include "Document.h"
#include "FrameView.h"
#include "GraphicsContext.h"
#include "HostWindow.h"
#include "NotImplemented.h"
#include "PaintInfo.h"
#include "RenderView.h"

#include <wx/defs.h>

#include <wx/dc.h>
#include <wx/dcgraph.h>
#include <wx/renderer.h>
#include <wx/dcclient.h>
#include <wx/scrolwin.h>
#include <wx/settings.h>

namespace WebCore {

IntRect getAdjustedRect(wxDC* dc, const IntRect& r)
{
    IntRect rect = r;
// On Mac, wxGraphicsContext and wxDC share the same native implementation,
// and so transformations are available.
// On Win and Linux, however, this is not true and transforms are lost,
// so we need to restore them here.
#if !wxCHECK_VERSION(2, 9, 2) && USE(WXGC) && !defined(__WXMAC__)
    LOG_ERROR("Rect is (%d, %d)\n", rect.x(), rect.y());
    double xtrans = 0;
    double ytrans = 0;
    
    wxGCDC* gcdc = static_cast<wxGCDC*>(dc);
    wxGraphicsContext* gc = gcdc->GetGraphicsContext();
    gc->GetTransform().TransformPoint(&xtrans, &ytrans);
    rect.setX(r.x() + (int)xtrans);
    rect.setY(r.y() + (int)ytrans);
    LOG_ERROR("Transform is (%f, %f), (%d, %d)\n", xtrans, ytrans, rect.x(), rect.y());
#endif

    return rect;
}

class RenderThemeWx : public RenderTheme {
private:
    RenderThemeWx() : RenderTheme() { }
    virtual ~RenderThemeWx();

public:
    static PassRefPtr<RenderTheme> create();

    // A method asking if the theme's controls actually care about redrawing when hovered.
    virtual bool supportsHover(const RenderStyle*) const { return true; }

    virtual bool paintCheckbox(RenderObject* o, const PaintInfo& i, const IntRect& r)
    {
        return paintButton(o, i, r);
    }
 
    virtual void setCheckboxSize(RenderStyle*) const;

    virtual bool paintRadio(RenderObject* o, const PaintInfo& i, const IntRect& r)
    {
        return paintButton(o, i, r);
    }

    virtual void setRadioSize(RenderStyle*) const;

    virtual void adjustRepaintRect(const RenderObject*, IntRect&);

    virtual void adjustButtonStyle(CSSStyleSelector*, RenderStyle*, Element*) const;
    virtual bool paintButton(RenderObject*, const PaintInfo&, const IntRect&);

    virtual void adjustTextFieldStyle(CSSStyleSelector*, RenderStyle*, Element*) const;
    virtual bool paintTextField(RenderObject*, const PaintInfo&, const IntRect&);

    virtual int minimumMenuListSize(RenderStyle*) const;

    virtual void adjustMenuListStyle(CSSStyleSelector*, RenderStyle*, Element*) const;
    virtual bool paintMenuList(RenderObject*, const PaintInfo&, const IntRect&);

    virtual void adjustMenuListButtonStyle(CSSStyleSelector*, RenderStyle*, Element*) const;
    virtual bool paintMenuListButton(RenderObject*, const PaintInfo&, const IntRect&);

    virtual bool isControlStyled(const RenderStyle*, const BorderData&,
                                 const FillLayer&, const Color&) const;

    virtual bool controlSupportsTints(const RenderObject*) const;

    virtual void systemFont(int propId, FontDescription&) const;

    virtual Color platformActiveSelectionBackgroundColor() const;
    virtual Color platformInactiveSelectionBackgroundColor() const;
    
    virtual Color platformActiveSelectionForegroundColor() const;
    virtual Color platformInactiveSelectionForegroundColor() const;
    
    virtual int popupInternalPaddingLeft(RenderStyle*) const;
    virtual int popupInternalPaddingRight(RenderStyle*) const;
    virtual int popupInternalPaddingTop(RenderStyle*) const;
    virtual int popupInternalPaddingBottom(RenderStyle*) const;

private:
    void addIntrinsicMargins(RenderStyle*) const;
    void close();

    bool supportsFocus(ControlPart) const;
};


// Constants

#define MINIMUM_MENU_LIST_SIZE 21
#define POPUP_INTERNAL_PADDING_LEFT 6
#define POPUP_INTERNAL_PADDING_TOP 2
#define POPUP_INTERNAL_PADDING_BOTTOM 2

#ifdef __WXMAC__
#define POPUP_INTERNAL_PADDING_RIGHT 22
#else
#define POPUP_INTERNAL_PADDING_RIGHT 20
#endif

RenderThemeWx::~RenderThemeWx()
{
}

PassRefPtr<RenderTheme> RenderThemeWx::create()
{
    return adoptRef(new RenderThemeWx());
}

PassRefPtr<RenderTheme> RenderTheme::themeForPage(Page* page)
{
    static RenderTheme* rt = RenderThemeWx::create().releaseRef();
    return rt;
}

wxWindow* nativeWindowForRenderObject(RenderObject* o)
{
    FrameView* frameView = o->view()->frameView();
    ASSERT(frameView);
    ASSERT(frameView->hostWindow());
    return frameView->hostWindow()->platformPageClient();
}


bool RenderThemeWx::isControlStyled(const RenderStyle* style, const BorderData& border,
                                     const FillLayer& background, const Color& backgroundColor) const
{
    if (style->appearance() == TextFieldPart || style->appearance() == TextAreaPart)
        return style->border() != border;

    // Normally CSS can be used to set properties of form controls (such as adding a background bitmap).
    // However, for this to work RenderThemeWx needs to adjust uncustomized elements (e.g. buttons) to reflect the
    // changes made by CSS. Since we don't do that right now, the native parts of form elements appear in odd places. 
    // Until we have time to implement that support, we return false here, so that we ignore customizations 
    // and always use the native theme drawing to draw form controls.
    return false;
}

void RenderThemeWx::adjustRepaintRect(const RenderObject* o, IntRect& r)
{
    switch (o->style()->appearance()) {
        case MenulistPart: {
            r.setWidth(r.width() + 100);
            break;
        }
        default:
            break;
    }
}

bool RenderThemeWx::controlSupportsTints(const RenderObject* o) const
{
    if (!isEnabled(o))
        return false;

    // Checkboxes only have tint when checked.
    if (o->style()->appearance() == CheckboxPart)
        return isChecked(o);

    // For now assume other controls have tint if enabled.
    return true;
}

void RenderThemeWx::systemFont(int propId, FontDescription& fontDescription) const
{
    // no-op
}

void RenderThemeWx::addIntrinsicMargins(RenderStyle* style) const
{
    // Cut out the intrinsic margins completely if we end up using a small font size
    if (style->fontSize() < 11)
        return;

    // Intrinsic margin value.
    const int m = 2;

    // FIXME: Using width/height alone and not also dealing with min-width/max-width is flawed.
    if (style->width().isIntrinsicOrAuto()) {
        if (style->marginLeft().quirk())
            style->setMarginLeft(Length(m, Fixed));

        if (style->marginRight().quirk())
            style->setMarginRight(Length(m, Fixed));
    }

    if (style->height().isAuto()) {
        if (style->marginTop().quirk())
            style->setMarginTop(Length(m, Fixed));

        if (style->marginBottom().quirk())
            style->setMarginBottom(Length(m, Fixed));
    }
}

void RenderThemeWx::setCheckboxSize(RenderStyle* style) const
{
    // If the width and height are both specified, then we have nothing to do.
    if (!style->width().isIntrinsicOrAuto() && !style->height().isAuto())
        return;

    // FIXME:  A hard-coded size of 13 is used.  This is wrong but necessary for now.  It matches Firefox.
    // At different DPI settings on Windows, querying the theme gives you a larger size that accounts for
    // the higher DPI.  Until our entire engine honors a DPI setting other than 96, we can't rely on the theme's
    // metrics.
    if (style->width().isIntrinsicOrAuto())
        style->setWidth(Length(13, Fixed));

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

void RenderThemeWx::setRadioSize(RenderStyle* style) const
{
    // This is the same as checkboxes.
    setCheckboxSize(style);
}

bool RenderThemeWx::supportsFocus(ControlPart part) const
{
    switch (part) {
        case PushButtonPart:
        case ButtonPart:
        case TextFieldPart:
        case MenulistPart:
            return true;
        default: // No for all others...
            return false;
    }
}

void RenderThemeWx::adjustButtonStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const
{
    addIntrinsicMargins(style);
}

bool RenderThemeWx::paintButton(RenderObject* o, const PaintInfo& i, const IntRect& r)
{
    wxWindow* window = nativeWindowForRenderObject(o);
    wxDC* dc = static_cast<wxDC*>(i.context->platformContext());
    int flags = 0;
    
    IntRect rect = getAdjustedRect(dc, r);

    if (!isEnabled(o))
        flags |= wxCONTROL_DISABLED;

    ControlPart part = o->style()->appearance();
    if (supportsFocus(part) && isFocused(o))
        flags |= wxCONTROL_FOCUSED;

    if (isPressed(o))
        flags |= wxCONTROL_PRESSED;
    
    if (part == PushButtonPart || part == ButtonPart)
        wxRendererNative::Get().DrawPushButton(window, *dc, rect, flags);
    else if(part == RadioPart) {
        if (isChecked(o))
            flags |= wxCONTROL_CHECKED;
#if wxCHECK_VERSION(2,9,1)
        wxRendererNative::Get().DrawRadioBitmap(window, *dc, rect, flags);
#elif wxCHECK_VERSION(2,9,0)
        wxRendererNative::Get().DrawRadioButton(window, *dc, rect, flags);
#else
        wxRenderer_DrawRadioButton(window, *dc, rect, flags);
#endif
    }
    else if(part == CheckboxPart) {
        if (isChecked(o))
            flags |= wxCONTROL_CHECKED;
        wxRendererNative::Get().DrawCheckBox(window, *dc, rect, flags);
    }
    return false;
}

void RenderThemeWx::adjustTextFieldStyle(CSSStyleSelector*, RenderStyle* style, Element*) const
{

}

bool RenderThemeWx::paintTextField(RenderObject* o, const PaintInfo& i, const IntRect& r)
{
    wxWindow* window = nativeWindowForRenderObject(o);
    wxDC* dc = static_cast<wxDC*>(i.context->platformContext());
    int flags = 0;
    
    IntRect rect = getAdjustedRect(dc, r);

    ControlPart part = o->style()->appearance();
    if (supportsFocus(part) && isFocused(o))
        flags |= wxCONTROL_FOCUSED;

#if wxCHECK_VERSION(2,9,0)
    wxRendererNative::Get().DrawTextCtrl(window, *dc, rect, flags);
#else
    wxRenderer_DrawTextCtrl(window, *dc, r, 0);
#endif

    return false;
}

int RenderThemeWx::minimumMenuListSize(RenderStyle*) const 
{ 
    return MINIMUM_MENU_LIST_SIZE; 
}

void RenderThemeWx::adjustMenuListStyle(CSSStyleSelector*, RenderStyle* style, Element*) const
{
     style->resetBorder();

    // Height is locked to auto.
    style->setHeight(Length(Auto));
    
    style->setPaddingTop(Length(2, Fixed));
    style->setPaddingBottom(Length(2, Fixed));
}
    
bool RenderThemeWx::paintMenuList(RenderObject* o, const PaintInfo& i, const IntRect& r)
{
    wxWindow* window = nativeWindowForRenderObject(o);
    wxDC* dc = static_cast<wxDC*>(i.context->platformContext());
 
    IntRect rect = getAdjustedRect(dc, r);

    int flags = 0;      
    if (!isEnabled(o))
        flags |= wxCONTROL_DISABLED;
        
    if (supportsFocus(o->style()->appearance()) && isFocused(o))
        flags |= wxCONTROL_FOCUSED;

    if (isPressed(o))
        flags |= wxCONTROL_PRESSED;

#if wxCHECK_VERSION(2,9,0)
    wxRendererNative::Get().DrawChoice(window, *dc, rect, flags);
#else
    wxRenderer_DrawChoice(window, *dc, rect, flags);
#endif

    return false;
}

void RenderThemeWx::adjustMenuListButtonStyle(CSSStyleSelector*, RenderStyle*, Element*) const
{
    notImplemented();
}
    
bool RenderThemeWx::paintMenuListButton(RenderObject* o, const PaintInfo& i, const IntRect& r)
{
    wxWindow* window = nativeWindowForRenderObject(o);
    wxDC* dc = static_cast<wxDC*>(i.context->platformContext());

    IntRect rect = getAdjustedRect(dc, r);

    int flags = 0;      
    if (!isEnabled(o))
        flags |= wxCONTROL_DISABLED;
        
    if (supportsFocus(o->style()->appearance()) && isFocused(o))
        flags |= wxCONTROL_FOCUSED;
    
    if (isPressed(o))
        flags |= wxCONTROL_PRESSED;

    wxRendererNative::Get().DrawComboBoxDropButton(window, *dc, rect, flags);
            
    return false;
}

    
Color RenderThemeWx::platformActiveSelectionBackgroundColor() const
{
    return wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT);
}

Color RenderThemeWx::platformInactiveSelectionBackgroundColor() const
{
    return wxSystemSettings::GetColour(wxSYS_COLOUR_BTNSHADOW);
}

Color RenderThemeWx::platformActiveSelectionForegroundColor() const
{
    // FIXME: Get wx to return the correct value for each platform.
#if __WXMAC__
    return Color();
#else
    return Color(255, 255, 255);
#endif
}

Color RenderThemeWx::platformInactiveSelectionForegroundColor() const
{
#if __WXMAC__
    return Color();
#else
    return Color(255, 255, 255);
#endif
}

int RenderThemeWx::popupInternalPaddingLeft(RenderStyle*) const 
{ 
    return POPUP_INTERNAL_PADDING_LEFT; 
}

int RenderThemeWx::popupInternalPaddingRight(RenderStyle*) const 
{
    return POPUP_INTERNAL_PADDING_RIGHT;
}

int RenderThemeWx::popupInternalPaddingTop(RenderStyle*) const 
{
    return POPUP_INTERNAL_PADDING_TOP;
}

int RenderThemeWx::popupInternalPaddingBottom(RenderStyle*) const
{ 
    return POPUP_INTERNAL_PADDING_BOTTOM; 
}

}

