/*  PCSX2 - PS2 Emulator for PCs
 *  Copyright (C) 2002-2010  PCSX2 Dev Team
 *
 *  PCSX2 is free software: you can redistribute it and/or modify it under the terms
 *  of the GNU Lesser General Public License as published by the Free Software Found-
 *  ation, either version 3 of the License, or (at your option) any later version.
 *
 *  PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 *  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 *  PURPOSE.  See the GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along with PCSX2.
 *  If not, see <http://www.gnu.org/licenses/>.
 */

#pragma once

// ----------------------------------------------------------------------------
// wxGuiTools.h
//
// This file is meant to contain utility classes for users of the wxWidgets library.
// All classes in this file are dependent on wxBase and wxCore libraries!  Meaning
// you will have to use wxCore header files and link against wxCore (GUI) to build
// them.  For tools which require only wxBase, see wxBaseTools.h
// ----------------------------------------------------------------------------


#if wxUSE_GUI

#include "Dependencies.h"
#include <memory>
#include <stack>

#include <wx/wx.h>

class pxStaticText;
class pxStaticHeading;
class pxCheckBox;
class wxSpinCtrl;

namespace pxSizerFlags
{
static int StdPadding = 4;

extern wxSizerFlags StdSpace();
extern wxSizerFlags StdCenter();
extern wxSizerFlags StdExpand();
extern wxSizerFlags TopLevelBox();
extern wxSizerFlags SubGroup();
extern wxSizerFlags StdButton();
extern wxSizerFlags Checkbox();
extern void SetBestPadding();
};

#define wxSF wxSizerFlags()

// --------------------------------------------------------------------------------------
//  pxAlignment / pxStretchType
// --------------------------------------------------------------------------------------
// These are full blown class types instead of enumerations because wxSizerFlags has an
// implicit conversion from integer (silly design flaw creating more work for me!)
//
struct pxAlignmentType
{
    enum {
        Centre,
        Center = Centre,
        Middle,
        Left,
        Right,
        Top,
        Bottom
    };

    int intval;

    wxSizerFlags Apply(wxSizerFlags flags = wxSizerFlags()) const;

    wxSizerFlags operator&(const wxSizerFlags &_flgs) const
    {
        return Apply(_flgs);
    }

    wxSizerFlags Expand() const
    {
        return Apply().Expand();
    }

    wxSizerFlags Border(int dir, int padding) const
    {
        return Apply().Border(dir, padding);
    }

    wxSizerFlags Proportion() const
    {
        return Apply().Proportion(intval);
    }

    operator wxSizerFlags() const
    {
        return Apply();
    }
};

struct pxStretchType
{
    enum {
        Shrink,
        Expand,
        Shaped,
        ReserveHidden,
        FixedMinimum
    };

    int intval;

    wxSizerFlags Apply(wxSizerFlags flags = wxSizerFlags()) const;

    wxSizerFlags operator&(const wxSizerFlags &_flgs) const
    {
        return Apply(_flgs);
    }

    wxSizerFlags Border(int dir, int padding) const
    {
        return Apply().Border(dir, padding);
    }

    wxSizerFlags Proportion() const
    {
        return Apply().Proportion(intval);
    }

    operator wxSizerFlags() const
    {
        return Apply();
    }
};

static __fi wxSizerFlags pxProportion(int prop)
{
    return wxSizerFlags(prop);
}

static __fi wxSizerFlags pxBorder(int dir = wxALL, int pad = pxSizerFlags::StdPadding)
{
    return wxSizerFlags().Border(dir, pad);
}

class pxStretchSpacer
{
public:
    int proportion;

    pxStretchSpacer(int prop = 0)
    {
        proportion = prop;
    }
};

extern const pxAlignmentType
    pxCentre, // Horizontal centered alignment
    pxCenter,
    pxMiddle, // vertical centered alignment

    pxAlignLeft,
    pxAlignRight,
    pxAlignTop,
    pxAlignBottom;

extern const pxStretchType
    pxShrink,
    pxExpand,
    pxShaped,
    pxReserveHidden,
    pxFixedMinimum;

// --------------------------------------------------------------------------------------
//  pxWindowAndFlags
// --------------------------------------------------------------------------------------
// This struct is a go-between for combining windows and sizer flags in "neat" fashion.
// To create the struct, use the | operator, like so:
//
//   myPanel | wxSizerFlags().Expand()
//
// Implementation Note:  This struct is a template as it allows us to use a special
// version of the += operator that retains the type information of the window, in case
// the window implements its own += overloads (one example is pxStaticText).  Without the
// template, the type of the window would only be known as "wxWindow" when it's added to the
// sizer, and would thus fail to invoke the correct operator overload.
//
template <typename WinType>
struct pxWindowAndFlags
{
    WinType *window;
    wxSizerFlags flags;
};


extern wxSizerFlags operator&(const wxSizerFlags &_flgs, const wxSizerFlags &_flgs2);

template <typename WinType, typename = typename std::enable_if<std::is_base_of<wxObject, WinType>::value>::type>
pxWindowAndFlags<WinType> operator|(WinType *_win, const wxSizerFlags &_flgs)
{
    pxWindowAndFlags<WinType> result = {_win, _flgs};
    return result;
}

template <typename WinType, typename = typename std::enable_if<std::is_base_of<wxObject, WinType>::value>::type>
pxWindowAndFlags<WinType> operator|(WinType &_win, const wxSizerFlags &_flgs)
{
    pxWindowAndFlags<WinType> result = {&_win, _flgs};
    return result;
}

// --------------------------------------------------------------------------------------
//  wxSizer Operator +=  .. a wxSizer.Add() Substitute
// --------------------------------------------------------------------------------------
// This set of operators is the *recommended* method for adding windows to sizers, not just
// because it's a lot prettier but also because it allows controls like pxStaticText to over-
// ride default sizerflags behavior.
//
// += operator works on either sizers, wxDialogs or wxPanels.  In the latter case, the window
// is added to the dialog/panel's toplevel sizer (wxPanel.GetSizer() is used).  If the panel
// has no sizer set via SetSizer(), an assertion is generated.
//

extern void operator+=(wxSizer &target, wxWindow *src);
extern void operator+=(wxSizer &target, wxSizer *src);
extern void operator+=(wxSizer &target, wxWindow &src);
extern void operator+=(wxSizer &target, wxSizer &src);

extern void operator+=(wxSizer *target, wxWindow &src);
extern void operator+=(wxSizer *target, wxSizer &src);

extern void operator+=(wxSizer &target, int spacer);
extern void operator+=(wxWindow &target, int spacer);
extern void operator+=(wxSizer &target, const pxStretchSpacer &spacer);
extern void operator+=(wxWindow &target, const pxStretchSpacer &spacer);

// ----------------------------------------------------------------------------
// Important: This template is needed in order to retain window type information and
// invoke the proper overloaded version of += (which is used by pxStaticText and other
// classes to perform special actions when added to sizers).
template <typename WinType>
void operator+=(wxWindow &target, WinType *src)
{
    if (!pxAssert(target.GetSizer() != NULL))
        return;
    *target.GetSizer() += src;
}

template <typename WinType>
void operator+=(wxWindow &target, WinType &src)
{
    if (!pxAssert(target.GetSizer() != NULL))
        return;
    *target.GetSizer() += src;
}

template <typename WinType>
void operator+=(wxWindow &target, const pxWindowAndFlags<WinType> &src)
{
    if (!pxAssert(target.GetSizer() != NULL))
        return;
    *target.GetSizer() += src;
}

template <typename WinType>
void operator+=(wxSizer &target, const pxWindowAndFlags<WinType> &src)
{
    target.Add(src.window, src.flags);
}

wxDECLARE_EVENT(pxEvt_OnDialogCreated, wxCommandEvent);

// --------------------------------------------------------------------------------------
//  pxDialogCreationFlags
// --------------------------------------------------------------------------------------
class pxDialogCreationFlags
{
public:
    wxSize MinimumSize;
    wxOrientation BoxSizerOrient;

    bool isResizable;
    bool hasContextHelp;
    bool hasCaption;
    bool hasMinimizeBox;
    bool hasMaximizeBox;
    bool hasSystemMenu;
    bool hasCloseBox;

public:
    virtual ~pxDialogCreationFlags() = default;

    pxDialogCreationFlags()
    {
        MinimumSize = wxDefaultSize;
        BoxSizerOrient = wxVERTICAL;
        isResizable = false;
        hasContextHelp = false;

        hasCloseBox = true;
        hasSystemMenu = true;
        hasMinimizeBox = false;
        hasMaximizeBox = false;
        hasCaption = true;
    }

    pxDialogCreationFlags &SetSizerOrient(wxOrientation orient)
    {
        BoxSizerOrient = orient;
        return *this;
    }

    pxDialogCreationFlags &SetResize(bool enable = true)
    {
        isResizable = enable;
        return *this;
    }

    pxDialogCreationFlags &SetMinimize(bool enable = true)
    {
        hasMinimizeBox = enable;
        return *this;
    }

    pxDialogCreationFlags &SetMaximize(bool enable = true)
    {
        hasMaximizeBox = enable;
        return *this;
    }

    // NOTE: Enabling system menu on dialogs usually doesn't work, and might cause
    // other unwanted behavior, such as a missing close button.
    pxDialogCreationFlags &SetSystemMenu(bool enable = true)
    {
        hasSystemMenu = enable;
        return *this;
    }

    pxDialogCreationFlags &SetCaption(bool enable = true)
    {
        hasCaption = enable;
        return *this;
    }

    pxDialogCreationFlags &SetCloseBox(bool enable = true)
    {
        hasCloseBox = enable;
        return *this;
    }

    pxDialogCreationFlags SetContextHelp(bool enabled = true)
    {
        hasContextHelp = enabled;
        return *this;
    }

    pxDialogCreationFlags &SetMinWidth(int width)
    {
        if (width > MinimumSize.x)
            MinimumSize.SetWidth(width);
        return *this;
    }

    pxDialogCreationFlags &SetMinHeight(int height)
    {
        if (height > MinimumSize.y)
            MinimumSize.SetHeight(height);
        return *this;
    }

    pxDialogCreationFlags &SetMinSize(const wxSize &size)
    {
        return SetMinWidth(size.x).SetMinHeight(size.y);
    }


    pxDialogCreationFlags Horizontal() const
    {
        return pxDialogCreationFlags(*this).SetSizerOrient(wxHORIZONTAL);
    }

    pxDialogCreationFlags Vertical() const
    {
        return pxDialogCreationFlags(*this).SetSizerOrient(wxVERTICAL);
    }

    pxDialogCreationFlags NoSizer() const
    {
        return pxDialogCreationFlags(*this).SetSizerOrient((wxOrientation)0);
    }

    pxDialogCreationFlags Resize(bool enable = true) const
    {
        return pxDialogCreationFlags(*this).SetResize(enable);
    }

    pxDialogCreationFlags Minimize(bool enable = true) const
    {
        return pxDialogCreationFlags(*this).SetMinimize(enable);
    }

    pxDialogCreationFlags Maximize(bool enable = true) const
    {
        return pxDialogCreationFlags(*this).SetMaximize(enable);
    }

    // NOTE: Enabling system menu on dialogs usually doesn't work, and might cause
    // other unwanted behavior, such as a missing close button.
    pxDialogCreationFlags SystemMenu(bool enable = true) const
    {
        return pxDialogCreationFlags(*this).SetSystemMenu(false);
    }

    pxDialogCreationFlags Caption(bool enable = true) const
    {
        return pxDialogCreationFlags(*this).SetCaption(enable);
    }

    pxDialogCreationFlags CloseBox(bool enable = true) const
    {
        return pxDialogCreationFlags(*this).SetCloseBox(enable);
    }

    pxDialogCreationFlags NoResize() const
    {
        return pxDialogCreationFlags(*this).SetResize(false);
    }

    pxDialogCreationFlags NoMinimize() const
    {
        return pxDialogCreationFlags(*this).SetMinimize(false);
    }

    pxDialogCreationFlags NoMaximize() const
    {
        return pxDialogCreationFlags(*this).SetMaximize(false);
    }

    pxDialogCreationFlags NoSystemMenu() const
    {
        return pxDialogCreationFlags(*this).SetSystemMenu(false);
    }

    pxDialogCreationFlags NoCaption() const
    {
        return pxDialogCreationFlags(*this).SetCaption(false);
    }

    pxDialogCreationFlags NoCloseBox() const
    {
        return pxDialogCreationFlags(*this).SetCloseBox(false);
    }

    pxDialogCreationFlags MinWidth(int width) const
    {
        return pxDialogCreationFlags(*this).SetMinWidth(width);
    }

    pxDialogCreationFlags MinHeight(int height) const
    {
        return pxDialogCreationFlags(*this).SetMinHeight(height);
    }

    pxDialogCreationFlags MinSize(const wxSize &size) const
    {
        return pxDialogCreationFlags(*this).SetMinSize(size);
    }

    pxDialogCreationFlags MinSize(int width, int height) const
    {
        return pxDialogCreationFlags(*this).SetMinWidth(width).SetMinHeight(height);
    }

    int GetWxWindowFlags() const
    {
        int retval = 0;
        if (isResizable)
            retval |= wxRESIZE_BORDER;
        if (hasCaption)
            retval |= wxCAPTION;
        if (hasMaximizeBox)
            retval |= wxMAXIMIZE_BOX;
        if (hasMinimizeBox)
            retval |= wxMINIMIZE_BOX;
        if (hasSystemMenu)
            retval |= wxSYSTEM_MENU;
        if (hasCloseBox)
            retval |= wxCLOSE_BOX;

        return retval;
    }
};

// --------------------------------------------------------------------------------------
//  wxDialogWithHelpers
// --------------------------------------------------------------------------------------
class wxDialogWithHelpers : public wxDialog
{
    typedef wxDialog _parent;
    wxDECLARE_DYNAMIC_CLASS_NO_COPY(wxDialogWithHelpers);

protected:
    bool m_hasContextHelp;
    wxBoxSizer *m_extraButtonSizer;
    wxRect m_CreatedRect;

public:
    wxDialogWithHelpers();
    wxDialogWithHelpers(wxWindow *parent, const wxString &title, const pxDialogCreationFlags &cflags = pxDialogCreationFlags());
    virtual ~wxDialogWithHelpers() = default;

    void Init(const pxDialogCreationFlags &cflags);
    void AddOkCancel(wxSizer &sizer, bool hasApply = false);
    void AddOkCancel(wxSizer *sizer = NULL, bool hasApply = false);
    void RememberPosition();

    virtual void SmartCenterFit();
    virtual int ShowModal();
    virtual bool Show(bool show = true);

    // Must return the same thing as GetNameStatic; a name ideal for use in uniquely
    // identifying dialogs.  (this version is the 'instance' version, which is called
    // by BaseConfigurationDialog to assign the wxWidgets dialog name, and for saving
    // screenshots to disk)
    virtual wxString GetDialogName() const;

    virtual wxStaticText &Label(const wxString &label);
    virtual pxStaticText &Text(const wxString &label);
    virtual pxStaticText &Heading(const wxString &label);

    wxDialogWithHelpers &SetMinWidth(int newWidth);
    wxDialogWithHelpers &SetMinHeight(int newHeight);

    int GetCharHeight() const;

protected:
    void OnDialogCreated(wxCommandEvent &evt);
    void OnOkCancel(wxCommandEvent &evt);

    bool ShouldPreventAppExit() const { return false; }

    void DoAutoCenter();
};

// --------------------------------------------------------------------------------------
//  wxPanelWithHelpers
// --------------------------------------------------------------------------------------
// Overview of Helpers provided by this class:
//  * Simpler constructors that have wxID, position, and size parameters removed (We never
//    use them in pcsx2)
//
//  * Automatic 'primary box sizer' creation, assigned via SetSizer() -- use GetSizer()
//    to retrieve it, or use the "*this += window;" syntax to add windows directly to it.
//
//  * Built-in support for StaticBoxes (aka groupboxes).  Create one at construction with
//    a wxString label, or add one "after the fact" using AddFrame.
//
//  * Propagates IdealWidth settings from parenting wxPanelWithHelpers classes, and auto-
//    matically adjusts the width based on the sizer type (groupsizers get truncated to
//    account for borders).
//
class wxPanelWithHelpers : public wxPanel
{
    wxDECLARE_DYNAMIC_CLASS_NO_COPY(wxPanelWithHelpers);

public:
    wxPanelWithHelpers(wxWindow *parent, wxOrientation orient, const wxString &staticBoxLabel);
    wxPanelWithHelpers(wxWindow *parent, wxOrientation orient);
    wxPanelWithHelpers(wxWindow *parent, const wxPoint &pos, const wxSize &size = wxDefaultSize);
    explicit wxPanelWithHelpers(wxWindow *parent = NULL);

    wxPanelWithHelpers *AddFrame(const wxString &label, wxOrientation orient = wxVERTICAL);

    wxStaticText &Label(const wxString &label);
    pxStaticText &Text(const wxString &label);
    pxStaticText &Heading(const wxString &label);

    virtual wxPanelWithHelpers &SetMinWidth(int newWidth);

protected:
    void Init();
};


// --------------------------------------------------------------------------------------
//  pxTextWrapperBase
// --------------------------------------------------------------------------------------
// this class is used to wrap the text on word boundary: wrapping is done by calling
// OnStartLine() and OnOutputLine() functions.  This class by itself can be used as a
// line counting tool, but produces no formatted text output.
//
// [class "borrowed" from wxWidgets private code, made public, and renamed to avoid possible
//  conflicts with future editions of wxWidgets which might make it public.  Why this isn't
//  publicly available already in wxBase I'll never know-- air]
//
class pxTextWrapperBase
{
protected:
    bool m_eol;
    int m_linecount;
    wxString m_indent;

public:
    virtual ~pxTextWrapperBase() = default;

    pxTextWrapperBase(const wxString &indent = wxEmptyString)
        : m_indent(indent)
    {
        m_eol = false;
        m_linecount = 0;
    }

    // win is used for getting the font, text is the text to wrap, width is the
    // max line width or -1 to disable wrapping
    pxTextWrapperBase &Wrap(const wxWindow &win, const wxString &text, int widthMax);

    int GetLineCount() const
    {
        return m_linecount;
    }

protected:
    // line may be empty
    virtual void OnOutputLine(const wxString &line) {}

    // called at the start of every new line (except the very first one)
    virtual void OnNewLine() {}

    void DoOutputLine(const wxString &line);
    bool IsStartOfNewLine();
};

// --------------------------------------------------------------------------------------
//  pxTextWrapper
// --------------------------------------------------------------------------------------
// This class extends pxTextWrapperBase and adds the ability to retrieve the formatted
// result of word wrapping.
//
class pxTextWrapper : public pxTextWrapperBase
{
    typedef pxTextWrapperBase _parent;

protected:
    wxString m_text;

public:
    pxTextWrapper(const wxString &wrapPrefix = wxEmptyString)
        : pxTextWrapperBase(wrapPrefix)
    {
    }

    virtual ~pxTextWrapper() = default;

    const wxString &GetResult() const
    {
        return m_text;
    }

    pxTextWrapper &Wrap(const wxWindow &win, const wxString &text, int widthMax);
    pxTextWrapper &Wrap(const wxWindow *win, const wxString &text, int widthMax);

protected:
    void OnOutputLine(const wxString &line);
    void OnNewLine();
};

// --------------------------------------------------------------------------------------
//  pxWindowTextWriter
// --------------------------------------------------------------------------------------
class pxWindowTextWriter
{
protected:
    wxDC &m_dc;

    wxAlignment m_align;
    wxPoint m_curpos;
    int m_leading;

    virtual void _DoWriteLn(const wxString &msg);
    void _DoWrite(const wxChar *msg);

public:
    pxWindowTextWriter(wxDC &dc);
    virtual ~pxWindowTextWriter() = default;

    virtual void OnFontChanged();

    pxWindowTextWriter &WriteLn();
    pxWindowTextWriter &FormatLn(const wxChar *fmt, ...);
    pxWindowTextWriter &WriteLn(const wxChar *fmt);
    pxWindowTextWriter &SetFont(const wxFont &font);
    pxWindowTextWriter &Align(const wxAlignment &align);
    pxWindowTextWriter &WriteLn(const wxString fmt);

    pxWindowTextWriter &SetLeading(int lead)
    {
        m_leading = lead;
        return *this;
    }

    pxWindowTextWriter &SetWeight(wxFontWeight weight);
    pxWindowTextWriter &SetStyle(wxFontStyle style);
    pxWindowTextWriter &Normal();

    pxWindowTextWriter &Bold()
    {
        return SetWeight(wxFONTWEIGHT_BOLD);
    }

    pxWindowTextWriter &Italic()
    {
        return SetStyle(wxFONTSTYLE_ITALIC);
    }

    pxWindowTextWriter &SetPos(const wxPoint &pos);
    pxWindowTextWriter &MovePos(const wxSize &delta);

    pxWindowTextWriter &SetPos(int xpos, int ypos)
    {
        return SetPos(wxPoint(xpos, ypos));
    }

    pxWindowTextWriter &MovePos(int xdelta, int ydelta)
    {
        return MovePos(wxSize(xdelta, ydelta));
    }

    pxWindowTextWriter &SetY(int ypos);
    pxWindowTextWriter &MoveY(int ydelta);
};

// --------------------------------------------------------------------------------------
//  MoreStockCursors
// --------------------------------------------------------------------------------------
// Because (inexplicably) the ArrowWait cursor isn't in wxWidgets stock list.
//
class MoreStockCursors
{
protected:
    std::unique_ptr<wxCursor> m_arrowWait;

public:
    MoreStockCursors() {}
    virtual ~MoreStockCursors() = default;
    const wxCursor &GetArrowWait();
};

enum BusyCursorType {
    Cursor_NotBusy,
    Cursor_KindaBusy,
    Cursor_ReallyBusy,
};

extern MoreStockCursors StockCursors;

// --------------------------------------------------------------------------------------
//  ScopedBusyCursor
// --------------------------------------------------------------------------------------
// ... because wxWidgets wxBusyCursor doesn't really do proper nesting (doesn't let me
// override a partially-busy cursor with a really busy one)

class ScopedBusyCursor
{
protected:
    static std::stack<BusyCursorType> m_cursorStack;
    static BusyCursorType m_defBusyType;

public:
    ScopedBusyCursor(BusyCursorType busytype);
    virtual ~ScopedBusyCursor();

    static void SetDefault(BusyCursorType busytype);
    static void SetManualBusyCursor(BusyCursorType busytype);
};

// --------------------------------------------------------------------------------------
//  pxFitToDigits
// --------------------------------------------------------------------------------------
// Fits a given text or spinner control to the number of digits requested, since by default
// they're usually way over-sized.

extern void pxFitToDigits(wxWindow *win, int digits);
extern void pxFitToDigits(wxSpinCtrl *win, int digits);
extern wxTextCtrl *CreateNumericalTextCtrl(wxWindow *parent, int digits, long flags = wxTE_RIGHT);


//////////////////////////////////////////////////////////////////////////////////////////////

extern bool pxDialogExists(const wxString &name);
extern bool pxIsValidWindowPosition(const wxWindow &window, const wxPoint &windowPos);
extern wxRect wxGetDisplayArea();
extern wxString pxGetAppName();

extern int pxGetCharHeight(const wxWindow *wind, int rows = 1);
extern int pxGetCharHeight(const wxWindow &wind, int rows = 1);

extern void pxSetToolTip(wxWindow *wind, const wxString &src);
extern void pxSetToolTip(wxWindow &wind, const wxString &src);
extern wxFont pxGetFixedFont(int ptsize = 8, wxFontWeight weight = wxFONTWEIGHT_NORMAL, bool underline = false);

extern pxDialogCreationFlags pxDialogFlags();

#endif
