#ifndef slic3r_GUI_ObjectDataViewModel_hpp_
#define slic3r_GUI_ObjectDataViewModel_hpp_

#include <wx/dataview.h>

#include <vector>

namespace Slic3r {

enum class ModelVolumeType : int;

namespace GUI {

typedef double                          coordf_t;
typedef std::pair<coordf_t, coordf_t>   t_layer_height_range;

// ----------------------------------------------------------------------------
// DataViewBitmapText: helper class used by BitmapTextRenderer
// ----------------------------------------------------------------------------

class DataViewBitmapText : public wxObject
{
public:
    DataViewBitmapText( const wxString &text = wxEmptyString,
                        const wxBitmap& bmp = wxNullBitmap) :
        m_text(text),
        m_bmp(bmp)
    { }

    DataViewBitmapText(const DataViewBitmapText &other)
        : wxObject(),
        m_text(other.m_text),
        m_bmp(other.m_bmp)
    { }

    void SetText(const wxString &text)      { m_text = text; }
    wxString GetText() const                { return m_text; }
    void SetBitmap(const wxBitmap &bmp)     { m_bmp = bmp; }
    const wxBitmap &GetBitmap() const       { return m_bmp; }

    bool IsSameAs(const DataViewBitmapText& other) const {
        return m_text == other.m_text && m_bmp.IsSameAs(other.m_bmp);
    }

    bool operator==(const DataViewBitmapText& other) const {
        return IsSameAs(other);
    }

    bool operator!=(const DataViewBitmapText& other) const {
        return !IsSameAs(other);
    }

private:
    wxString    m_text;
    wxBitmap    m_bmp;

    wxDECLARE_DYNAMIC_CLASS(DataViewBitmapText);
};
DECLARE_VARIANT_OBJECT(DataViewBitmapText)

// ----------------------------------------------------------------------------
// BitmapTextRenderer
// ----------------------------------------------------------------------------
#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING
class BitmapTextRenderer : public wxDataViewRenderer
#else
class BitmapTextRenderer : public wxDataViewCustomRenderer
#endif //ENABLE_NONCUSTOM_DATA_VIEW_RENDERING
{
public:
    BitmapTextRenderer(wxWindow* parent,
        wxDataViewCellMode mode =
#ifdef __WXOSX__
        wxDATAVIEW_CELL_INERT
#else
        wxDATAVIEW_CELL_EDITABLE
#endif

        , int align = wxDVR_DEFAULT_ALIGNMENT
#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING
    );
#else
        ) :
    wxDataViewCustomRenderer(wxT("DataViewBitmapText"), mode, align),
        m_parent(parent)
    {}
#endif //ENABLE_NONCUSTOM_DATA_VIEW_RENDERING

    bool SetValue(const wxVariant& value);
    bool GetValue(wxVariant& value) const;
#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING && wxUSE_ACCESSIBILITY
    virtual wxString GetAccessibleDescription() const override;
#endif // wxUSE_ACCESSIBILITY && ENABLE_NONCUSTOM_DATA_VIEW_RENDERING

    virtual bool Render(wxRect cell, wxDC* dc, int state) override;
    virtual wxSize GetSize() const override;

    bool        HasEditorCtrl() const override
    {
#ifdef __WXOSX__
        return false;
#else
        return true;
#endif
    }
    wxWindow* CreateEditorCtrl(wxWindow* parent,
        wxRect labelRect,
        const wxVariant& value) override;
    bool        GetValueFromEditorCtrl(wxWindow* ctrl,
        wxVariant& value) override;
    bool        WasCanceled() const { return m_was_unusable_symbol; }

private:
    DataViewBitmapText  m_value;
    bool                m_was_unusable_symbol{ false };
    wxWindow* m_parent{ nullptr };
};


// ----------------------------------------------------------------------------
// BitmapChoiceRenderer
// ----------------------------------------------------------------------------

class BitmapChoiceRenderer : public wxDataViewCustomRenderer
{
public:
    BitmapChoiceRenderer(wxDataViewCellMode mode =
#ifdef __WXOSX__
        wxDATAVIEW_CELL_INERT
#else
        wxDATAVIEW_CELL_EDITABLE
#endif
        , int align = wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL
    ) : wxDataViewCustomRenderer(wxT("DataViewBitmapText"), mode, align) {}

    bool SetValue(const wxVariant& value);
    bool GetValue(wxVariant& value) const;

    virtual bool Render(wxRect cell, wxDC* dc, int state) override;
    virtual wxSize GetSize() const override;

    bool        HasEditorCtrl() const override { return true; }
    wxWindow* CreateEditorCtrl(wxWindow* parent,
        wxRect labelRect,
        const wxVariant& value) override;
    bool        GetValueFromEditorCtrl(wxWindow* ctrl,
        wxVariant& value) override;

private:
    DataViewBitmapText  m_value;
};


// ----------------------------------------------------------------------------
// ObjectDataViewModelNode: a node inside ObjectDataViewModel
// ----------------------------------------------------------------------------
enum ItemType {
    itUndef         = 0,
    itObject        = 1,
    itVolume        = 2,
    itInstanceRoot  = 4,
    itInstance      = 8,
    itSettings      = 16,
    itLayerRoot     = 32,
    itLayer         = 64,
};

enum ColumnNumber
{
    colName         = 0,    // item name
    colPrint           ,    // printable property
    colExtruder        ,    // extruder selection
    colEditing         ,    // item editing
};

enum PrintIndicator
{
    piUndef         = 0,    // no print indicator
    piPrintable        ,    // printable
    piUnprintable      ,    // unprintable
};

class ObjectDataViewModelNode;
WX_DEFINE_ARRAY_PTR(ObjectDataViewModelNode*, MyObjectTreeModelNodePtrArray);

class ObjectDataViewModelNode
{
    ObjectDataViewModelNode*	    m_parent;
    MyObjectTreeModelNodePtrArray   m_children;
    wxBitmap                        m_empty_bmp;
    size_t                          m_volumes_cnt = 0;
    std::vector< std::string >      m_opt_categories;
    t_layer_height_range            m_layer_range = { 0.0f, 0.0f };

    wxString				        m_name;
    wxBitmap&                       m_bmp = m_empty_bmp;
    ItemType				        m_type;
    int                             m_idx = -1;
    bool					        m_container = false;
    wxString				        m_extruder = "default";
    wxBitmap                        m_extruder_bmp;
    wxBitmap				        m_action_icon;
    PrintIndicator                  m_printable {piUndef};
    wxBitmap				        m_printable_icon;

    std::string                     m_action_icon_name = "";
    ModelVolumeType                 m_volume_type;

public:
    ObjectDataViewModelNode(const wxString& name,
                            const wxString& extruder):
        m_parent(NULL),
        m_name(name),
        m_type(itObject),
        m_extruder(extruder)
    {
        set_action_and_extruder_icons();
        init_container();
	}

    ObjectDataViewModelNode(ObjectDataViewModelNode* parent,
                            const wxString& sub_obj_name,
                            const wxBitmap& bmp,
                            const wxString& extruder,
                            const int idx = -1 ) :
        m_parent	(parent),
        m_name		(sub_obj_name),
        m_type		(itVolume),
        m_idx       (idx),
        m_extruder  (extruder)
    {
        m_bmp = bmp;
        set_action_and_extruder_icons();
        init_container();
    }

    ObjectDataViewModelNode(ObjectDataViewModelNode* parent,
                            const t_layer_height_range& layer_range,
                            const int idx = -1,
                            const wxString& extruder = wxEmptyString );

    ObjectDataViewModelNode(ObjectDataViewModelNode* parent, const ItemType type);

    ~ObjectDataViewModelNode()
    {
        // free all our children nodes
        size_t count = m_children.GetCount();
        for (size_t i = 0; i < count; i++)
        {
            ObjectDataViewModelNode *child = m_children[i];
            delete child;
        }
#ifndef NDEBUG
        // Indicate that the object was deleted.
        m_idx = -2;
#endif /* NDEBUG */
    }

	void init_container();
	bool IsContainer() const
	{
		return m_container;
	}

    ObjectDataViewModelNode* GetParent()
    {
        assert(m_parent == nullptr || m_parent->valid());
        return m_parent;
    }
    MyObjectTreeModelNodePtrArray& GetChildren()
    {
        return m_children;
    }
    ObjectDataViewModelNode* GetNthChild(unsigned int n)
    {
        return m_children.Item(n);
    }
    void Insert(ObjectDataViewModelNode* child, unsigned int n)
    {
        if (!m_container)
            m_container = true;
        m_children.Insert(child, n);
    }
    void Append(ObjectDataViewModelNode* child)
    {
        if (!m_container)
            m_container = true;
        m_children.Add(child);
    }
    void RemoveAllChildren()
    {
        if (GetChildCount() == 0)
            return;
        for (int id = int(GetChildCount()) - 1; id >= 0; --id)
        {
            if (m_children.Item(id)->GetChildCount() > 0)
                m_children[id]->RemoveAllChildren();
            auto node = m_children[id];
            m_children.RemoveAt(id);
            delete node;
        }
    }

    size_t GetChildCount() const
    {
        return m_children.GetCount();
    }

    bool            SetValue(const wxVariant &variant, unsigned int col);

    void            SetBitmap(const wxBitmap &icon) { m_bmp = icon; }
    const wxBitmap& GetBitmap() const               { return m_bmp; }
    const wxString& GetName() const                 { return m_name; }
    ItemType        GetType() const                 { return m_type; }
	void			SetIdx(const int& idx);
	int             GetIdx() const                  { return m_idx; }
	t_layer_height_range    GetLayerRange() const   { return m_layer_range; }
    PrintIndicator  IsPrintable() const             { return m_printable; }

    // use this function only for childrens
    void AssignAllVal(ObjectDataViewModelNode& from_node)
    {
        // ! Don't overwrite other values because of equality of this values for all children --
        m_name = from_node.m_name;
        m_bmp = from_node.m_bmp;
        m_idx = from_node.m_idx;
        m_extruder = from_node.m_extruder;
        m_type = from_node.m_type;
    }

    bool SwapChildrens(int frst_id, int scnd_id) {
        if (GetChildCount() < 2 ||
            frst_id < 0 || (size_t)frst_id >= GetChildCount() ||
            scnd_id < 0 || (size_t)scnd_id >= GetChildCount())
            return false;

        ObjectDataViewModelNode new_scnd = *GetNthChild(frst_id);
        ObjectDataViewModelNode new_frst = *GetNthChild(scnd_id);

        new_scnd.m_idx = m_children.Item(scnd_id)->m_idx;
        new_frst.m_idx = m_children.Item(frst_id)->m_idx;

        m_children.Item(frst_id)->AssignAllVal(new_frst);
        m_children.Item(scnd_id)->AssignAllVal(new_scnd);
        return true;
    }

    // Set action icons for node
    void        set_action_and_extruder_icons();
	// Set printable icon for node
    void        set_printable_icon(PrintIndicator printable);

    void        update_settings_digest_bitmaps();
    bool        update_settings_digest(const std::vector<std::string>& categories);
    int         volume_type() const { return int(m_volume_type); }
    void        msw_rescale();

#ifndef NDEBUG
    bool 		valid();
#endif /* NDEBUG */
    bool        invalid() const { return m_idx < -1; }

private:
    friend class ObjectDataViewModel;
};


// ----------------------------------------------------------------------------
// ObjectDataViewModel
// ----------------------------------------------------------------------------

// custom message the model sends to associated control to notify a last volume deleted from the object:
wxDECLARE_EVENT(wxCUSTOMEVT_LAST_VOLUME_IS_DELETED, wxCommandEvent);

class ObjectDataViewModel :public wxDataViewModel
{
    std::vector<ObjectDataViewModelNode*>       m_objects;
    std::vector<wxBitmap*>                      m_volume_bmps;
    wxBitmap*                                   m_warning_bmp { nullptr };

    wxDataViewCtrl*                             m_ctrl { nullptr };

public:
    ObjectDataViewModel();
    ~ObjectDataViewModel();

    wxDataViewItem Add( const wxString &name,
                        const int extruder,
                        const bool has_errors = false);
    wxDataViewItem AddVolumeChild(  const wxDataViewItem &parent_item,
                                    const wxString &name,
                                    const Slic3r::ModelVolumeType volume_type,
                                    const bool has_errors = false,
                                    const int extruder = 0,
                                    const bool create_frst_child = true);
    wxDataViewItem AddSettingsChild(const wxDataViewItem &parent_item);
    wxDataViewItem AddInstanceChild(const wxDataViewItem &parent_item, size_t num);
    wxDataViewItem AddInstanceChild(const wxDataViewItem &parent_item, const std::vector<bool>& print_indicator);
    wxDataViewItem AddLayersRoot(const wxDataViewItem &parent_item);
    wxDataViewItem AddLayersChild(  const wxDataViewItem &parent_item,
                                    const t_layer_height_range& layer_range,
                                    const int extruder = 0,
                                    const int index = -1);
    wxDataViewItem Delete(const wxDataViewItem &item);
    wxDataViewItem DeleteLastInstance(const wxDataViewItem &parent_item, size_t num);
    void DeleteAll();
    void DeleteChildren(wxDataViewItem& parent);
    void DeleteVolumeChildren(wxDataViewItem& parent);
    void DeleteSettings(const wxDataViewItem& parent);
    wxDataViewItem GetItemById(int obj_idx);
    wxDataViewItem GetItemById(const int obj_idx, const int sub_obj_idx, const ItemType parent_type);
    wxDataViewItem GetItemByVolumeId(int obj_idx, int volume_idx);
    wxDataViewItem GetItemByInstanceId(int obj_idx, int inst_idx);
    wxDataViewItem GetItemByLayerId(int obj_idx, int layer_idx);
    wxDataViewItem GetItemByLayerRange(const int obj_idx, const t_layer_height_range& layer_range);
    int  GetItemIdByLayerRange(const int obj_idx, const t_layer_height_range& layer_range);
    int  GetIdByItem(const wxDataViewItem& item) const;
    int  GetIdByItemAndType(const wxDataViewItem& item, const ItemType type) const;
    int  GetObjectIdByItem(const wxDataViewItem& item) const;
    int  GetVolumeIdByItem(const wxDataViewItem& item) const;
    int  GetInstanceIdByItem(const wxDataViewItem& item) const;
    int  GetLayerIdByItem(const wxDataViewItem& item) const;
    void GetItemInfo(const wxDataViewItem& item, ItemType& type, int& obj_idx, int& idx);
    int  GetRowByItem(const wxDataViewItem& item) const;
    bool IsEmpty() { return m_objects.empty(); }
    bool InvalidItem(const wxDataViewItem& item);

    // helper method for wxLog

    wxString    GetName(const wxDataViewItem &item) const;
    wxBitmap&   GetBitmap(const wxDataViewItem &item) const;
    wxString    GetExtruder(const wxDataViewItem &item) const;
    int         GetExtruderNumber(const wxDataViewItem &item) const;

    // helper methods to change the model

    virtual unsigned int    GetColumnCount() const override { return 3;}
    virtual wxString        GetColumnType(unsigned int col) const override{ return wxT("string"); }

    virtual void GetValue(  wxVariant &variant,
                            const wxDataViewItem &item,
                            unsigned int col) const override;
    virtual bool SetValue(  const wxVariant &variant,
                            const wxDataViewItem &item,
                            unsigned int col) override;
    bool SetValue(  const wxVariant &variant,
                    const int item_idx,
                    unsigned int col);

    void SetExtruder(const wxString& extruder, wxDataViewItem item);

    // For parent move child from cur_volume_id place to new_volume_id
    // Remaining items will moved up/down accordingly
    wxDataViewItem  ReorganizeChildren( const int cur_volume_id,
                                        const int new_volume_id,
                                        const wxDataViewItem &parent);
    wxDataViewItem  ReorganizeObjects( int current_id, int new_id);

    virtual bool    IsEnabled(const wxDataViewItem &item, unsigned int col) const override;

    virtual wxDataViewItem  GetParent(const wxDataViewItem &item) const override;
    // get object item
    wxDataViewItem          GetTopParent(const wxDataViewItem &item) const;
    virtual bool            IsContainer(const wxDataViewItem &item) const override;
    virtual unsigned int    GetChildren(const wxDataViewItem &parent,
                                        wxDataViewItemArray &array) const override;
    void GetAllChildren(const wxDataViewItem &parent,wxDataViewItemArray &array) const;
    // Is the container just a header or an item with all columns
    // In our case it is an item with all columns
    virtual bool    HasContainerColumns(const wxDataViewItem& WXUNUSED(item)) const override {	return true; }

    ItemType        GetItemType(const wxDataViewItem &item) const ;
    wxDataViewItem  GetItemByType(  const wxDataViewItem &parent_item,
                                    ItemType type) const;
    wxDataViewItem  GetSettingsItem(const wxDataViewItem &item) const;
    wxDataViewItem  GetInstanceRootItem(const wxDataViewItem &item) const;
    wxDataViewItem  GetLayerRootItem(const wxDataViewItem &item) const;
    bool    IsSettingsItem(const wxDataViewItem &item) const;
    void    UpdateSettingsDigest(   const wxDataViewItem &item,
                                    const std::vector<std::string>& categories);

    bool    IsPrintable(const wxDataViewItem &item) const;
    void    UpdateObjectPrintable(wxDataViewItem parent_item);
    void    UpdateInstancesPrintable(wxDataViewItem parent_item);

    void    SetVolumeBitmaps(const std::vector<wxBitmap*>& volume_bmps) { m_volume_bmps = volume_bmps; }
    void    SetWarningBitmap(wxBitmap* bitmap)                          { m_warning_bmp = bitmap; }
    void    SetVolumeType(const wxDataViewItem &item, const Slic3r::ModelVolumeType type);
    wxDataViewItem SetPrintableState( PrintIndicator printable, int obj_idx,
                                      int subobj_idx = -1, 
                                      ItemType subobj_type = itInstance);
    wxDataViewItem SetObjectPrintableState(PrintIndicator printable, wxDataViewItem obj_item);

    void    SetAssociatedControl(wxDataViewCtrl* ctrl) { m_ctrl = ctrl; }
    // Rescale bitmaps for existing Items
    void    Rescale();

    wxBitmap    GetVolumeIcon(const Slic3r::ModelVolumeType vol_type,
                              const bool is_marked = false);
    void        DeleteWarningIcon(const wxDataViewItem& item, const bool unmark_object = false);
    t_layer_height_range    GetLayerRangeByItem(const wxDataViewItem& item) const;

    bool        UpdateColumValues(unsigned col);
    void        UpdateExtruderBitmap(wxDataViewItem item);

private:
    wxDataViewItem  AddRoot(const wxDataViewItem& parent_item, const ItemType root_type);
    wxDataViewItem  AddInstanceRoot(const wxDataViewItem& parent_item);
    void            AddAllChildren(const wxDataViewItem& parent);
};


}
}


#endif // slic3r_GUI_ObjectDataViewModel_hpp_
