/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.

#include "QParentWndWidget.h"
#include <QEvent>
#include <QGuiApplication>
#include <QApplication>
#include <QFocusEvent>

#include "QParentWndWidget.h"

#include <qt_windows.h>

#if QT_VERSION >= 0x050000
#include <QWindow>
#endif
static HWND FindTopmostWindow(HWND child, bool considerWsChild)
{
    if (child == GetDesktopWindow())
    {
        return 0;
    }
    HWND current = child;
    while (GetParent(current) != 0)
    {
        if (considerWsChild && (GetWindowLongW(current, GWL_STYLE) & WS_CHILD) == 0)
        {
            break;
        }
        current = GetParent(current);
    }

    return current;
}

QParentWndWidget::QParentWndWidget(HWND parent)
    : m_parent(parent)
    , m_previousFocus(0)
    , m_modalityRoot(0)
    , m_parentToCenterOn(0)
{
    if (m_parent)
    {
        SetWindowLongA((HWND)winId(), GWL_STYLE, WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_TABSTOP);

#if QT_VERSION >= 0x50000
        QWindow* window = windowHandle();
        window->setProperty("_q_embedded_native_parent_handle", (WId)m_parent);
        SetParent((HWND)winId(), m_parent);
        window->setFlags(Qt::FramelessWindowHint);
#else
        SetParent((HWND)winId(), m_parent);
#endif
        QEvent e(QEvent::EmbeddingControl);
        QApplication::sendEvent(this, &e);
    }

    m_parentToCenterOn = FindTopmostWindow(m_parent, true);
    m_modalityRoot = FindTopmostWindow(m_parent, false);
}

void QParentWndWidget::childEvent(QChildEvent* ev)
{
    QObject* child = ev->child();
    if (child->isWidgetType())
    {
        if (ev->added())
        {
            if (child->isWidgetType())
            {
                child->installEventFilter(this);
            }
        }
        else if (ev->removed() && m_parentWasDisabled)
        {
            m_parentWasDisabled = false;
            EnableWindow(m_modalityRoot, true);
            child->removeEventFilter(this);
        }
    }
    QWidget::childEvent(ev);
}

void QParentWndWidget::show()
{
    if (!m_previousFocus)
    {
        m_previousFocus = ::GetFocus();
    }
    if (!m_previousFocus)
    {
        m_previousFocus = parentWindow();
    }

    QWidget::show();
}
void QParentWndWidget::hide()
{
    QWidget::hide();
}

void QParentWndWidget::center()
{
    const QWidget* child = findChild<QWidget*>();

    RECT rect;
    GetWindowRect(m_parentToCenterOn, &rect);
    setGeometry((rect.right - rect.left) / 2 + rect.left,
        (rect.bottom - rect.top) / 2 + rect.top, 0, 0);
}

#if QT_VERSION >= 0x50000
bool QParentWndWidget::nativeEvent(const QByteArray&, void* message, long* result)
#else
bool QParentWndWidget::winEvent(MSG* msg, long* result)
#endif
{
#if QT_VERSION >= 0x50000
    MSG* msg = (MSG*)message;
#endif
    if (msg->message == WM_SETFOCUS)
    {
        Qt::FocusReason reason;
        if (::GetKeyState(VK_LBUTTON) < 0 ||
                ::GetKeyState(VK_RBUTTON) < 0)
        {
            reason = Qt::MouseFocusReason;
        }
        else if (::GetKeyState(VK_SHIFT) < 0)
        {
            reason = Qt::BacktabFocusReason;
        }
        else
        {
            reason = Qt::TabFocusReason;
        }
        QFocusEvent ev(QEvent::FocusIn, reason);
        QApplication::sendEvent(this, &ev);
    }
    if (msg->message == WM_GETDLGCODE)
    {
        *result = DLGC_WANTARROWS | DLGC_WANTTAB;
        return(true);
    }

    return false;
}

bool QParentWndWidget::eventFilter(QObject* obj, QEvent* ev)
{
    QWidget* widget = (QWidget*)obj;
    switch (ev->type())
    {
    case QEvent::WindowDeactivate:
    {
        if (widget->isModal() && isHidden())
        {
            BringWindowToTop(m_parent);
        }
        break;
    }
    case QEvent::Show:
    {
        if (widget->isWindow())
        {
            if (!m_previousFocus)
            {
                m_previousFocus = ::GetFocus();
            }
            if (!m_previousFocus)
            {
                m_previousFocus = parentWindow();
            }
            hide();
            if (widget->isModal() && !m_parentWasDisabled)
            {
                EnableWindow(m_modalityRoot, false);
                m_parentWasDisabled = true;
            }
        }
        break;
    }
    case QEvent::Hide:
    {
        if (m_parentWasDisabled)
        {
            EnableWindow(m_modalityRoot, true);
            m_parentWasDisabled = false;
        }
        if (m_previousFocus)
        {
            ::SetFocus(m_previousFocus);
        }
        else
        {
            ::SetFocus(parentWindow());
        }
        if (widget->testAttribute(Qt::WA_DeleteOnClose) && widget->isWindow())
        {
            deleteLater();
        }
        break;
    }
    case QEvent::Close:
    {
        ::SetActiveWindow(m_parent);
        if (widget->testAttribute(Qt::WA_DeleteOnClose))
        {
            deleteLater();
        }
        break;
    }
    default:
        break;
    }
    ;

    return QWidget::eventFilter(obj, ev);
}

void QParentWndWidget::focusInEvent(QFocusEvent* ev)
{
    QWidget* candidate = this;

    if (ev->reason() == Qt::TabFocusReason || ev->reason() == Qt::BacktabFocusReason)
    {
        while (candidate && (candidate->focusPolicy() & Qt::TabFocus) == 0)
        {
            candidate = candidate->nextInFocusChain();
            if (candidate == this)
            {
                candidate = 0;
            }
        }
        if (candidate)
        {
            candidate->setFocus(ev->reason());
            candidate->setAttribute(Qt::WA_KeyboardFocusChange);
            candidate->window()->setAttribute(Qt::WA_KeyboardFocusChange);
            if (ev->reason() == Qt::BacktabFocusReason)
            {
                QWidget::focusNextPrevChild(false);
            }
        }
    }
}

bool QParentWndWidget::focusNextPrevChild(bool next)
{
    QWidget* current = focusWidget();
    if (next)
    {
        QWidget* nextFocus = current;
        while (true)
        {
            nextFocus = nextFocus->nextInFocusChain();
            if (nextFocus->isWindow())
            {
                break;
            }
            if (nextFocus->focusPolicy() & Qt::TabFocus)
            {
                return QWidget::focusNextPrevChild(true);
            }
        }
    }
    else
    {
        if (!current->isWindow())
        {
            QWidget* nextFocus = current->nextInFocusChain();
            QWidget* prevFocus = 0;
            QWidget* topLevel = 0;
            while (nextFocus != current)
            {
                if ((nextFocus->focusPolicy() & Qt::TabFocus) != 0)
                {
                    prevFocus = nextFocus;
                    topLevel = 0;
                }
                else if (nextFocus->isWindow())
                {
                    topLevel = nextFocus;
                }
                nextFocus = nextFocus->nextInFocusChain();
            }

            if (!topLevel)
            {
                return QWidget::focusNextPrevChild(false);
            }
        }
    }

    ::SetFocus(m_parent);
    return true;
}

#include <QParentWndWidget.moc>