/////////////////////////////////////////////////////////////////////////////
// Name:        src/osx/nonownedwnd_osx.cpp
// Purpose:     implementation of wxNonOwnedWindow
// Author:      Stefan Csomor
// Created:     2008-03-24
// Copyright:   (c) Stefan Csomor 2008
// Licence:     wxWindows licence
/////////////////////////////////////////////////////////////////////////////

// For compilers that support precompilation, includes "wx.h".
#include "wx/wxprec.h"

#ifndef WX_PRECOMP
    #include "wx/app.h"
    #include "wx/dcmemory.h"
    #include "wx/log.h"
#endif // WX_PRECOMP

#include "wx/evtloop.h"
#include "wx/tooltip.h"
#include "wx/nonownedwnd.h"

#include "wx/osx/private.h"
#include "wx/settings.h"
#include "wx/frame.h"

#if wxUSE_SYSTEM_OPTIONS
    #include "wx/sysopt.h"
#endif

#include <unordered_map>

// ----------------------------------------------------------------------------
// constants
// ----------------------------------------------------------------------------

// trace mask for activation tracing messages
#define TRACE_ACTIVATE "activation"

clock_t wxNonOwnedWindow::s_lastFlush = 0;

// unified title and toolbar constant - not in Tiger headers, so we duplicate it here
#define kWindowUnifiedTitleAndToolbarAttribute (1 << 7)

// ---------------------------------------------------------------------------
// wxWindowMac utility functions
// ---------------------------------------------------------------------------

namespace
{

using MacWindowMap = std::unordered_map<WXWindow, wxNonOwnedWindowImpl*>;

static MacWindowMap wxWinMacWindowList;

} // anonymous namespace

wxNonOwnedWindow* wxNonOwnedWindow::GetFromWXWindow( WXWindow win )
{
    wxNonOwnedWindowImpl* impl = wxNonOwnedWindowImpl::FindFromWXWindow(win);

    return ( impl != nullptr ? impl->GetWXPeer() : nullptr ) ;
}

wxNonOwnedWindowImpl* wxNonOwnedWindowImpl::FindFromWXWindow (WXWindow window)
{
    MacWindowMap::iterator node = wxWinMacWindowList.find(window);

    return (node == wxWinMacWindowList.end()) ? nullptr : node->second;
}

void wxNonOwnedWindowImpl::RemoveAssociations( wxNonOwnedWindowImpl* impl)
{
    MacWindowMap::iterator it;
    for ( it = wxWinMacWindowList.begin(); it != wxWinMacWindowList.end(); ++it )
    {
        if ( it->second == impl )
        {
            wxWinMacWindowList.erase(it);
            break;
        }
    }
}

void wxNonOwnedWindowImpl::Associate( WXWindow window, wxNonOwnedWindowImpl *impl )
{
    // adding null WindowRef is (first) surely a result of an error and
    // nothing else :-)
    wxCHECK_RET( window != (WXWindow) nullptr, wxT("attempt to add a null WindowRef to window list") );

    wxWinMacWindowList[window] = impl;
}

// ----------------------------------------------------------------------------
// wxNonOwnedWindow creation
// ----------------------------------------------------------------------------

wxIMPLEMENT_ABSTRACT_CLASS(wxNonOwnedWindowImpl, wxObject);

wxNonOwnedWindow *wxNonOwnedWindow::s_macDeactivateWindow = nullptr;

void wxNonOwnedWindow::Init()
{
    m_nowpeer = nullptr;
    m_isNativeWindowWrapper = false;
    m_ignoreResizing = false;
}

bool wxNonOwnedWindow::Create(wxWindow *parent,
                                 wxWindowID id,
                                 const wxPoint& posOrig,
                                 const wxSize& sizeOrig,
                                 long style,
                                 const wxString& name)
{
    m_windowStyle = style;

    SetName( name );

    m_windowId = id == -1 ? NewControlId() : id;
    m_windowStyle = style;
    m_isShown = false;

    // use the appropriate defaults for the position and size if necessary
    wxSize size(sizeOrig);
    if ( !size.IsFullySpecified() )
        size.SetDefaults(wxTopLevelWindow::GetDefaultSize());

    wxPoint pos(posOrig);
    if ( !pos.IsFullySpecified() )
    {
        wxRect rectWin = wxRect(size).CentreIn(wxGetClientDisplayRect());

        // The size of the window is often not really known yet, TLWs are often
        // created with some small initial size and later are fitted to contain
        // their children so centering the window will show it too much to the
        // right and bottom, adjust for it by putting it more to the left and
        // center.
        rectWin.x /= 2;
        rectWin.y /= 2;

        pos.SetDefaults(rectWin.GetPosition());
    }

    // create frame.
    m_nowpeer = wxNonOwnedWindowImpl::CreateNonOwnedWindow
                (
                    this, parent,
                    pos , size,
                    style, GetExtraStyle(),
                    name
                );
    wxNonOwnedWindowImpl::Associate( m_nowpeer->GetWXWindow() , m_nowpeer ) ;
    SetPeer(wxWidgetImpl::CreateContentView(this));

    DoSetWindowVariant( m_windowVariant ) ;

    wxWindowCreateEvent event(this);
    HandleWindowEvent(event);

    if ( parent )
        parent->AddChild(this);

    return true;
}

bool wxNonOwnedWindow::Create(wxWindow *parent, WXWindow nativeWindow)
{
    if ( parent )
        parent->AddChild(this);

    SubclassWin(nativeWindow);

    return true;
}

void wxNonOwnedWindow::SubclassWin(WXWindow nativeWindow)
{
    wxASSERT_MSG( !m_isNativeWindowWrapper, wxT("subclassing window twice?") );
    wxASSERT_MSG( m_nowpeer == nullptr, wxT("window already was created") );

    m_nowpeer = wxNonOwnedWindowImpl::CreateNonOwnedWindow(this, GetParent(), nativeWindow );
    m_isNativeWindowWrapper = true;
    wxNonOwnedWindowImpl::Associate( m_nowpeer->GetWXWindow() , m_nowpeer ) ;
    SetPeer(wxWidgetImpl::CreateContentView(this));
}

void wxNonOwnedWindow::UnsubclassWin()
{
    wxASSERT_MSG( m_isNativeWindowWrapper, wxT("window was not subclassed") );

    if ( GetParent() )
        GetParent()->RemoveChild(this);

    wxNonOwnedWindowImpl::RemoveAssociations(m_nowpeer) ;
    wxDELETE(m_nowpeer);
    SetPeer(nullptr);
    m_isNativeWindowWrapper = false;
}


wxNonOwnedWindow::~wxNonOwnedWindow()
{
    SendDestroyEvent();

    wxNonOwnedWindowImpl::RemoveAssociations(m_nowpeer) ;

    DestroyChildren();

    wxDELETE(m_nowpeer);

    // avoid dangling refs
    if ( s_macDeactivateWindow == this )
        s_macDeactivateWindow = nullptr;
}

bool wxNonOwnedWindow::Destroy()
{
    WillBeDestroyed();

    return wxWindow::Destroy();
}

void wxNonOwnedWindow::WillBeDestroyed()
{
    if ( m_nowpeer )
        m_nowpeer->WillBeDestroyed();
}

// ----------------------------------------------------------------------------
// wxNonOwnedWindow misc
// ----------------------------------------------------------------------------

bool wxNonOwnedWindow::OSXShowWithEffect(bool show,
                                         wxShowEffect effect,
                                         unsigned timeout)
{
    if ( effect == wxSHOW_EFFECT_NONE ||
            !m_nowpeer || !m_nowpeer->ShowWithEffect(show, effect, timeout) )
        return Show(show);

    if ( show )
    {
        // as apps expect a size event to occur when the window is shown,
        // generate one when it is shown with effect too
        SendSizeEvent();
    }

    return true;
}

wxPoint wxNonOwnedWindow::GetClientAreaOrigin() const
{
    int left, top, width, height;
    m_nowpeer->GetContentArea(left, top, width, height);
    return wxPoint(left, top);
}

bool wxNonOwnedWindow::SetBackgroundColour(const wxColour& c )
{
    if ( !wxWindow::SetBackgroundColour(c) && m_hasBgCol )
        return false ;

    // only set the native background color if the toplevel window's
    // background is not supposed to be transparent, otherwise the
    // transparency is lost
    // we cannot set the background color on a shaped toplevel window
    // otherwise we loose the shape and end up with a rectangular background
    if ( GetBackgroundStyle() != wxBG_STYLE_PAINT && GetBackgroundStyle() != wxBG_STYLE_TRANSPARENT && GetShape().IsEmpty() )
    {
        if ( m_nowpeer )
            return m_nowpeer->SetBackgroundColour(c);
    }
    return true;
}

void wxNonOwnedWindow::SetWindowStyleFlag(long flags)
{
    if (flags == GetWindowStyleFlag())
        return;

    wxWindow::SetWindowStyleFlag(flags);

    if (m_nowpeer)
        m_nowpeer->SetWindowStyleFlag(flags);
}

// Raise the window to the top of the Z order
void wxNonOwnedWindow::Raise()
{
    if (!IsShown())
        return;

    m_nowpeer->Raise();
}

// Lower the window to the bottom of the Z order
void wxNonOwnedWindow::Lower()
{
    m_nowpeer->Lower();
}

void wxNonOwnedWindow::HandleActivated( double timestampsec, bool didActivate )
{
    MacActivate( (int) (timestampsec * 1000) , didActivate);
    wxActivateEvent wxevent(wxEVT_ACTIVATE, didActivate , GetId());
    wxevent.SetTimestamp( (int) (timestampsec * 1000) );
    wxevent.SetEventObject(this);
    HandleWindowEvent(wxevent);
}

void wxNonOwnedWindow::HandleResized( double WXUNUSED(timestampsec) )
{
    SendSizeEvent();
    // we have to inform some controls that have to reset things
    // relative to the toplevel window (e.g. OpenGL buffers)
    wxWindowMac::MacSuperChangedPosition() ; // like this only children will be notified
}

void wxNonOwnedWindow::HandleResizing( double WXUNUSED(timestampsec), wxRect* rect )
{
    if ( m_ignoreResizing )
        return;

    wxRect r = *rect ;

    // this is a EVT_SIZING not a EVT_SIZE type !
    wxSizeEvent wxevent( r , GetId() ) ;
    wxevent.SetEventObject( this ) ;
    if ( HandleWindowEvent(wxevent) )
        r = wxevent.GetRect() ;

    if ( GetMaxWidth() != -1 && r.GetWidth() > GetMaxWidth() )
        r.SetWidth( GetMaxWidth() ) ;
    if ( GetMaxHeight() != -1 && r.GetHeight() > GetMaxHeight() )
        r.SetHeight( GetMaxHeight() ) ;
    if ( GetMinWidth() != -1 && r.GetWidth() < GetMinWidth() )
        r.SetWidth( GetMinWidth() ) ;
    if ( GetMinHeight() != -1 && r.GetHeight() < GetMinHeight() )
        r.SetHeight( GetMinHeight() ) ;

    *rect = r;
    // TODO actuall this is too early, in case the window extents are adjusted
    wxWindowMac::MacSuperChangedPosition() ; // like this only children will be notified
}

void wxNonOwnedWindow::HandleMoved( double timestampsec )
{
    wxMoveEvent wxevent( GetPosition() , GetId());
    wxevent.SetTimestamp( (int) (timestampsec * 1000) );
    wxevent.SetEventObject( this );
    HandleWindowEvent(wxevent);
}

void wxNonOwnedWindow::MacDelayedDeactivation(long timestamp)
{
    if (s_macDeactivateWindow)
    {
        wxLogTrace(TRACE_ACTIVATE,
                   wxT("Doing delayed deactivation of %p"),
                   s_macDeactivateWindow);

        s_macDeactivateWindow->MacActivate(timestamp, false);
    }
}

void wxNonOwnedWindow::MacActivate( long timestamp , bool WXUNUSED(inIsActivating) )
{
    wxLogTrace(TRACE_ACTIVATE, wxT("TopLevel=%p::MacActivate"), this);

    if (s_macDeactivateWindow == this)
        s_macDeactivateWindow = nullptr;

    MacDelayedDeactivation(timestamp);
}

bool wxNonOwnedWindow::Show(bool show)
{
    if ( !wxWindow::Show(show) )
        return false;

    if ( m_nowpeer )
        m_nowpeer->Show(show);

    if ( show )
    {
        // because apps expect a size event to occur at this moment
        SendSizeEvent();
    }

    return true ;
}

bool wxNonOwnedWindow::SetTransparent(wxByte alpha)
{
    return m_nowpeer->SetTransparent(alpha);
}


bool wxNonOwnedWindow::CanSetTransparent()
{
    return m_nowpeer->CanSetTransparent();
}


void wxNonOwnedWindow::SetExtraStyle(long exStyle)
{
    if ( GetExtraStyle() == exStyle )
        return ;

    wxWindow::SetExtraStyle( exStyle ) ;

    if ( m_nowpeer )
        m_nowpeer->SetExtraStyle(exStyle);
}

bool wxNonOwnedWindow::SetBackgroundStyle(wxBackgroundStyle style)
{
    if ( !wxWindow::SetBackgroundStyle(style) )
        return false ;

    return m_nowpeer ? m_nowpeer->SetBackgroundStyle(style) : true;
}

void wxNonOwnedWindow::DoMoveWindow(int x, int y, int width, int height)
{
    if ( m_nowpeer == nullptr )
        return;

    m_cachedClippedRectValid = false ;

    m_nowpeer->MoveWindow(x, y, width, height);
    wxWindowMac::MacSuperChangedPosition() ; // like this only children will be notified
}

void wxNonOwnedWindow::DoGetPosition( int *x, int *y ) const
{
    if ( m_nowpeer == nullptr )
        return;

    int x1,y1 ;
    m_nowpeer->GetPosition(x1, y1);

    if (x)
       *x = x1 ;
    if (y)
       *y = y1 ;
}

void wxNonOwnedWindow::DoGetSize( int *width, int *height ) const
{
    if ( m_nowpeer == nullptr )
        return;

    int w,h;

    m_nowpeer->GetSize(w, h);

    if (width)
       *width = w ;
    if (height)
       *height = h ;
}

void wxNonOwnedWindow::DoGetClientSize( int *width, int *height ) const
{
    if ( m_nowpeer == nullptr )
        return;

    int left, top, w, h;
    m_nowpeer->GetContentArea(left, top, w, h);

    if (width)
       *width = w ;
    if (height)
       *height = h ;
}

void wxNonOwnedWindow::WindowWasPainted()
{
    s_lastFlush = clock();
}

void wxNonOwnedWindow::Update()
{
    if ( m_nowpeer == nullptr )
        return;

    if ( clock() - s_lastFlush > CLOCKS_PER_SEC / 30 )
    {
        s_lastFlush = clock();
        m_nowpeer->Update();
    }
}

WXWindow wxNonOwnedWindow::GetWXWindow() const
{
    return m_nowpeer ? m_nowpeer->GetWXWindow() : nullptr;
}

#if wxOSX_USE_COCOA_OR_IPHONE
void *wxNonOwnedWindow::OSXGetViewOrWindow() const
{
    return GetWXWindow();
}
#endif

// ---------------------------------------------------------------------------
// Shape implementation
// ---------------------------------------------------------------------------

bool wxNonOwnedWindow::DoClearShape()
{
    m_shape.Clear();
    m_shapePath = wxGraphicsPath();

    wxSize sz = GetClientSize();
    wxRegion region(0, 0, sz.x, sz.y);

    return m_nowpeer->SetShape(region);
}

bool wxNonOwnedWindow::DoSetRegionShape(const wxRegion& region)
{
    m_shapePath = wxGraphicsPath();
    m_shape = region;

    // set the native content view to transparency, this is an implementation detail
    // no reflected in the wx BackgroundStyle
    GetPeer()->SetBackgroundStyle(wxBG_STYLE_TRANSPARENT);
    GetPeer()->SetNeedsDisplay();

    return m_nowpeer->SetShape(region);
}

#if wxUSE_GRAPHICS_CONTEXT

#include <memory>

bool wxNonOwnedWindow::DoSetPathShape(const wxGraphicsPath& path)
{
    // Convert the path to wxRegion by rendering the path on a window-sized
    // bitmap, creating a mask from it and finally creating the region from
    // this mask.
    wxBitmap bmp(GetSize());

    {
        wxMemoryDC dc(bmp);
        dc.SetBackground(*wxBLACK_BRUSH);
        dc.Clear();

        std::unique_ptr<wxGraphicsContext> context(wxGraphicsContext::Create(dc));
        context->SetBrush(*wxWHITE_BRUSH);
        context->SetAntialiasMode(wxANTIALIAS_NONE);
        context->FillPath(path);
    }

    bmp.SetMask(new wxMask(bmp, *wxBLACK));

    // the shape path has to be set AFTER the region is set, because that method
    // clears any former path
    bool success = DoSetRegionShape(wxRegion(bmp));
    if ( success )
        m_shapePath = path;

    return success;
}

void
wxNonOwnedWindow::OSXHandleMiniaturize(double WXUNUSED(timestampsec),
                                       bool miniaturized)
{
    if ( wxTopLevelWindowMac* top = (wxTopLevelWindowMac*) MacGetTopLevelWindow() )
        top->OSXSetIconizeState(miniaturized);
}

#endif // wxUSE_GRAPHICS_CONTEXT
