/////////////////////////////////////////////////////////////////////////////
// Name:        classlist.cpp
// Purpose:     ClassListDialog implementation
// Author:      Francesco Montorsi
// Created:     03/06/2007 14:49:55
// Copyright:   (c) 2007 Francesco Montorsi
// Licence:     wxWindows licence
/////////////////////////////////////////////////////////////////////////////


// ----------------------------------------------------------------------------
// headers
// ----------------------------------------------------------------------------

// For compilers that support precompilation, includes "wx/wx.h".
#include "wx/wxprec.h"


#ifndef WX_PRECOMP
#include "wx/wx.h"
#endif

#include "classlist.h"

#if !wxUSE_EXTENDED_RTTI
    #error This sample requires XTI (eXtended RTTI) enabled
#endif

// wxIMPLEMENT_DYNAMIC_CLASS(ClassListDialog, wxDialog);  -- see the header
wxBEGIN_EVENT_TABLE( ClassListDialog, wxDialog )
    EVT_LISTBOX( ID_LISTBOX, ClassListDialog::OnListboxSelected )
    EVT_TREE_SEL_CHANGED( ID_TREECTRL, ClassListDialog::OnTreectrlSelChanged )
    EVT_CHOICEBOOK_PAGE_CHANGED( ID_LISTMODE, ClassListDialog::OnChoiceBookPageChange )

    EVT_CHECKBOX( ID_SHOW_ONLY_XTI, ClassListDialog::OnShowOnlyXTICheckbox )
    EVT_CHECKBOX( ID_SHOW_PROPERTIES_RECURSIVELY, ClassListDialog::OnShowRecursiveInfoCheckbox )
wxEND_EVENT_TABLE()

// defined later
wxString DumpClassInfo(const wxClassInfo*, bool recursive);


// ----------------------------------------------------------------------------
// ClassListDialog
// ----------------------------------------------------------------------------

ClassListDialog::ClassListDialog()
{
    Init();
}

ClassListDialog::ClassListDialog( wxWindow* parent, wxWindowID id,
                                      const wxString& caption, const wxPoint& pos,
                                      const wxSize& size, long style )
{
    Init();
    Create(parent, id, caption, pos, size, style);
}

bool ClassListDialog::Create( wxWindow* parent, wxWindowID id, const wxString& caption,
                                const wxPoint& pos, const wxSize& size, long style )
{
    SetExtraStyle(wxWS_EX_BLOCK_EVENTS);
    wxDialog::Create( parent, id, caption, pos, size, style );

    CreateControls();
    if (GetSizer())
    {
        GetSizer()->SetSizeHints(this);
    }
    Centre();

    return true;
}

ClassListDialog::~ClassListDialog()
{
}

void ClassListDialog::Init()
{
    m_pClassCountText = nullptr;
    m_pRawListBox = nullptr;
    m_pParentTreeCtrl = nullptr;
    m_pSizeListBox = nullptr;
    m_pTextCtrl = nullptr;
}

void ClassListDialog::CreateControls()
{
    wxBoxSizer* itemBoxSizer2 = new wxBoxSizer(wxVERTICAL);
    this->SetSizer(itemBoxSizer2);

    wxStaticText* itemStaticText3 = new wxStaticText( this, wxID_STATIC, _("This is the list of wxWidgets classes registered in the XTI system.\nNote that not all wxWidgets classes are registered nor all registered classes are completely _described_ using XTI metadata."), wxDefaultPosition, wxDefaultSize, 0 );
    itemBoxSizer2->Add(itemStaticText3, 0, wxALIGN_LEFT|wxALL, 5);

    // filters
    wxBoxSizer* filters = new wxBoxSizer(wxHORIZONTAL);
    itemBoxSizer2->Add(filters, 0, wxGROW|wxLEFT|wxRIGHT|wxBOTTOM, 5);
    filters->Add(new wxCheckBox(this, ID_SHOW_ONLY_XTI,
                                "Show only classes with eXtended infos"));
    filters->AddSpacer(10);
    filters->Add(new wxCheckBox(this, ID_SHOW_PROPERTIES_RECURSIVELY,
                                "Show properties of parent classes"));

    // show how many have we filtered out
    m_pClassCountText = new wxStaticText( this, wxID_STATIC,
                "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
                wxDefaultPosition, wxDefaultSize, 0 );
    m_pClassCountText->SetFont(wxFontInfo(8).Family(wxFONTFAMILY_SWISS).Bold());
    itemBoxSizer2->Add(m_pClassCountText, 0, wxALIGN_LEFT|wxLEFT|wxRIGHT|wxBOTTOM, 5);

    wxBoxSizer* itemBoxSizer5 = new wxBoxSizer(wxHORIZONTAL);
    itemBoxSizer2->Add(itemBoxSizer5, 1, wxGROW, 5);

    m_pChoiceBook = new wxChoicebook( this, ID_LISTMODE, wxDefaultPosition, wxDefaultSize, wxCHB_DEFAULT );

    // raw-list page
    wxPanel* itemPanel7 = new wxPanel( m_pChoiceBook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
    wxBoxSizer* itemBoxSizer8 = new wxBoxSizer(wxHORIZONTAL);
    itemPanel7->SetSizer(itemBoxSizer8);

    wxArrayString m_pRawListBoxStrings;
    m_pRawListBox = new wxListBox( itemPanel7, ID_LISTBOX, wxDefaultPosition, wxDefaultSize, m_pRawListBoxStrings, wxLB_SINGLE );
    itemBoxSizer8->Add(m_pRawListBox, 1, wxGROW, 5);

    m_pChoiceBook->AddPage(itemPanel7, _("Raw list"));

    // by-size page
    wxPanel* itemPanel13 = new wxPanel( m_pChoiceBook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSUNKEN_BORDER|wxTAB_TRAVERSAL );
    wxBoxSizer* itemBoxSizer14 = new wxBoxSizer(wxHORIZONTAL);
    itemPanel13->SetSizer(itemBoxSizer14);

    wxArrayString m_pSizeListBoxStrings;
    m_pSizeListBox = new wxListBox( itemPanel13, ID_LISTBOX, wxDefaultPosition, wxDefaultSize, m_pSizeListBoxStrings, wxLB_SINGLE );
    itemBoxSizer14->Add(m_pSizeListBox, 1, wxGROW, 5);

    m_pChoiceBook->AddPage(itemPanel13, _("Classes by size"));

    // tree page
    wxPanel* itemPanel10 = new wxPanel( m_pChoiceBook, ID_PANEL, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
    wxBoxSizer* itemBoxSizer11 = new wxBoxSizer(wxVERTICAL);
    itemPanel10->SetSizer(itemBoxSizer11);

    m_pParentTreeCtrl = new wxTreeCtrl( itemPanel10, ID_TREECTRL, wxDefaultPosition, wxSize(100, 100), wxTR_HAS_BUTTONS |wxTR_SINGLE );
    itemBoxSizer11->Add(m_pParentTreeCtrl, 1, wxGROW, 5);

    m_pChoiceBook->AddPage(itemPanel10, _("Classes by parent"));


    itemBoxSizer5->Add(m_pChoiceBook, 0, wxGROW|wxALL, 5);

    m_pTextCtrl = new wxTextCtrl( this, ID_TEXTCTRL, "", wxDefaultPosition, wxSize(500, -1), wxTE_MULTILINE|wxTE_READONLY );
    itemBoxSizer5->Add(m_pTextCtrl, 3, wxGROW|wxALL, 5);

    wxStdDialogButtonSizer* itemStdDialogButtonSizer17 = new wxStdDialogButtonSizer;

    itemBoxSizer2->Add(itemStdDialogButtonSizer17, 0, wxGROW|wxALL, 5);
    wxButton* itemButton18 = new wxButton( this, wxID_OK, _("&OK"), wxDefaultPosition, wxDefaultSize, 0 );
    itemStdDialogButtonSizer17->AddButton(itemButton18);

    wxButton* itemButton19 = new wxButton( this, wxID_CANCEL, _("&Cancel"), wxDefaultPosition, wxDefaultSize, 0 );
    itemStdDialogButtonSizer17->AddButton(itemButton19);

    itemStdDialogButtonSizer17->Realize();

    InitControls();
}

int ClassListDialog::AddClassesWithParent(const wxClassInfo *parent, const wxTreeItemId &id)
{
    const wxClassInfo *ci = wxClassInfo::GetFirst();
    int count = 0;
    while (ci)
    {
        // is this class derived from the given parent?
        if (wxString(ci->GetBaseClassName1()) == parent->GetClassName() ||
            wxString(ci->GetBaseClassName2()) == parent->GetClassName())
        {
            wxTreeItemId child = m_pParentTreeCtrl->AppendItem(id, ci->GetClassName());

            // update the name of this child with the count of the children classes
            int ret = AddClassesWithParent(ci, child);
            m_pParentTreeCtrl->SetItemText(child,
                                     m_pParentTreeCtrl->GetItemText(child) +
                                     wxString::Format(" [%d]", ret));
            count += ret+1;
        }

        ci = ci->GetNext();
    }

    // reorder all the children we've just added
    m_pParentTreeCtrl->SortChildren(id);

    return count;
}

int GetSizeOfClass(const wxString &cn)
{
    const wxClassInfo *ci = wxClassInfo::FindClass(cn);
    if (ci)
        return ci->GetSize();
    return 0;
}

int CompareClassSizes(const wxString &class1, const wxString &class2)
{
    return GetSizeOfClass(class1) - GetSizeOfClass(class2);
}

void ClassListDialog::InitControls()
{
    // create a wxArrayString with the names of all classes:
    const wxClassInfo *ci = wxClassInfo::GetFirst();
    wxArrayString arr;
    while (ci)
    {
        arr.Add(ci->GetClassName());
        ci = ci->GetNext();
    }

    arr.Sort();     // sort alphabetically

    // now add it to the raw-mode listbox
    for (unsigned int i=0; i<arr.GetCount(); i++)
        if (!IsToDiscard(arr[i]))
            m_pRawListBox->Append(arr[i]);
    m_nCount = m_pRawListBox->GetCount();

    // sort again using size as sortkey
    arr.Sort((wxArrayString::CompareFunction)CompareClassSizes);

    // now add it to the size-mode listbox
    for (unsigned int i=0; i<arr.GetCount(); i++)
        if (!IsToDiscard(arr[i]))
            m_pSizeListBox->Append(arr[i]);

    // add root item to parent-mode treectrl
    wxTreeItemId id = m_pParentTreeCtrl->AddRoot("wxObject");

    // recursively add all leaves to the treectrl
    int count = AddClassesWithParent(CLASSINFO(wxObject), id);
    m_pParentTreeCtrl->SetItemText(id, m_pParentTreeCtrl->GetItemText(id) +
                                 wxString::Format(" [%d]", count));

    // initially expand the root item
    m_pParentTreeCtrl->Expand(id);

    m_nTotalCount = arr.GetCount();
    UpdateFilterText();

    // don't leave blank the XTI info display
    m_pChoiceBook->ChangeSelection(0);
    m_pRawListBox->Select(0);
    UpdateClassInfo(m_pRawListBox->GetStringSelection());
}

bool ClassListDialog::IsToDiscard(const wxString &classname) const
{
    wxCheckBox *cb = static_cast<wxCheckBox*>(FindWindow(ID_SHOW_ONLY_XTI));
    if (!cb || !cb->IsChecked())
        return false;

    // check if this class has XTI infos
    wxClassInfo *info = wxClassInfo::FindClass(classname);
    if (!info)
        return false;
    if (info->GetFirstProperty() != nullptr || info->GetFirstHandler() != nullptr)
        return false;       // has XTI info
    return true;            // no XTI info
}

void ClassListDialog::UpdateFilterText()
{
    // tell the user how many registered classes are present and
    // how many are we showing
    m_pClassCountText->SetLabel(
        wxString::Format(
            "Showing %d classes on a total of %d registered classes in wxXTI.",
            m_nCount, m_nTotalCount));
}

void ClassListDialog::UpdateClassInfo(const wxString &itemName)
{
    wxString classname = itemName.BeforeFirst(' ');
    wxCheckBox *cb = static_cast<wxCheckBox*>(FindWindow(ID_SHOW_PROPERTIES_RECURSIVELY));

    m_pTextCtrl->SetValue(
        DumpClassInfo(wxClassInfo::FindClass(classname), cb->IsChecked()));
}


// ----------------------------------------------------------------------------
// ClassListDialog - event handlers
// ----------------------------------------------------------------------------

void ClassListDialog::OnShowOnlyXTICheckbox( wxCommandEvent& WXUNUSED(event) )
{
    m_pRawListBox->Clear();
    m_pParentTreeCtrl->DeleteAllItems();
    m_pSizeListBox->Clear();

    InitControls();
}

void ClassListDialog::OnShowRecursiveInfoCheckbox( wxCommandEvent& WXUNUSED(event) )
{
    m_pRawListBox->Clear();
    m_pParentTreeCtrl->DeleteAllItems();
    m_pSizeListBox->Clear();

    InitControls();
}

void ClassListDialog::OnListboxSelected( wxCommandEvent& event )
{
    UpdateClassInfo(event.GetString());
}

void ClassListDialog::OnTreectrlSelChanged( wxTreeEvent& event )
{
    UpdateClassInfo(m_pParentTreeCtrl->GetItemText(event.GetItem()));
}

void ClassListDialog::OnChoiceBookPageChange( wxChoicebookEvent& event )
{
    switch (event.GetSelection())
    {
    case 0:
        if (m_pRawListBox->GetCount())
        {
            m_pRawListBox->Select(0);
            UpdateClassInfo(m_pRawListBox->GetStringSelection());
        }
        break;
    case 1:
        if (m_pSizeListBox->GetCount())
        {
            m_pSizeListBox->Select(0);
            UpdateClassInfo(m_pSizeListBox->GetStringSelection());
        }
        break;
    case 2:
        {
            wxTreeItemId root = m_pParentTreeCtrl->GetRootItem();
            if (root.IsOk())
            {
                m_pParentTreeCtrl->SelectItem(root);
                UpdateClassInfo(m_pParentTreeCtrl->GetItemText(root));
            }
        }
        break;
    }
}



// ----------------------------------------------------------------------------
// dump functions
// ----------------------------------------------------------------------------

wxString DumpStr(const wxString &str)
{
    if (str.empty())
        return "none";
    return str;
}

wxString DumpTypeInfo(const wxTypeInfo *ti)
{
    if (!ti)
        return "none";

    return DumpStr(ti->GetTypeName());
}

wxString DumpPropertyAccessor(const wxPropertyAccessor *acc, int indent)
{
    wxString ind = "\n" + wxString(indent, ' ');
    wxString infostr;

    if (!acc)
        return ind + "no property accessors";

    if (acc->HasSetter())
        infostr << ind << "setter name: " << acc->GetSetterName();
    if (acc->HasCollectionGetter())
        infostr << ind << "collection getter name: " << acc->GetCollectionGetterName();
    if (acc->HasGetter())
        infostr << ind << "getter name: " << acc->GetGetterName();
    if (acc->HasAdder())
        infostr << ind << "adder name: " << acc->GetAdderName();

    return infostr;
}

wxString DumpPropertyInfo(const wxPropertyInfo *prop, int indent)
{
    wxString ind = "\n" + wxString(indent, ' ');
    wxString infostr;

    if (!prop)
        return ind + "none";

    infostr << ind << "flags: ";
    if (prop->GetFlags() & wxPROP_DEPRECATED)
        infostr << "wxPROP_DEPRECATED,";
    if (prop->GetFlags() & wxPROP_OBJECT_GRAPH)
        infostr << "wxPROP_OBJECT_GRAPH,";
    if (prop->GetFlags() & wxPROP_ENUM_STORE_LONG)
        infostr << "wxPROP_ENUM_STORE_LONG,";
    if (prop->GetFlags() & wxPROP_DONT_STREAM)
        infostr << "wxPROP_DONT_STREAM,";

    if (prop->GetFlags() == 0)
        infostr << "none";
    else
        infostr.RemoveLast();       // remove last comma

    infostr << ind << "help string: " << DumpStr(prop->GetHelpString());
    infostr << ind << "group string: " << DumpStr(prop->GetGroupString());

    infostr << ind << "collection element type: " << DumpTypeInfo(prop->GetCollectionElementTypeInfo());
    infostr << ind << "type: " << DumpTypeInfo(prop->GetTypeInfo());

    infostr << ind << "default value: " << DumpStr(wxAnyGetAsString(prop->GetDefaultValue()));
    infostr << DumpPropertyAccessor(prop->GetAccessor(), indent+1);

    return infostr;
}

wxString DumpHandlerInfo(const wxHandlerInfo *phdlr, int indent)
{
    wxString ind = "\n" + wxString(indent, ' ');
    wxString infostr;

    if (!phdlr)
        return ind + "none";

    infostr << ind << "event class: " <<
        (phdlr->GetEventClassInfo() ? wxString(phdlr->GetEventClassInfo()->GetClassName()) : "none");

    return infostr;
}

int DumpProperties(const wxClassInfo *info, wxString& infostr, bool recursive)
{
    const wxPropertyInfo *prop;
    int pcount;
    for (prop = info->GetFirstProperty(), pcount = 0;
         prop;
         prop = prop->GetNext(), pcount++)
    {
        infostr << "\n\n  [" << pcount+1 << "] Property: " << prop->GetName();
        infostr << DumpPropertyInfo(prop, 4);
    }

    if (pcount == 0)
        infostr << "\n None";

    if (recursive)
    {
        const wxClassInfo **parent = info->GetParents();
        wxString str;

        for (int i=0; parent[i] != nullptr; i++)
        {
            int ppcount = DumpProperties(parent[i], str, recursive);
            if (ppcount)
            {
                pcount += ppcount;
                infostr << "\n\n  " << parent[i]->GetClassName() << " PARENT'S PROPERTIES:";
                infostr << str;
            }
        }
    }

    return pcount;
}

int DumpHandlers(const wxClassInfo *info, wxString& infostr, bool recursive)
{
    const wxHandlerInfo *h;
    int hcount;
    for (h = info->GetFirstHandler(), hcount = 0;
         h;
         h = h->GetNext(), hcount++)
    {
        infostr << "\n\n  [" << hcount+1 << "] Handler: " << h->GetName();
        infostr << DumpHandlerInfo(h, 4);
    }

    if (hcount == 0)
        infostr << "\n None";

    if (recursive)
    {
        const wxClassInfo **parent = info->GetParents();
        wxString str;

        for (int i=0; parent[i] != nullptr; i++)
        {
            int hhcount = DumpHandlers(parent[i], str, recursive);
            if (hhcount)
            {
                hcount += hhcount;
                infostr << "\n\n  " << parent[i]->GetClassName() << " PARENT'S HANDLERS:";
                infostr << str;
            }
        }
    }

    return hcount;
}

wxString DumpClassInfo(const wxClassInfo *info, bool recursive)
{
    wxString infostr;

    if (!info)
        return wxEmptyString;

    // basic stuff:

    infostr << "\n BASIC RTTI INFO ABOUT " << info->GetClassName();
    infostr << "\n =================================================";
    infostr << "\n  Base class #1: " << DumpStr(info->GetBaseClassName1());
    infostr << "\n  Base class #2: " << DumpStr(info->GetBaseClassName2());
    infostr << "\n  Include file: " << DumpStr(info->GetIncludeName());
    infostr << "\n  Size: " << info->GetSize();
    infostr << "\n  Dynamic: " << (info->IsDynamic() ? "true" : "false");


    // advanced stuff:

    infostr << "\n\n\n ADVANCED RTTI INFO ABOUT " << info->GetClassName();
    infostr << "\n =================================================\n";
    infostr << "\n PROPERTIES";
    infostr << "\n -----------------------------------------";
    int pcount = DumpProperties(info, infostr, recursive);
    infostr << "\n\n HANDLERS";
    infostr << "\n -----------------------------------------";
    int hcount = DumpHandlers(info, infostr, recursive);

    if (pcount+hcount == 0)
        infostr << "\n\n no advanced info\n";
    else
    {
        infostr << "\n\n Total count of properties: " << pcount;
        infostr << "\n Total count of handlers: " << hcount << "\n";
    }

    return infostr;
}
