// ----------------------------------------------------------------------------
MenuItemDataMap_t clKeyboardManager::DoLoadDefaultAccelerators()
// ----------------------------------------------------------------------------
{
    MenuItemDataMap_t entries;
    wxFileName fnDefaultOldSettings(wxStandardPaths::Get().GetTempDir(), _T("keyMnuAccels.conf"));
    wxString personality = Manager::Get()->GetPersonalityManager()->GetPersonality();
    fnDefaultOldSettings.SetName(personality + _T(".") + fnDefaultOldSettings.GetName());

    if(fnDefaultOldSettings.FileExists())
    {
        wxString content;
        if(not ReadFileContent(fnDefaultOldSettings, content)) {
            return entries;
        }
        wxArrayString lines = ::wxStringTokenize(content, _T("\r\n"), wxTOKEN_STRTOK);
        for(size_t i = 0; i < lines.GetCount(); ++i)
        {
            wxArrayString parts = ::wxStringTokenize(lines.Item(i), _T("|"), wxTOKEN_RET_EMPTY);
            //-if(parts.GetCount() < 3) continue; //(pecan 2019/04/27)
            if(parts.GetCount() < 2) continue;
            MenuItemData binding;
            binding.resourceID = parts.Item(0);
            binding.parentMenu = parts.Item(1);
            if (parts.GetCount() > 2)               //(pecan 2019/04/27)
            binding.action = parts.Item(2);
            if(parts.GetCount() == 4) {
                binding.accel = parts.Item(3);
            }
            entries.insert(std::make_pair(binding.resourceID, binding));
        }
    }
    return entries;
}
// ----------------------------------------------------------------------------
void clKeyboardManager::Update(wxFrame* frame)
// ----------------------------------------------------------------------------
{
    // Since we keep the accelerators with their original resource ID in the form of string
    // we need to convert the map into a different integer with integer as the resource ID

    // Note that we place the items from the m_menuTable first and then we add the globals
    // this is because menu entries takes precedence over global accelerators
    MenuItemDataMap_t accels = m_menuTable;
    accels.insert(m_globalTable.begin(), m_globalTable.end());

    MenuItemDataIntMap_t intAccels;
    DoConvertToIntMap(accels, intAccels);

    if(!frame) {
        // update all frames
        wxFrame* topFrame = dynamic_cast<wxFrame*>(wxTheApp->GetTopWindow());
        CHECK_PTR_RET(topFrame);

        FrameList_t frames;
        DoGetFrames(topFrame, frames);
        for(FrameList_t::iterator iter = frames.begin(); iter != frames.end(); ++iter) {

            DoUpdateFrame(*iter, intAccels);
        }
    } else {
        // update only the requested frame
        DoUpdateFrame(frame, intAccels);
    }
}
// ----------------------------------------------------------------------------
void clKeyboardManager::GetAllAccelerators(MenuItemDataMap_t& accels) const
// ----------------------------------------------------------------------------
{
    accels.clear();
    accels.insert(m_menuTable.begin(), m_menuTable.end());
    accels.insert(m_globalTable.begin(), m_globalTable.end());
}
//// ----------------------------------------------------------------------------
//void clKeyboardManager::OnStartupCompleted(wxCommandEvent& event)
//// ----------------------------------------------------------------------------
//{
//    event.Skip();
//    this->Initialize();
//}
// ----------------------------------------------------------------------------
void clKeyboardManager::DoConvertToIntMap(const MenuItemDataMap_t& strMap, MenuItemDataIntMap_t& intMap)
// ----------------------------------------------------------------------------
{
    // Convert the string map into int based map
    MenuItemDataMap_t::const_iterator iter = strMap.begin();
    for(; iter != strMap.end(); ++iter)
    {
        wxString resourceIDStr = iter->second.resourceID;
        long lnResourceID; resourceIDStr.ToLong(&lnResourceID);
        //-intMap.insert(std::make_pair(wxXmlResource::GetXRCID(iter->second.resourceID), iter->second));
        intMap.insert(std::make_pair(lnResourceID, iter->second));
    }
}
//int clKeyboardManager::PopupNewKeyboardShortcutDlg(wxWindow* parent, MenuItemData& menuItemData)
//{
//    NewKeyShortcutDlg dlg(parent, menuItemData);
//    if(dlg.ShowModal() == wxID_OK) {
//        menuItemData.accel = dlg.GetAccel();
//        return wxID_OK;
//    }
//    return wxID_CANCEL;
//}
// ----------------------------------------------------------------------------
bool clKeyboardManager::Exists(const wxString& accel) const
// ----------------------------------------------------------------------------
{
    if(accel.IsEmpty()) return false;

    MenuItemDataMap_t accels;
    GetAllAccelerators(accels);

    MenuItemDataMap_t::const_iterator iter = accels.begin();
    for(; iter != accels.end(); ++iter) {
        if(iter->second.accel == accel) {
            return true;
        }
    }
    return false;
}
void EditSnippetsDlg::DoItemSelected(const wxString& text)
{
    m_textCtrlMenuEntry->SetValue(text);
    m_textCtrlSnippet->SetValue(GetStringDb()->GetSnippetString(text));
    MenuItemDataMap_t accelMap;
    clKeyboardManager::Get()->GetAllAccelerators(accelMap);
    if(text.IsEmpty()) {
        return;
    }

    m_textCtrlAccelerator->SetValue(wxT(""));
    MenuItemDataMap_t::iterator iter = accelMap.begin();
    for(; iter != accelMap.end(); ++iter) {
        MenuItemData mid = iter->second;
        if(mid.action == text) {
            m_textCtrlAccelerator->SetValue(mid.accel);
        }
    }
}
// ----------------------------------------------------------------------------
void clKeyboardManager::SetAccelerators(const MenuItemDataMap_t& accels)
// ----------------------------------------------------------------------------
{
    // separate the globals from the menu accelerators
    // The process is done by checking each item's parentMenu
    // If the parentMenu is empty -> global accelerator
    MenuItemDataMap_t globals, menus;
    MenuItemDataMap_t::const_iterator iter = accels.begin();
    for(; iter != accels.end(); ++iter) {
        if(iter->second.parentMenu.IsEmpty()) {
            globals.insert(std::make_pair(iter->first, iter->second));
        } else {
            menus.insert(std::make_pair(iter->first, iter->second));
        }
    }

    m_menuTable.swap(menus);
    m_globalTable.swap(globals);
    Update();
    Save();
}
// ----------------------------------------------------------------------------
wxArrayString clKeyboardManager::GetAllUnasignedKeyboardShortcuts() const
// ----------------------------------------------------------------------------
{
    MenuItemDataMap_t accels;
    GetAllAccelerators(accels);

    wxStringSet_t usedShortcuts;
    std::for_each(accels.begin(), accels.end(), [&](const std::pair<wxString, MenuItemData>& p) {
        if(!p.second.accel.IsEmpty()) {
            usedShortcuts.insert(p.second.accel);
        }
    });

    // Remove all duplicate entries
    wxArrayString allUnasigned;
    std::set_difference(m_allShorcuts.begin(),
                        m_allShorcuts.end(),
                        usedShortcuts.begin(),
                        usedShortcuts.end(),
                        std::back_inserter(allUnasigned));
    return allUnasigned;
}
void AccelTableDlg::PopulateTable(const wxString& filter)
{
    m_dvListCtrl->DeleteAllItems();

    MenuItemDataMap_t filteredMap;
    if(filter.IsEmpty()) {
        filteredMap = m_accelMap;
    } else {
        for(MenuItemDataMap_t::iterator iter = m_accelMap.begin(); iter != m_accelMap.end(); ++iter) {
            if(!IsMatchesFilter(filter, iter->second)) continue;
            filteredMap.insert(std::make_pair(iter->first, iter->second));
        }
    }

    if(filteredMap.empty()) return;

    // Add core entries
    for(MenuItemDataMap_t::const_iterator iter = filteredMap.begin(); iter != filteredMap.end(); ++iter) {
        const MenuItemData& mid = iter->second;

        wxVector<wxVariant> cols;
        wxString parentMenu = mid.parentMenu.BeforeFirst(':');
        if(parentMenu.IsEmpty()) {
            parentMenu = "<Global>";
        }

        cols.push_back(parentMenu);                // Parent menu
        cols.push_back(mid.action.AfterLast(':')); // Action description
        cols.push_back(mid.accel);                 // shortcut
        m_dvListCtrl->AppendItem(cols, (wxUIntPtr) new AccelItemData(mid));
    }

    m_dvListCtrl->GetColumn(0)->SetSortable(true);
    m_dvListCtrl->GetColumn(1)->SetSortable(true);
    m_dvListCtrl->GetColumn(2)->SetSortable(true);
}
// -----------------------------------------------------------------------------------------------------------------
void clKeyboardManager::CheckForDuplicateAccels(MenuItemDataMap_t& accelMap) const //(2019/04/22)
// -----------------------------------------------------------------------------------------------------------------
{
    // Warn about duplicate Menu accelerators //(2019/04/22)

    wxArrayString dupMsgs;
    for(MenuItemDataMap_t::iterator accelIter = accelMap.begin(); accelIter != accelMap.end(); ++accelIter)
    {
        if (accelIter->second.accel.empty()) continue;
        if (accelIter->second.parentMenu.empty()) continue; //skip global accelerators
        MenuItemDataMap_t::iterator foundIter   = accelMap.end();
        MenuItemDataMap_t::iterator patternIter = accelIter;
        while (accelMap.end() != (foundIter = ExistsALikeAccel(accelMap, patternIter)) )
        {
            #if defined(LOGGING)
            wxString patternAccel  = patternIter->second.accel;
            wxString patternAction = patternIter->second.action;
            wxString dupAccel      = foundIter->second.accel;
            wxString dupAction     = foundIter->second.action;
            #endif
            //skip found global accelerators
            if (foundIter->second.parentMenu.empty())
            {
                patternIter = foundIter;
                continue;
            }

            // found a duplicate menu accelerator further down the accelerator map
            MenuItemDataMap_t::iterator srcIter = patternIter;


            wxString srcMenuLabel = srcIter->second.parentMenu;
            srcMenuLabel.Replace(_T("\t"), _T(" "));
            srcMenuLabel.Replace(_T("&"), _T(""));
            srcMenuLabel.Replace(_T("::"), _T("/"));

            wxString foundMenuLabel = foundIter->second.parentMenu;
            foundMenuLabel.Replace(_T("\t"), _T(" "));
            foundMenuLabel.Replace(_T("&"), _T(""));
            foundMenuLabel.Replace(_T("::"), _T("/"));

            long srcMenuID; srcIter->first.ToLong(&srcMenuID);
            long foundMenuID; foundIter->first.ToLong(&foundMenuID);

            wxString msg = wxString::Format(_T("Conflicting menu items: \'%s\' && \'%s\'"),
                                            srcMenuLabel.wx_str(), foundMenuLabel.wx_str())
                         + wxString::Format(_T("\n   Both using shortcut: \'%s\'"), foundIter->second.accel.wx_str())
                         + wxString::Format(_T(" (IDs [%ld] [%ld])"),srcMenuID, foundMenuID );
            msg += _T("\n\n");
            dupMsgs.Add(msg);
            patternIter = foundIter;

        }//end while
    }
    if (dupMsgs.GetCount())
    {
        bool isParentWindowDialog = false;
        // Get top window to solve msg window getting hidden behind keybinder dialog
        wxWindow* pMainWin = nullptr;
        if ( (pMainWin = wxFindWindowByLabel(_T("Configure editor"))) )
        {   pMainWin = wxFindWindowByLabel(_T("Configure editor"));
            isParentWindowDialog = true;
        }
        else pMainWin = Manager::Get()->GetAppWindow();
        wxString msg = _T("Keyboard shortcut conflicts found.\n");
        if (not isParentWindowDialog)
            msg += _T("Use Settings/Editor/KeyboardShortcuts to resolve conflicts.\n\n");
        for (size_t ii=0; ii<dupMsgs.GetCount(); ++ii)
            msg += dupMsgs[ii];
        //-cbMessageBox(msg, _T("Keyboard shortcuts conflicts"), wxOK, pMainWin);
        AnnoyingDialog dlg(_("Keyboard shortcuts conflicts"), msg, wxART_INFORMATION,  AnnoyingDialog::OK);
        dlg.ShowModal();
    }//endif dupMsgs

    return;
}
// ----------------------------------------------------------------------------
void clKeyboardManager::Initialize(bool isRefreshRequest)
// ----------------------------------------------------------------------------
{
    wxUnusedVar(isRefreshRequest);
    m_menuTable.clear();

    // First, try to load accelerators from %appdata% keybindings.conf
    //      containing merged default + user defined accerators
    // Second, try loading from default accerators in %appdata% + accerators.conf

    clKeyboardBindingConfig config;
    if( not config.Exists()) //does keybindings.conf exist?
    {
        #if defined(LOGGING)
        LOGIT( _T("[%s]"), _("Keyboard manager: No configuration found - importing old settings"));
        #endif
        //CL_DEBUG("Keyboard manager: No configuration found - importing old settings");
        // Decide which file we want to load, take the user settings file first
        // GetUserDataDir() == "c:\Users\<username>\AppData\Roaming\<appname>\config\keybindings.conf"
        // GetDataDir()     == executable directory

        // Old accererator setting are in %appdata%
        wxFileName fnOldSettings(wxStandardPaths::Get().GetTempDir(), _T("keyMnuAccels.conf"));
        wxString personality = Manager::Get()->GetPersonalityManager()->GetPersonality();
        fnOldSettings.SetName(personality + _T(".") + fnOldSettings.GetName());

        wxFileName fnFileToLoad;
        bool canDeleteOldSettings(false);
        // If %appdata% accerators.conf exist, use it
        if(fnOldSettings.FileExists())
        {
            fnFileToLoad = fnOldSettings;
            //-canDeleteOldSettings = true;
        }
        else    // else use executable dir accerators.conf.default accerators
        {
            //-fnFileToLoad = fnDefaultOldSettings;
            wxASSERT_MSG(0, _("clKeyboardManager::Initialize() missing accerators.conf file"));
        }

        if(fnFileToLoad.FileExists())
        {
            #if defined(LOGGING)
            LOGIT( _T("KeyboardManager:Importing settings from:\n\t[%s]"), fnFileToLoad.GetFullPath().wx_str());
            #endif
            // Apply the old settings to the menus
            wxString content;
            if(not ReadFileContent(fnFileToLoad, content)) return;
            wxArrayString lines = ::wxStringTokenize(content, _T("\r\n"), wxTOKEN_STRTOK);
            for(size_t i = 0; i < lines.GetCount(); ++i)
            {
                #if defined(LOGGING)
                    #if wxVERSION_NUMBER > 3000
                    LOGIT( _T("AccelFile[%u:%s]"), (unsigned)i, lines.Item(i).wx_str() );
                    #else
                    LOGIT( _T("AccelFile[%u:%s]"), i, lines.Item(i).wx_str() );
                    #endif
                #endif
                wxArrayString parts = ::wxStringTokenize(lines.Item(i), _T("|"), wxTOKEN_RET_EMPTY);
                if(parts.GetCount() < 3) continue;
                MenuItemData binding;
                binding.resourceID = parts.Item(0);
                binding.parentMenu = parts.Item(1);
                binding.action = parts.Item(2);
                if(parts.GetCount() == 4) {
                    binding.accel = parts.Item(3);
                }
                m_menuTable.insert(std::make_pair(binding.resourceID, binding));
            }

            if(canDeleteOldSettings) {
                if (fnFileToLoad.FileExists())
                    ::wxRemoveFile(fnFileToLoad.GetFullPath());
            }
        }
    }
    else //config exists: "keybindings.conf"
    {
        config.Load();
        m_menuTable = config.GetBindings();
    }

    // Load the default settings and add any new entries from accerators.conf
    MenuItemDataMap_t defaultEntries = DoLoadDefaultAccelerators();

    // Remove any map items nolonger matching the menu structure
    for (MenuItemDataMap_t::iterator mapIter = m_menuTable.begin(); mapIter != m_menuTable.end(); ++mapIter)
    {
        mnuContinue:
        if (mapIter == m_menuTable.end()) break;
        //search menu structure map for map menuId
        if ( defaultEntries.count(mapIter->first) == 0)
        {   // menuID nolonger exists
            #if defined(LOGGING)
                wxString mapAccel = mapIter->second.accel;
                wxString mapParent = mapIter->second.parentMenu;
                wxString mapMnuID = mapIter->first;
                LOGIT( _T("Removing ID mismatch[%s][%s][%s]"), mapMnuID.wx_str(), mapParent.wx_str(), mapAccel.wx_str());
            #endif
            mapIter = m_menuTable.erase(mapIter);
            goto mnuContinue;
        }
        else //remove the found map item if its label doesn't match menu structure label//(pecan 2019/05/18)
        {   // Have matching map resoureID and menu structure resourceID (ie., menuItemID)
            MenuItemDataMap_t::iterator mnuIter = defaultEntries.find(mapIter->first);
            if (mnuIter == defaultEntries.end())
                continue;
            wxString mapParent = mapIter->second.parentMenu;
            if (mapParent.empty()) continue; //skip global accelerators
            wxString mnuParent = mnuIter->second.parentMenu;
            if (mnuParent.empty()) continue; //skip global accelerators
            if (mapParent.Lower() != mnuParent.Lower())
            {
                #if defined(LOGGING)
                    wxString mapMnuID = mapIter->first;
                    wxString mapAccel = mapIter->second.accel;
                    LOGIT( _T("Removing LabelMismatch[%s][%s][%s]"), mapMnuID.wx_str(), mapParent.wx_str(), mapAccel.wx_str());
                #endif
                mapIter = m_menuTable.erase(mapIter);
                goto mnuContinue;
            }//endif label compare
        }//endif else have matching resourceID
    }//endfor mapIter

    // Add any new entries from accerators.conf (the menu structure)
    std::for_each(defaultEntries.begin(), defaultEntries.end(), [&](const MenuItemDataMap_t::value_type& vdflt)
    {
        //-wxString vtValue = vdflt.first;         //The menu id number
        if(m_menuTable.count(vdflt.first) == 0) {  //searches map for like shortcut string
            m_menuTable.insert(vdflt);
        }
        // ----------------------------------------------------------------------------
        // NO!no! don't overwrite past user changes; m_menuTable already has user keybinder.conf changes.
        // while defaultEntries have original CodeBlocks menu accelerators
        // User must make (or made) m_menuTable changes via KeyBinder configuration dialog.
        // ----------------------------------------------------------------------------
        //-else //verify keyboard shortcut
        //-{
        //-    MenuItemDataMap_t::iterator mapIter = m_menuTable.find(vdflt.first);
        //-    if (mapIter != m_menuTable.end()) //should never be true!
        //-    if (mapIter->second.accel != vdflt.second.accel)
        //-    {
        //-        #if defined(LOGGING)
        //-        wxString mapMenuItem = mapIter->second.parentMenu + mapIter->second.accel;
        //-        wxString vdfltMenuItem = vdflt.second.parentMenu +vdflt.second.accel;
        //-        LOGIT( _T("Initialize changing accel[%s]to[%s]"), mapMenuItem.wx_str(), vdfltMenuItem.wx_str());
        //-        #endif
        //-        mapIter->second.accel = vdflt.second.accel;
        //-    }
        //-}
    });

    // Warn about duplicate shortcut entries (eg., (Print/PrevCallTip Ctrl-P) and (CC Search/Ctrl-Shift-.) have duplicates) //(2019/04/23)
    CheckForDuplicateAccels(m_menuTable);

    // Store the correct configuration; globalTable is inserted into menuTable
    config.SetBindings(m_menuTable, m_globalTable).Save();

    // And apply the changes
    Update();
}