#ifndef CL_GENERIC_NOTEBOOK_HPP
#define CL_GENERIC_NOTEBOOK_HPP

#include "bitmap_loader.h"
#include "clTabHistory.h"
#include "clTabRenderer.h"
#include "clThemedButton.h"
#include "cl_command_event.h"
#include "windowstack.h"

#include <list>
#include <vector>
#include <wx/bookctrl.h>
#include <wx/dcmemory.h>
#include <wx/dnd.h>
#include <wx/dynarray.h>
#include <wx/notebook.h>
#include <wx/panel.h>
#include <wx/settings.h>
#include <wx/sharedptr.h>

class clGenericNotebook;
class wxMenu;
class clTabCtrl;

enum class eDirection {
    kInvalid = -1,
    kRight = 0,
    kLeft = 1,
    kUp = 2,
    kDown = 3,
};

// DnD support of tabs
class WXDLLIMPEXP_SDK clTabCtrlDropTarget : public wxTextDropTarget
{
    clTabCtrl* m_tabCtrl;
    clGenericNotebook* m_notebook;

public:
    clTabCtrlDropTarget(clTabCtrl* tabCtrl);
    clTabCtrlDropTarget(clGenericNotebook* notebook);
    virtual ~clTabCtrlDropTarget();
    virtual bool OnDropText(wxCoord x, wxCoord y, const wxString& data);
};

/**
 * @class clTabCtrl
 * @author Eran Ifrah
 * @brief The Window that all the tabs are drawn on
 */
class WXDLLIMPEXP_SDK clTabCtrl : public wxPanel
{
    int m_nHeight;
    int m_nWidth;

    clTabInfo::Vec_t m_tabs;
    friend class clGenericNotebook;
    friend class clTabCtrlDropTarget;

    size_t m_style;
    clTabColours m_colours;
    clTabInfo::Vec_t m_visibleTabs;
    int m_closeButtonClickedIndex;
    wxMenu* m_contextMenu;
    wxRect m_chevronRect;
    clTabHistory::Ptr_t m_history;
    clTabRenderer::Ptr_t m_art;

    wxDateTime m_dragStartTime;
    wxPoint m_dragStartPos;

    clBitmapList* m_bitmaps = nullptr;
    clButton* m_fileListButton = nullptr;
    clButton* m_actionButton = nullptr;

protected:
    void DoChangeSelection(size_t index);
    void PositionFilelistButton();

protected:
    void OnActivateApp(wxActivateEvent& e);
    void OnPaint(wxPaintEvent& e);
    void OnEraseBG(wxEraseEvent& e);
    void OnSize(wxSizeEvent& event);
    void OnLeftDown(wxMouseEvent& event);
    void OnRightUp(wxMouseEvent& event);
    void OnLeftUp(wxMouseEvent& event);
    void OnLeftDClick(wxMouseEvent& event);
    void OnMouseMotion(wxMouseEvent& event);
    void OnMouseMiddleClick(wxMouseEvent& event);
    void OnMouseScroll(wxMouseEvent& event);
    void OnContextMenu(wxContextMenuEvent& event);
    int DoGetPageIndex(wxWindow* win) const;
    int DoGetPageIndex(const wxString& label) const;
    void DoDrawBottomBox(clTabInfo::Ptr_t activeTab, const wxRect& clientRect, wxDC& dc, const clTabColours& colours);
    bool ShiftRight(clTabInfo::Vec_t& tabs);
    bool ShiftBottom(clTabInfo::Vec_t& tabs);
    bool IsActiveTabInList(const clTabInfo::Vec_t& tabs) const;
    bool IsActiveTabVisible(const clTabInfo::Vec_t& tabs) const;

    /**
     * @brief loop over the tabs and set their coordiantes
     */
    void DoUpdateCoordiantes(clTabInfo::Vec_t& tabs);
    /**
     * @brief get list of tabs to draw. This call always returns the active tab as part of the list
     * It also ensures that we draw as much tabs as we can.
     * @param offset reset the 0 based index from m_tabs
     */
    void UpdateVisibleTabs(bool forceReshuffle = false);

    /**
     * @brief calculate and set the tab ctrl size
     */
    void DoSetBestSize();

    clTabInfo::Ptr_t GetTabInfo(size_t index);
    clTabInfo::Ptr_t GetTabInfo(size_t index) const;
    clTabInfo::Ptr_t GetTabInfo(wxWindow* page);
    clTabInfo::Ptr_t GetActiveTabInfo();

    WindowStack* GetStack();

    void DoDeletePage(size_t page) { RemovePage(page, true, true); }
    void DoShowTabList();
    void DoUpdateXCoordFromPage(wxWindow* page, int diff);

    void OnBeginDrag();

public:
    clTabCtrl(wxWindow* notebook, size_t style);
    virtual ~clTabCtrl();

    /// bitmaps mangement
    clBitmapList* GetBitmaps() const { return m_bitmaps; }

    /**
     * @brief return the art class used by this tab control
     */
    clTabRenderer::Ptr_t GetArt() { return m_art; }

    /**
     * @brief replace the art used by this tab control
     */
    void SetArt(clTabRenderer::Ptr_t art);

    bool IsVerticalTabs() const;

    const clTabColours& GetColours() const { return m_colours; }

    /**
     * @brief test if pt is on one of the visible tabs return its index
     * @param pt mouse click position
     * @param realPosition [output] the index position in the m_tabs array
     * @param tabHit [output] the index position in the m_visibleTabs array
     * @param leftSide [output] if the point is on the LEFT side of the tab's rect, then return wxALIGN_LEFT, otherwise
     * return wxALIGN_RIGHT. Another possible value is wxALIGN_INVALID
     */
    void TestPoint(const wxPoint& pt, int& realPosition, int& tabHit, eDirection& align);

    /**
     * @brief Move the active tab to a new position
     * @param newIndex the new position. 0-based index in the m_tabs array
     */
    bool MoveActiveToIndex(int newIndex, eDirection direction);

    /**
     * @brief return true if index is in the tabs vector range
     */
    bool IsIndexValid(size_t index) const;

    void SetStyle(size_t style);
    size_t GetStyle() const { return m_style; }

    /**
     * @brief update the selected tab. This function also fires an event
     */
    int SetSelection(size_t tabIdx);

    /**
     * @brief update the selected tab. This function does not fire an event
     */
    int ChangeSelection(size_t tabIdx);

    /**
     * @brief return the current selection. return wxNOT_FOUND if non is selected
     */
    int GetSelection() const;

    /**
     * @brief Sets the text for the given page.
     */
    bool SetPageText(size_t page, const wxString& text);
    wxString GetPageText(size_t page) const;

    void SetPageModified(size_t page, bool modified);
    bool IsModified(size_t page) const;

    void AddPage(clTabInfo::Ptr_t tab);
    bool InsertPage(size_t index, clTabInfo::Ptr_t tab);

    void SetPageBitmap(size_t index, int bitmapId);
    const wxBitmap& GetPageBitmap(size_t index) const;
    int GetPageBitmapIndex(size_t index) const;
    wxWindow* GetPage(size_t index) const;
    void GetAllPages(std::vector<wxWindow*>& pages);
    int FindPage(wxWindow* page) const;
    bool RemovePage(size_t page, bool notify, bool deletePage);
    bool DeleteAllPages();
    void SetMenu(wxMenu* menu);
    bool SetPageToolTip(size_t page, const wxString& tooltip);
    const clTabInfo::Vec_t& GetTabs() const { return m_tabs; }
    clTabHistory::Ptr_t GetHistory() const { return m_history; }
};

/**
 * @class clGenericNotebook
 * @author Eran Ifrah
 * @brief A modern notebook (similar to the ones seen on Sublime Text and Atom editors
 * for wxWidgets. The class implementation uses wxSimplebook as the tab container and a
 * custom drawing tab area (see above the class clTabCtrl)
 */
class WXDLLIMPEXP_SDK clGenericNotebook : public wxPanel
{
    WindowStack* m_windows;
    clTabCtrl* m_tabCtrl;
    friend class clTabCtrl;

protected:
    void DoChangeSelection(wxWindow* page);
    bool IsVerticalTabs() const { return m_tabCtrl->IsVerticalTabs(); }
    void OnSize(wxSizeEvent& event);
    void PositionControls();
    void OnPreferencesChanged(wxCommandEvent& event);
    void OnColoursChanged(clCommandEvent& event);

public:
    /**
     * Constructor
     */
    clGenericNotebook(wxWindow* parent, wxWindowID id = wxID_ANY, const wxPoint& pos = wxDefaultPosition,
                      const wxSize& size = wxDefaultSize, long style = 0, const wxString& name = wxEmptyString);

    /// bitmaps mangement
    clBitmapList* GetBitmaps() const { return m_tabCtrl->GetBitmaps(); }

    /**
     * @brief update the notebook art class and refresh
     */
    void SetArt(clTabRenderer::Ptr_t art) { m_tabCtrl->SetArt(art); }

    /**
     * @brief set the notebook style. The style bits are kNotebook_* (you can set several
     * styles OR-ed)
     */
    void SetStyle(size_t style);

    /**
     * @brief set the tab direction
     */
    void SetTabDirection(wxDirection d);

    /**
     * @brief return the book style
     */
    size_t GetStyle() const { return m_tabCtrl->GetStyle(); }

    /**
     * @brief enable a specific style in the notebook
     */
    void EnableStyle(NotebookStyle style, bool enable);

    /**
     * destructor
     */
    virtual ~clGenericNotebook();

    /**
     * @brief append page to the notebook
     */
    void AddPage(wxWindow* page, const wxString& label, bool selected = false, int bitmapId = wxNOT_FOUND,
                 const wxString& shortLabel = wxEmptyString);

    /**
     * @brief insert page at a specified position
     */
    bool InsertPage(size_t index, wxWindow* page, const wxString& label, bool selected = false,
                    int bitmapId = wxNOT_FOUND, const wxString& shortLabel = wxEmptyString);

    /**
     * @brief return the currently selected page or null
     */
    wxWindow* GetCurrentPage() const;

    /**
     * @brief Returns the index of the specified tab window or wxNOT_FOUND if not found
     */
    int FindPage(wxWindow* page) const;

    /**
     * @brief Deletes the specified page, without deleting the associated window
     */
    bool RemovePage(size_t page, bool notify = false);

    /**
     * @brief Deletes the specified page and the associated window
     */
    bool DeletePage(size_t page, bool notify = true);

    /**
     * @brief Deletes all pages
     */
    bool DeleteAllPages();

    /**
     * @brief set a new selection. This function fires an event that can be vetoed
     */
    int SetSelection(size_t selection) { return m_tabCtrl->SetSelection(selection); }
    /**
     * @brief set new selection. No events are fired
     */
    int ChangeSelection(size_t selection) { return m_tabCtrl->ChangeSelection(selection); }

    /**
     * @brief return the currently selected page, return wxNOT_FOUND if non found
     */
    int GetSelection() const { return m_tabCtrl->GetSelection(); }

    /**
     * @brief Sets the text for the given page.
     */
    bool SetPageText(size_t page, const wxString& text) { return m_tabCtrl->SetPageText(page, text); }

    /**
     * @brief Returns the string for the given page
     */
    wxString GetPageText(size_t page) const { return m_tabCtrl->GetPageText(page); }

    void SetPageModified(size_t page, bool modified) { m_tabCtrl->SetPageModified(page, modified); }
    bool IsModified(size_t page) const { return m_tabCtrl->IsModified(page); }

    /**
     * @brief set the image for the given page
     */
    void SetPageBitmap(size_t index, int bitmapId) { m_tabCtrl->SetPageBitmap(index, bitmapId); }

    /**
     * @brief return bitmap for a given page. Return wxNullBitmap if invalid page
     */
    const wxBitmap& GetPageBitmap(size_t index) const { return m_tabCtrl->GetPageBitmap(index); }

    /**
     * @brief return bitmap for a given page. Return wxNullBitmap if invalid page
     */
    int GetPageBitmapIndex(size_t index) const { return m_tabCtrl->GetPageBitmapIndex(index); }

    // Base class members...
    virtual bool SetPageImage(size_t page, int image)
    {
        wxUnusedVar(page);
        wxUnusedVar(image);
        return false;
    }
    virtual int GetPageImage(size_t n) const { return wxNOT_FOUND; }

    /**
     * @brief return the index of a given window in the tab control
     * @param window
     * @return return window index, or wxNOT_FOUND
     */
    int GetPageIndex(wxWindow* window) const { return m_tabCtrl->DoGetPageIndex(window); }

    /**
     * @brief return the index of a given window by its title
     */
    int GetPageIndex(const wxString& label) const { return m_tabCtrl->DoGetPageIndex(label); }

    /**
     * @brief Returns the number of pages in the control
     */
    size_t GetPageCount() const { return m_tabCtrl->GetTabs().size(); }

    /**
     * @brief Returns the window at the given page position.
     */
    wxWindow* GetPage(size_t index) const { return m_tabCtrl->GetPage(index); }

    /**
     * @brief return an array of all the windows managed by this notebook
     */
    void GetAllPages(std::vector<wxWindow*>& pages) { m_tabCtrl->GetAllPages(pages); }

    /**
     * @brief return all tabs info
     * @param tabs [output]
     */
    size_t GetAllTabs(clTabInfo::Vec_t& tabs)
    {
        tabs = m_tabCtrl->GetTabs();
        return tabs.size();
    }

    /**
     * @brief set a context menu to be shown whe context menu is requested
     * on a tab label
     */
    void SetMenu(wxMenu* menu) { m_tabCtrl->SetMenu(menu); }

    /**
     * @brief Sets the tool tip displayed when hovering over the tab label of the page
     * @return true if tool tip was updated, false if it failed, e.g. because the page index is invalid.
     */
    bool SetPageToolTip(size_t page, const wxString& tooltip) { return m_tabCtrl->SetPageToolTip(page, tooltip); }

    /**
     * @brief return the tabbing history
     * @return
     */
    clTabHistory::Ptr_t GetHistory() const { return m_tabCtrl->GetHistory(); }

    /**
     * @brief move the active page and place it in the new nexIndex
     */
    bool MoveActivePage(int newIndex);
};

wxDECLARE_EXPORTED_EVENT(WXDLLIMPEXP_SDK, wxEVT_BOOK_PAGE_CHANGING, wxBookCtrlEvent);
wxDECLARE_EXPORTED_EVENT(WXDLLIMPEXP_SDK, wxEVT_BOOK_PAGE_CHANGED, wxBookCtrlEvent);
wxDECLARE_EXPORTED_EVENT(WXDLLIMPEXP_SDK, wxEVT_BOOK_PAGE_CLOSING, wxBookCtrlEvent);
wxDECLARE_EXPORTED_EVENT(WXDLLIMPEXP_SDK, wxEVT_BOOK_PAGE_CLOSED, wxBookCtrlEvent);
wxDECLARE_EXPORTED_EVENT(WXDLLIMPEXP_SDK, wxEVT_BOOK_TAB_CONTEXT_MENU, wxBookCtrlEvent);
wxDECLARE_EXPORTED_EVENT(WXDLLIMPEXP_SDK, wxEVT_BOOK_PAGE_CLOSE_BUTTON, wxBookCtrlEvent);
wxDECLARE_EXPORTED_EVENT(WXDLLIMPEXP_SDK, wxEVT_BOOK_TAB_DCLICKED, wxBookCtrlEvent);
wxDECLARE_EXPORTED_EVENT(WXDLLIMPEXP_SDK, wxEVT_BOOK_NEW_PAGE, wxBookCtrlEvent);
wxDECLARE_EXPORTED_EVENT(WXDLLIMPEXP_SDK, wxEVT_BOOK_FILELIST_BUTTON_CLICKED, clContextMenuEvent);
#endif // CL_GENERIC_NOTEBOOK_HPP
