/////////////////////////////////////////////////////////////////////////////
// Name:        src/aui/tabartmsw.cpp
// Purpose:     wxAuiMSWTabArt implementation
// Author:      Tobias Taschner
// Created:     2015-09-26
// Copyright:   (c) 2015 wxWidgets development team
// Licence:     wxWindows licence
/////////////////////////////////////////////////////////////////////////////

#include "wx/wxprec.h"

#if wxUSE_AUI && !defined(__WXUNIVERSAL__)

#ifndef WX_PRECOMP
    #include "wx/dc.h"
    #include "wx/settings.h"
#endif

#include "wx/aui/tabart.h"
#include "wx/aui/auibook.h"
#include "wx/msw/uxtheme.h"
#include "wx/msw/private.h"
#include "wx/msw/private/darkmode.h"
#include "wx/renderer.h"

wxAuiMSWTabArt::wxAuiMSWTabArt()
{
    m_closeBtnSize = wxDefaultSize;
    m_maxTabHeight = 0;

    m_themed = wxUxThemeIsActive() && !wxMSWDarkMode::IsActive();
}

wxAuiMSWTabArt::~wxAuiMSWTabArt()
{
}

wxAuiTabArt* wxAuiMSWTabArt::Clone()
{
    return new wxAuiMSWTabArt(*this);
}

void wxAuiMSWTabArt::DrawBorder(wxDC& dc, wxWindow* wnd, const wxRect& rect)
{
    if ( !IsThemed() )
    {
        wxAuiGenericTabArt::DrawBorder(dc, wnd, rect);
        return;
    }

    wxRect drawRect(rect);

    drawRect.y += m_maxTabHeight + wnd->FromDIP(1);
    drawRect.height -= m_maxTabHeight;

    // Mask border not covered by native theme
    wxRect topDrawRect(rect);
    topDrawRect.height = drawRect.height;
    dc.SetPen(wxPen(wnd->GetBackgroundColour(), GetBorderWidth(wnd)));
    dc.DrawRectangle(topDrawRect);

    wxUxThemeHandle hTheme(wnd, L"TAB");

    hTheme.DrawBackground(
        GetHdcOf(dc.GetTempHDC()),
        drawRect,
        TABP_PANE
    );
}

void wxAuiMSWTabArt::DrawBackground(wxDC& dc,
    wxWindow* wnd,
    const wxRect& rect)
{
    if ( !IsThemed() )
    {
        wxAuiGenericTabArt::DrawBackground(dc, wnd, rect);
        return;
    }

    int borderHeight = 2;

    wxRect drawRect = rect;
    drawRect.height -= borderHeight;

    // Draw background
    dc.SetBrush(wxBrush(wnd->GetBackgroundColour()));
    dc.SetPen(*wxTRANSPARENT_PEN);
    dc.DrawRectangle(drawRect);

    // Draw top border
    drawRect.y = drawRect.height;
    drawRect.height = borderHeight + 2;

    drawRect.Inflate(1, 0);

    wxUxThemeHandle hTheme(wnd, L"TAB");

    hTheme.DrawBackground(
        GetHdcOf(dc.GetTempHDC()),
        drawRect,
        TABP_PANE
    );
}

int wxAuiMSWTabArt::DrawPageTab(wxDC& dc,
    wxWindow* wnd,
    wxAuiNotebookPage& page,
    const wxRect& rect)
{
    if ( !IsThemed() )
    {
        return wxAuiGenericTabArt::DrawPageTab(dc, wnd, page, rect);
    }

    if ( !m_closeBtnSize.IsFullySpecified() )
        InitSizes(wnd, dc);

    // figure out the size of the tab
    int x_extent = 0;
    wxSize tabSize = GetPageTabSize(dc, wnd, page, &x_extent);

    wxCoord tabHeight = tabSize.y;
    wxCoord tabWidth = tabSize.x;
    wxCoord tabX = rect.x;
    wxCoord tabY = rect.y;

    if (!page.active)
    {
        tabY += wnd->FromDIP(2);
        tabHeight -= wnd->FromDIP(2);
    }
    else
    {
        tabX -= wnd->FromDIP(2);
        tabWidth += wnd->FromDIP(4);
        tabHeight += 2;
    }

    int clipWidth = tabWidth;
    if ( tabX + clipWidth > rect.x + rect.width )
        clipWidth = (rect.x + rect.width) - tabX;

    wxDCClipper clipper(dc, tabX - wnd->FromDIP(2), tabY, clipWidth + wnd->FromDIP(4), tabHeight);


    // draw tab
    wxRect tabRect(tabX, tabY, tabWidth, tabHeight);

    int tabState;
    if ( page.active )
        tabState = TIS_SELECTED;
    else if ( page.hover )
        tabState = TIS_HOT;
    else
        tabState = TIS_NORMAL;

    wxUxThemeHandle hTabTheme(wnd, L"Tab");
    hTabTheme.DrawBackground(
        GetHdcOf(dc.GetTempHDC()),
        tabRect,
        TABP_TABITEM,
        tabState
    );

    // Apparently, in at least some Windows 10 installations the call above
    // does not draw the left edge of the first tab and it needs to be drawn
    // separately, or it wouldn't be drawn at all.
    if ( tabX == GetIndentSize() )
    {
        hTabTheme.DrawBackground(
                GetHdcOf(dc.GetTempHDC()),
                tabRect,
                TABP_TABITEMLEFTEDGE,
                tabState
            );
    }

    wxRect textRect = tabRect;
    if ( !page.active )
        textRect.Offset(0, wnd->FromDIP(1));

    for ( auto& button : page.buttons )
    {
        if ( button.curState & wxAUI_BUTTON_STATE_HIDDEN )
            continue;

        switch ( button.id )
        {
            case wxAUI_BUTTON_CLOSE:
                {
                    wxUxThemeHandle hToolTipTheme(wnd, L"TOOLTIP");

                    int btnState;
                    if ( button.curState & wxAUI_BUTTON_STATE_HOVER )
                        btnState = TTCS_HOT;
                    else if ( button.curState & wxAUI_BUTTON_STATE_PRESSED )
                        btnState = TTCS_PRESSED;
                    else
                        btnState = TTCS_NORMAL;

                    button.rect = wxRect(tabX + tabWidth - m_closeBtnSize.x - wnd->FromDIP(4),
                        tabY + (tabHeight / 2) - (m_closeBtnSize.y / 2),
                        m_closeBtnSize.x,
                        m_closeBtnSize.y);

                    hToolTipTheme.DrawBackground(
                        GetHdcOf(dc.GetTempHDC()),
                        button.rect,
                        TTP_CLOSE,
                        btnState
                    );
                }

                textRect.width -= m_closeBtnSize.x + wnd->FromDIP(3);
                break;
        }
    }

    dc.SetFont(wnd->GetFont());
    dc.SetTextForeground(wxSystemSettings::GetColour(wxSYS_COLOUR_BTNTEXT));
    dc.DrawLabel(page.caption, page.bitmap.GetBitmapFor(wnd), textRect, wxALIGN_CENTRE);

    // draw focus rectangle
    if ( page.active && (wnd->FindFocus() == wnd) )
    {
        wxRect focusRect = tabRect;
        focusRect.Deflate(wnd->FromDIP(2));

        wxRendererNative::Get().DrawFocusRect(wnd, dc, focusRect, 0);
    }

    page.rect = wxRect(tabX, tabY, tabWidth, tabHeight);

    return x_extent;
}

int wxAuiMSWTabArt::GetIndentSize()
{
    if ( IsThemed() )
        return wxWindow::FromDIP(3, nullptr); // This should be 1 but we can't draw into the border from DrawTab
    else
        return wxAuiGenericTabArt::GetIndentSize();
}

int wxAuiMSWTabArt::GetBorderWidth(wxWindow* wnd)
{
    return wxAuiGenericTabArt::GetBorderWidth(wnd);
}

int wxAuiMSWTabArt::GetAdditionalBorderSpace(wxWindow* wnd)
{
    if ( IsThemed() )
    {
        return wnd->FromDIP(4, nullptr);
    }
    else
        return wxAuiGenericTabArt::GetAdditionalBorderSpace(wnd);
}

wxSize wxAuiMSWTabArt::GetPageTabSize(wxReadOnlyDC& dc,
    wxWindow* wnd,
    const wxAuiNotebookPage& page,
    int* x_extent)
{
    if ( !IsThemed() )
        return wxAuiGenericTabArt::GetPageTabSize(dc, wnd, page, x_extent);

    if ( !m_closeBtnSize.IsFullySpecified() )
        InitSizes(wnd, dc);

    wxCoord textWidth, textHeight;

    dc.SetFont(wnd->GetFont());
    dc.GetTextExtent(page.caption, &textWidth, nullptr);
    dc.GetTextExtent("ABCDEFXj", nullptr, &textHeight);

    wxCoord tabWidth = wxMax(m_tabSize.x, textWidth);
    wxCoord tabHeight = wxMax(m_tabSize.y, textHeight);

    // if the close button is showing, add space for it
    for ( const auto& button : page.buttons )
    {
        if ( button.curState & wxAUI_BUTTON_STATE_HIDDEN )
            continue;

        switch ( button.id )
        {
            case wxAUI_BUTTON_CLOSE:
                tabWidth += m_closeBtnSize.x;
                tabHeight = wxMax(tabHeight, m_closeBtnSize.y);
                break;
        }
    }

    // if there's a bitmap, add space for it
    if ( page.bitmap.IsOk() )
    {
        const wxSize bitmapSize = page.bitmap.GetPreferredLogicalSizeFor(wnd);

        tabWidth += bitmapSize.x + wnd->FromDIP(3); // bitmap padding
        tabHeight = wxMax(tabHeight, bitmapSize.y + wnd->FromDIP(2));
    }

    // add padding
    tabWidth += wnd->FromDIP(12);
    tabHeight += wnd->FromDIP(3);

    if ( m_flags & wxAUI_NB_TAB_FIXED_WIDTH )
    {
        tabWidth = m_fixedTabWidth;
    }
    else
    {
        int minTabWidth = wnd->FromDIP(46);
        if (tabWidth < minTabWidth)
            tabWidth = minTabWidth;
    }

    if ( x_extent )
        *x_extent = tabWidth;

    if (tabHeight > m_maxTabHeight)
        m_maxTabHeight = tabHeight;

    return wxSize(tabWidth, tabHeight);
}


void wxAuiMSWTabArt::DrawButton(wxDC& dc,
    wxWindow* wnd,
    const wxRect& in_rect,
    int bitmap_id,
    int button_state,
    int orientation,
    wxRect* out_rect)
{
    if ( !IsThemed() )
    {
        wxAuiGenericTabArt::DrawButton(dc, wnd, in_rect, bitmap_id, button_state, orientation, out_rect);
        return;
    }

    const wchar_t* themeId = nullptr;
    int part = 0;

    switch (bitmap_id)
    {
    case wxAUI_BUTTON_CLOSE:
        themeId = L"Window";
        part = WP_CLOSEBUTTON;
        break;
    case wxAUI_BUTTON_LEFT:
        themeId = L"Spin";
        part = SPNP_DOWNHORZ;
        break;
    case wxAUI_BUTTON_RIGHT:
        themeId = L"Spin";
        part = SPNP_UPHORZ;
        break;
    case wxAUI_BUTTON_WINDOWLIST:
        themeId = L"Combobox";
        part = CP_DROPDOWNBUTTON;
        break;
    }

    wxRect rect = in_rect;

    if ( orientation == wxLEFT )
    {
        rect.SetX(in_rect.x);
        rect.SetY(((in_rect.y + in_rect.height) / 2) - (m_closeBtnSize.GetHeight() / 2));
        rect.SetWidth(m_closeBtnSize.GetWidth());
        rect.SetHeight(m_closeBtnSize.GetHeight());
    }
    else
    {
        rect = wxRect(in_rect.x + in_rect.width - m_closeBtnSize.GetWidth(),
            ((in_rect.y + in_rect.height) / 2) - (m_closeBtnSize.GetHeight() / 2),
            m_closeBtnSize.GetWidth(), m_closeBtnSize.GetHeight());
    }

    if ( bitmap_id == wxAUI_BUTTON_LEFT ||
        bitmap_id == wxAUI_BUTTON_RIGHT )
    {
        rect.y = in_rect.y;
        rect.height = in_rect.height - wnd->FromDIP(7);
    }

    dc.SetPen(*wxTRANSPARENT_PEN);
    dc.SetBrush(wxBrush(m_baseColour));
    dc.DrawRectangle(rect);

    int btnState;
    if ( button_state == wxAUI_BUTTON_STATE_DISABLED )
        btnState = TTCS_PRESSED + 1;
    else if ( button_state == wxAUI_BUTTON_STATE_HOVER )
        btnState = TTCS_HOT;
    else if ( button_state == wxAUI_BUTTON_STATE_PRESSED )
        btnState = TTCS_PRESSED;
    else
        btnState = TTCS_NORMAL;

    wxUxThemeHandle hTheme(wnd, themeId);

    wxRect btnRect(rect);
    btnRect.width -= wnd->FromDIP(1);

    hTheme.DrawBackground(
        GetHdcOf(dc.GetTempHDC()),
        rect,
        part,
        btnState
    );

    if ( out_rect )
        *out_rect = rect;
}

int wxAuiMSWTabArt::ShowDropDown(wxWindow* wnd,
    const wxAuiNotebookPageArray& pages,
    int active_idx)
{
    return wxAuiGenericTabArt::ShowDropDown(wnd, pages, active_idx);
}

int wxAuiMSWTabArt::GetBestTabCtrlSize(wxWindow* wnd,
    const wxAuiNotebookPageArray& pages,
    const wxSize& requiredBmp_size)
{
    return wxAuiGenericTabArt::GetBestTabCtrlSize(wnd, pages, requiredBmp_size);
}

void wxAuiMSWTabArt::InitSizes(wxWindow* wnd, wxReadOnlyDC& WXUNUSED(dc))
{
    // Borrow close button from tooltip (best fit on various backgrounds)
    wxUxThemeHandle hTooltipTheme(wnd, L"Tooltip");
    m_closeBtnSize = hTooltipTheme.GetTrueSize(TTP_CLOSE);

    wxUxThemeHandle hTabTheme(wnd, L"Tab");
    m_tabSize = hTabTheme.GetTrueSize(TABP_TABITEM);
}

bool wxAuiMSWTabArt::IsThemed() const
{
    return
        m_themed &&
        !(m_flags & wxAUI_NB_PIN_ON_ACTIVE_TAB) && // We don't draw pin
        !(m_flags & wxAUI_NB_UNPIN_ON_ALL_PINNED) && // ...and unpin buttons yet
        !(m_flags & wxAUI_NB_BOTTOM); // Native theme does not support bottom tabs
}

void wxAuiMSWTabArt::UpdateDpi()
{
    m_closeBtnSize = wxDefaultSize;
}

#endif // wxUSE_AUI && !defined(__WXUNIVERSAL__)
