///////////////////////////////////////////////////////////////////////////////
// Name:        wx/msw/private/tlwgeom.h
// Purpose:     wxMSW-specific wxTLWGeometry class.
// Author:      Vadim Zeitlin
// Created:     2018-04-29
// Copyright:   (c) 2018 Vadim Zeitlin <vadim@wxwidgets.org>
// Licence:     wxWindows licence
///////////////////////////////////////////////////////////////////////////////

#ifndef _WX_MSW_PRIVATE_TLWGEOM_H_
#define _WX_MSW_PRIVATE_TLWGEOM_H_

#include "wx/log.h"

#include "wx/msw/private.h"

// names for MSW-specific options
#define wxPERSIST_TLW_MAX_X "xmax"
#define wxPERSIST_TLW_MAX_Y "ymax"

class wxTLWGeometry : public wxTLWGeometryBase
{
public:
    wxTLWGeometry()
    {
        wxZeroMemory(m_placement);
        m_placement.length = sizeof(m_placement);
    }

    virtual bool Save(const Serializer& ser) const override
    {
        // For compatibility with the existing saved positions/sizes, use the
        // same keys as the generic version (which was previously used under
        // MSW too).

        // Normal position and size.
        const RECT& rc = m_placement.rcNormalPosition;
        if ( !ser.SaveField(wxPERSIST_TLW_X, rc.left) ||
             !ser.SaveField(wxPERSIST_TLW_Y, rc.top) )
            return false;

        if ( !ser.SaveField(wxPERSIST_TLW_W, rc.right - rc.left) ||
             !ser.SaveField(wxPERSIST_TLW_H, rc.bottom - rc.top) )
            return false;

        // Maximized/minimized state.
        UINT show = m_placement.showCmd;
        if ( !ser.SaveField(wxPERSIST_TLW_MAXIMIZED, show == SW_SHOWMAXIMIZED) )
            return false;

        if ( !ser.SaveField(wxPERSIST_TLW_ICONIZED, show == SW_SHOWMINIMIZED) )
            return false;

        // Maximized window position.
        const POINT pt = m_placement.ptMaxPosition;
        if ( !ser.SaveField(wxPERSIST_TLW_MAX_X, pt.x) ||
             !ser.SaveField(wxPERSIST_TLW_MAX_Y, pt.y) )
            return false;

        // We don't currently save the minimized window position, it doesn't
        // seem useful for anything and is probably just a left over from
        // Windows 3.1 days, when icons were positioned on the desktop instead
        // of being located in the taskbar.

        return true;
    }

    virtual bool Restore(Serializer& ser) override
    {
        // Normal position and size.
        wxRect r;
        if ( !ser.RestoreField(wxPERSIST_TLW_X, &r.x) ||
             !ser.RestoreField(wxPERSIST_TLW_Y, &r.y) ||
             !ser.RestoreField(wxPERSIST_TLW_W, &r.width) ||
             !ser.RestoreField(wxPERSIST_TLW_H, &r.height) )
            return false;
        wxCopyRectToRECT(r, m_placement.rcNormalPosition);

        // Maximized/minimized state.
        //
        // Note the special case of SW_MINIMIZE: while GetWindowPlacement()
        // returns SW_SHOWMINIMIZED when the window is iconized, we restore it
        // as SW_MINIMIZE as this is what the code in wxTLW checks to determine
        // whether the window is supposed to be iconized or not.
        //
        // Just to confuse matters further, note that SW_MAXIMIZE is exactly
        // the same thing as SW_SHOWMAXIMIZED.
        int tmp;
        UINT& show = m_placement.showCmd;
        if ( ser.RestoreField(wxPERSIST_TLW_MAXIMIZED, &tmp) && tmp )
            show = SW_MAXIMIZE;
        else if ( ser.RestoreField(wxPERSIST_TLW_ICONIZED, &tmp) && tmp )
            show = SW_MINIMIZE;
        else
            show = SW_SHOWNORMAL;

        // Maximized window position.
        if ( ser.RestoreField(wxPERSIST_TLW_MAX_X, &r.x) &&
             ser.RestoreField(wxPERSIST_TLW_MAX_Y, &r.y) )
        {
            m_placement.ptMaxPosition.x = r.x;
            m_placement.ptMaxPosition.y = r.y;
        }

        return true;
    }

    virtual bool GetFrom(const wxTopLevelWindow* tlw) override
    {
        if ( !::GetWindowPlacement(GetHwndOf(tlw), &m_placement) )
        {
            wxLogLastError(wxS("GetWindowPlacement"));
            return false;
        }

        if (m_placement.showCmd != SW_SHOWMAXIMIZED && m_placement.showCmd != SW_SHOWMINIMIZED)
        {
            RECT rcWindow;
            ::GetWindowRect(tlw->GetHWND(), &rcWindow);
            // Height and width should be the same unless the user performed
            // an Aero Snap operation.
            const RECT rcNormal = m_placement.rcNormalPosition;
            if ((rcWindow.bottom - rcWindow.top) != (rcNormal.bottom - rcNormal.top) ||
                (rcWindow.right - rcWindow.left) != (rcNormal.right - rcNormal.left))
            {
                WinStruct<MONITORINFO> mi;
                if (!::GetMonitorInfo(::MonitorFromWindow(tlw->GetHWND(),
                    MONITOR_DEFAULTTONEAREST), &mi))
                {
                    wxLogLastError("GetMonitorInfo");
                    return false;
                }

                // If the tray is on the top or the left, then the rectangle needs to
                // be adjusted to match what ::SetWindowPlacement expects.
                if (mi.rcMonitor.top < mi.rcWork.top ||
                    mi.rcMonitor.left < mi.rcWork.left)
                {
                    // Negative offset to eliminate the tray width/height.
                    OffsetRect(&rcWindow, (mi.rcMonitor.left - mi.rcWork.left),
                         (mi.rcMonitor.top - mi.rcWork.top));
                }

                ::CopyRect(&m_placement.rcNormalPosition, &rcWindow);
            }
        }

        return true;
    }

    virtual bool ApplyTo(wxTopLevelWindow* tlw) override
    {
        // There is a subtlety here: if the window is currently hidden,
        // restoring its geometry shouldn't show it, so we must use SW_HIDE as
        // show command, but showing it later should restore it to the correct
        // state, so we need to remember it in wxTLW itself. And even if it's
        // currently shown, we still need to update its show command, so that
        // it matches the real window state after SetWindowPlacement() call.
        tlw->MSWSetShowCommand(m_placement.showCmd);
        if ( !tlw->IsShown() )
        {
            m_placement.showCmd = SW_HIDE;
        }

        const wxSize oldDPI = tlw->GetDPI();
        if ( !::SetWindowPlacement(GetHwndOf(tlw), &m_placement) )
        {
            wxLogLastError(wxS("SetWindowPlacement"));
            return false;
        }

        // Check if a window DPI hasn't changed, as the result of being placed
        // on a monitor with a different DPI from the main one because in this
        // case its size is not restored properly.
        const wxSize newDPI = tlw->GetDPI();
        if ( oldDPI != newDPI )
        {
            // So try setting the placement again as now it will use the
            // correct scaling factor, because the window is already on the
            // correct monitor.
            if ( !::SetWindowPlacement(GetHwndOf(tlw), &m_placement) )
            {
                wxLogLastError(wxS("SetWindowPlacement (2nd time)"));
                return false;
            }
        }

        return true;
    }

private:
    WINDOWPLACEMENT m_placement;
};

#endif // _WX_MSW_PRIVATE_TLWGEOM_H_
