<filename>src_main/gameui/BonusMapsDialog.cpp
// Copyright © 1996-2018, Valve Corporation, All rights reserved.

#include "BonusMapsDialog.h"

#include "EngineInterface.h"
#include "FileSystem.h"
#include "MouseMessageForwardingPanel.h"
#include "TGAImagePanel.h"
#include "basepanel.h"
#include "vgui/ILocalize.h"
#include "vgui/ISurface.h"
#include "vgui/ISystem.h"
#include "vgui/IVGui.h"
#include "vgui_controls/BitmapImagePanel.h"
#include "vgui_controls/Button.h"
#include "vgui_controls/ComboBox.h"
#include "vgui_controls/FileOpenDialog.h"
#include "vgui_controls/ImagePanel.h"
#include "vgui_controls/PanelListPanel.h"
#include "vgui_controls/QueryBox.h"

#include "tier0/include/memdbgon.h"

using namespace vgui;

#define MAX_LISTED_BONUS_MAPS 128

extern const char *COM_GetModDirectory(void);

static bool ConstructFullImagePath(const char *pCurrentPath,
                                   const char *pchImageName,
                                   char *pchImageFileName,
                                   size_t image_file_name_size) {
  char *ext = Q_strstr(pchImageName, ".tga");
  if (ext) {
    // Use the specified image
    if (pchImageName[0] != '.')
      Q_snprintf(pchImageFileName, image_file_name_size, "%s", pchImageName);
    else
      Q_snprintf(pchImageFileName, image_file_name_size, "%s/%s", pCurrentPath,
                 pchImageName);

    return true;
  }

  Q_strcpy(pchImageFileName, image_file_name_size, pchImageName);

  return false;
}

//-----------------------------------------------------------------------------
// Purpose: Describes the layout of a same game pic
//-----------------------------------------------------------------------------
class CBonusMapPanel : public vgui::EditablePanel {
  DECLARE_CLASS_SIMPLE(CBonusMapPanel, vgui::EditablePanel);

 public:
  CBonusMapPanel(PanelListPanel *parent, const char *name,
                 int bonusMapListItemID)
      : BaseClass(parent, name) {
    m_iBonusMapListItemID = bonusMapListItemID;
    m_pParent = parent;
    m_pBonusMapTGAImage = new CTGAImagePanel(this, "BonusMapTGAImage");
    m_pBonusMapImage = SETUP_PANEL(new ImagePanel(this, "BonusMapImage"));
    m_pBonusMapScreenshotBackground =
        SETUP_PANEL(new ImagePanel(this, "BonusMapScreenshotBackground"));
    m_pMapNameLabel = new Label(this, "MapNameLabel", "");
    m_pLockIcon = new ImagePanel(this, "LockIcon");
    m_pCompleteIcon = new ImagePanel(this, "CompleteIcon");

    CMouseMessageForwardingPanel *panel =
        new CMouseMessageForwardingPanel(this, NULL);
    panel->SetZPos(2);

    SetSize(200, 140);

    LoadControlSettings("resource/BonusMapPanel.res");

    m_FillColor = m_pBonusMapScreenshotBackground->GetFillColor();
  }

  void SetBonusMapInfo(const char *pCurrentPath, BonusMapDescription_t &map) {
    // set the image to display
    char szImageFileName[SOURCE_MAX_PATH];

    bool bIsTGA = false;

    if (map.bIsFolder) {
      if (map.szImageName[0] == '\0') {
        // use associate bonus folder icon
        Q_snprintf(szImageFileName, sizeof(szImageFileName),
                   "%s/foldericon.tga", map.szFileName);
        bIsTGA = true;

        // use default folder icon
        if (!g_pFullFileSystem->FileExists(szImageFileName, "MOD")) {
          Q_strcpy(szImageFileName, "bonusmaps/icon_bonus_map_folder");
          bIsTGA = false;
        }
      } else {
        // Use the specified image
        bIsTGA = ConstructFullImagePath(pCurrentPath, map.szImageName,
                                        szImageFileName,
                                        SOURCE_ARRAYSIZE(szImageFileName));
      }
    } else {
      if (map.szImageName[0] == '\0') {
        // Didn't specify an image name, so pair it with the name of this file
        char szImpliedTgaName[SOURCE_MAX_PATH];
        Q_snprintf(szImpliedTgaName, sizeof(szImpliedTgaName), "%s.tga",
                   map.szMapFileName);
        bIsTGA = ConstructFullImagePath(pCurrentPath, szImpliedTgaName,
                                        szImageFileName,
                                        SOURCE_ARRAYSIZE(szImageFileName));

        // if it doesn't exist use default bonus map icon
        if (!g_pFullFileSystem->FileExists(szImageFileName, "MOD")) {
          Q_strcpy(szImageFileName, "bonusmaps/icon_bonus_map_default");
          bIsTGA = false;
        }
      } else {
        // Use the specified image
        bIsTGA = ConstructFullImagePath(pCurrentPath, map.szImageName,
                                        szImageFileName,
                                        SOURCE_ARRAYSIZE(szImageFileName));
      }
    }

    if (bIsTGA) {
      m_pBonusMapTGAImage->SetTGANonMod(szImageFileName);
      m_pBonusMapTGAImage->SetSize(180, 100);
      m_pBonusMapTGAImage->SetVisible(true);
      m_pBonusMapImage->SetVisible(false);
    } else {
      m_pBonusMapImage->SetImage(szImageFileName);
      m_pBonusMapImage->SetSize(180, 100);
      m_pBonusMapImage->SetVisible(true);
      m_pBonusMapTGAImage->SetVisible(false);
    }

    m_pLockIcon->SetVisible(map.bLocked);
    m_pCompleteIcon->SetVisible(map.bComplete);

    // set the title text
    m_pMapNameLabel->SetText(map.szMapName);
  }

  MESSAGE_FUNC_INT(OnPanelSelected, "PanelSelected", state) {
    if (state) {
      // set the text color to be orange, and the pic border to be orange
      m_pBonusMapScreenshotBackground->SetFillColor(m_SelectedColor);
      m_pMapNameLabel->SetFgColor(Color(0, 0, 0, 255));
    } else {
      m_pBonusMapScreenshotBackground->SetFillColor(m_FillColor);
      m_pMapNameLabel->SetFgColor(m_TextColor);
    }

    PostMessage(m_pParent->GetVParent(), new KeyValues("PanelSelected"));
  }

  virtual void OnMousePressed(vgui::MouseCode code) {
    m_pParent->SetSelectedPanel(this);
  }

  virtual void ApplySchemeSettings(IScheme *pScheme) {
    m_TextColor =
        pScheme->GetColor("NewGame.TextColor", Color(255, 255, 255, 255));
    m_SelectedColor =
        pScheme->GetColor("NewGame.SelectionColor", Color(255, 255, 255, 255));

    BaseClass::ApplySchemeSettings(pScheme);
  }

  virtual void OnMouseDoublePressed(vgui::MouseCode code) {
    // call the panel
    OnMousePressed(code);
    PostMessage(m_pParent->GetParent(),
                new KeyValues("Command", "command", "loadbonusmap"));
  }

  int GetBonusMapListItemID() { return m_iBonusMapListItemID; }

 private:
  vgui::PanelListPanel *m_pParent;
  vgui::Label *m_pMapNameLabel;
  ImagePanel *m_pBonusMapImage;
  CTGAImagePanel *m_pBonusMapTGAImage;

  ImagePanel *m_pLockIcon;
  ImagePanel *m_pCompleteIcon;

  // things to change color when the selection changes
  ImagePanel *m_pBonusMapScreenshotBackground;
  Color m_TextColor, m_FillColor, m_SelectedColor;

  int m_iBonusMapListItemID;
};

CBonusMapsDialog *g_pBonusMapsDialog = NULL;

//-----------------------------------------------------------------------------
// Purpose:Constructor
//-----------------------------------------------------------------------------
CBonusMapsDialog::CBonusMapsDialog(vgui::Panel *parent)
    : BaseClass(parent, "BonusMapsDialog") {
  g_pBonusMapsDialog = this;
  m_hImportBonusMapsDialog = NULL;

  BonusMapsDatabase()->RootPath();

  CreateBonusMapsList();

  BuildMapsList();

  new vgui::Button(this, "loadbonusmap", "");
  SetControlEnabled("loadbonusmap", false);

  SetDeleteSelfOnClose(true);
  // SetBounds(0, 0, 512, 384);
  // SetMinimumSize( 256, 300 );
  SetSizeable(false);

  SetTitle("#GameUI_BonusMaps", true);

  vgui::Button *cancel = new vgui::Button(this, "Cancel", "#GameUI_Cancel");
  cancel->SetCommand("Close");

  m_pPercentageBarBackground =
      SETUP_PANEL(new ImagePanel(this, "PercentageBarBackground"));
  m_pPercentageBar = SETUP_PANEL(new ImagePanel(this, "PercentageBar"));

  LoadControlSettings("resource/BonusMapsDialog.res");

  // Stop blinking the bonus maps menu item
  CBasePanel *pBasePanel = BasePanel();
  if (pBasePanel)
    pBasePanel->SetMenuItemBlinkingState("OpenBonusMapsDialog", false);

  BonusMapsDatabase()->SetBlink(false);
}

//-----------------------------------------------------------------------------
// Purpose: Destructor
//-----------------------------------------------------------------------------
CBonusMapsDialog::~CBonusMapsDialog() {
  BonusMapsDatabase()
      ->WriteSaveData();  // Closing this dialog is a good time to save
  g_pBonusMapsDialog = NULL;
}

bool CBonusMapsDialog::ImportZippedBonusMaps(const char *pchZippedFileName) {
  // Get the zip file's name with dir info
  char *pchShortFileName = Q_strrchr(pchZippedFileName, '\\');

  if (!pchShortFileName) return false;

  // It's going to go in the maps folder
  char szOutFilename[512];

  Q_snprintf(szOutFilename, sizeof(szOutFilename), "maps%s", pchShortFileName);
  Q_StripExtension(szOutFilename, szOutFilename, sizeof(szOutFilename));

  // If there's already a folder by the same name we're going to tack a number
  // onto the end
  int iOutFilenameLength = Q_strlen(szOutFilename);
  int iSameFolderCount = 1;

  while (g_pFullFileSystem->FileExists(szOutFilename, "MOD")) {
    ++iSameFolderCount;

    if (iSameFolderCount > 99) {
      return false;
    }

    szOutFilename[iOutFilenameLength] = '\0';
    Q_snprintf(szOutFilename, sizeof(szOutFilename), "%s%02i", szOutFilename,
               iSameFolderCount);
  }

  // Pull the files out of the zip
  if (g_pFullFileSystem->UnzipFile(pchZippedFileName, "MOD", szOutFilename)) {
    // New maps have been imported, so refresh the list
    BuildMapsList();
  }

  return false;
}

//-----------------------------------------------------------------------------
// Purpose: builds bonus map list from directory
//-----------------------------------------------------------------------------
void CBonusMapsDialog::BuildMapsList(void) {
  // clear the current list
  m_pGameList->DeleteAllItems();

  BonusMapsDatabase()->ClearBonusMapsList();
  BonusMapsDatabase()->ScanBonusMaps();

  // Enable back button if we're in a sub folder
  bool bIsRoot = (Q_strcmp(BonusMapsDatabase()->GetPath(), ".") == 0);
  SetControlEnabled("Back", !bIsRoot);
  SetControlVisible("Back", !bIsRoot);
  SetControlEnabled("ImportBonusMaps", bIsRoot);
  SetControlVisible("ImportBonusMaps", bIsRoot);

  char szDisplayPath[SOURCE_MAX_PATH];
  Q_snprintf(szDisplayPath, SOURCE_MAX_PATH, "%s/",
             BonusMapsDatabase()->GetPath());

  SetControlString("FileName", szDisplayPath);
  SetControlString("CommentLabel", "");

  int iMapCount = BonusMapsDatabase()->BonusCount();

  // add to the list
  for (int iMapIndex = 0;
       iMapIndex < iMapCount && iMapIndex < MAX_LISTED_BONUS_MAPS;
       ++iMapIndex) {
    CBonusMapPanel *bonusMapPanel =
        new CBonusMapPanel(m_pGameList, "BonusMapPanel", iMapIndex);
    bonusMapPanel->SetBonusMapInfo(
        BonusMapsDatabase()->GetPath(),
        *(BonusMapsDatabase()->GetBonusData(iMapIndex)));
    m_pGameList->AddItem(NULL, bonusMapPanel);
  }

  // display a message if there are no save games
  if (iMapCount <= 0) {
    vgui::Label *pNoSavesLabel = SETUP_PANEL(new Label(
        m_pGameList, "NoBonusMapsLabel", "#GameUI_NoBonusMapsToDisplay"));
    pNoSavesLabel->SetTextColorState(vgui::Label::CS_DULL);
    m_pGameList->AddItem(NULL, pNoSavesLabel);
    m_pGameList->SetNumColumns(1);
  } else {
    m_pGameList->SetNumColumns(3);
  }

  RefreshCompletionPercentage();

  // Disable load button
  SetControlEnabled("loadbonusmap", false);

  // Make challenge selection invisible
  m_pChallengeSelection->SetEnabled(false);
  m_pChallengeSelection->SetVisible(false);
}

//-----------------------------------------------------------------------------
// Purpose: Creates the load game display list
//-----------------------------------------------------------------------------
void CBonusMapsDialog::CreateBonusMapsList() {
  m_pGameList = new vgui::PanelListPanel(this, "listpanel_bonusmaps");
  m_pGameList->SetFirstColumnWidth(0);

  new Label(this, "FileName", "./");
  new Label(this, "CommentLabel", "");

  m_pChallengeSelection =
      new vgui::ComboBox(this, "ChallengeSelection", 0, false);
}

//-----------------------------------------------------------------------------
// Purpose: returns the save file name of the selected item
//-----------------------------------------------------------------------------
int CBonusMapsDialog::GetSelectedItemBonusMapIndex() {
  CBonusMapPanel *panel =
      dynamic_cast<CBonusMapPanel *>(m_pGameList->GetSelectedPanel());
  if (panel &&
      panel->GetBonusMapListItemID() < BonusMapsDatabase()->BonusCount())
    return panel->GetBonusMapListItemID();

  return BonusMapsDatabase()->InvalidIndex();
}

void CBonusMapsDialog::SetSelectedBooleanStatus(const char *pchName,
                                                bool bValue) {
  CBonusMapPanel *pSelectedBonusMapPanel =
      (CBonusMapPanel *)m_pGameList->GetSelectedPanel();
  if (!pSelectedBonusMapPanel) return;

  BonusMapsDatabase()->SetBooleanStatus(
      pchName, pSelectedBonusMapPanel->GetBonusMapListItemID(), bValue);

  // Change the status in the dialog
  BonusMapDescription_t *pMap = BonusMapsDatabase()->GetBonusData(
      pSelectedBonusMapPanel->GetBonusMapListItemID());
  pSelectedBonusMapPanel->SetBonusMapInfo(BonusMapsDatabase()->GetPath(),
                                          *pMap);

  RefreshCompletionPercentage();
  RefreshDialog(pMap);
}

void CBonusMapsDialog::RefreshData(void) {
  for (int iMap = 0; iMap < m_pGameList->GetItemCount(); ++iMap) {
    CBonusMapPanel *pBonusMapPanel =
        (CBonusMapPanel *)m_pGameList->GetItemPanel(iMap);

    if (pBonusMapPanel)
      pBonusMapPanel->SetBonusMapInfo(
          BonusMapsDatabase()->GetPath(),
          *(BonusMapsDatabase()->GetBonusData(
              pBonusMapPanel->GetBonusMapListItemID())));
  }

  CBonusMapPanel *pSelectedBonusMapPanel =
      (CBonusMapPanel *)m_pGameList->GetSelectedPanel();
  if (!pSelectedBonusMapPanel) return;

  BonusMapDescription_t *pMap = BonusMapsDatabase()->GetBonusData(
      pSelectedBonusMapPanel->GetBonusMapListItemID());

  RefreshCompletionPercentage();
  RefreshDialog(pMap);
}

int CBonusMapsDialog::GetSelectedChallenge(void) {
  if (!m_pChallengeSelection->IsEnabled()) return -1;

  KeyValues *pUserDataKey = m_pChallengeSelection->GetActiveItemUserData();
  return pUserDataKey->GetInt("challenge");
}

void CBonusMapsDialog::RefreshDialog(BonusMapDescription_t *pMap) {
  if (!pMap || pMap->bLocked ||
      (m_pChallengeSelection->IsEnabled() && GetSelectedChallenge() == -1)) {
    // It's locked or no challenge is selected, so disable the load button
    SetControlEnabled("loadbonusmap", false);
  } else {
    // Enable the load button
    SetControlEnabled("loadbonusmap", true);
  }

  RefreshMedalDisplay(pMap);

  if (pMap)
    SetControlString("CommentLabel", pMap->szComment);
  else
    SetControlString("CommentLabel", "");
}

void CBonusMapsDialog::RefreshMedalDisplay(BonusMapDescription_t *pMap) {
  int iFirstChildIndex = FindChildIndexByName("ChallengeMedalOverview00");
  for (int iMedal = 0; iMedal < 5; ++iMedal) {
    Panel *pMedalImage = GetChild(iFirstChildIndex + iMedal);
    pMedalImage->SetVisible(false);
  }

  if (!pMap || !pMap->m_pChallenges) {
    SetControlVisible("ChallengeCommentLabel", false);
    SetControlVisible("ChallengeEarnedMedal", false);
    SetControlVisible("ChallengeBestLabel", false);
    SetControlVisible("ChallengeNextMedal", false);
    SetControlVisible("ChallengeNextLabel", false);

    return;
  }

  char szBuff[512];

  int iChallenge = GetSelectedChallenge();

  if (iChallenge < 0) {
    SetControlVisible("ChallengeCommentLabel", false);
    SetControlVisible("ChallengeEarnedMedal", false);
    SetControlVisible("ChallengeBestLabel", false);
    SetControlVisible("ChallengeNextMedal", false);
    SetControlVisible("ChallengeNextLabel", false);

    iFirstChildIndex = FindChildIndexByName("ChallengeMedalOverview00");
    for (int iChallengeIndex = 0;
         iChallengeIndex < m_pChallengeSelection->GetItemCount();
         ++iChallengeIndex) {
      KeyValues *pUserDataKey =
          m_pChallengeSelection->GetItemUserData(iChallengeIndex);
      int iChallengeNum = pUserDataKey->GetInt("challenge");

      if (iChallengeNum >= 0) {
        ChallengeDescription_t *pChallengeDescription = NULL;

        for (int i = 0; i < pMap->m_pChallenges->Count(); ++i) {
          int iType = ((*pMap->m_pChallenges)[i]).iType;

          if (iType == -1) {
            iType = i;
          }

          if (iType == iChallengeNum)
            pChallengeDescription = &((*pMap->m_pChallenges)[i]);
        }

        if (pChallengeDescription) {
          int iBest, iEarnedMedal, iNext, iNextMedal;
          GetChallengeMedals(pChallengeDescription, iBest, iEarnedMedal, iNext,
                             iNextMedal);

          if (iChallengeNum < 10)
            Q_snprintf(szBuff, 256, "medals/medal_0%i_%s", iChallengeNum,
                       g_pszMedalNames[iEarnedMedal]);
          else
            Q_snprintf(szBuff, 256, "medals/medal_%i_%s", iChallengeNum,
                       g_pszMedalNames[iEarnedMedal]);
        }

        CBitmapImagePanel *pBitmap = dynamic_cast<CBitmapImagePanel *>(
            GetChild(iFirstChildIndex + iChallengeNum));
        pBitmap->SetVisible(true);
        pBitmap->setTexture(szBuff);
      }
    }

    return;
  }

  ChallengeDescription_t *pChallengeDescription = NULL;

  for (int i = 0; i < pMap->m_pChallenges->Count(); ++i) {
    int iType = ((*pMap->m_pChallenges)[i]).iType;

    if (iType == -1) {
      iType = i;
    }

    if (iType == iChallenge)
      pChallengeDescription = &((*pMap->m_pChallenges)[i]);
  }

  if (!pChallengeDescription) return;

  const char *pchChallengeComment = pChallengeDescription->szComment;

  int iBest, iEarnedMedal, iNext, iNextMedal;
  GetChallengeMedals(pChallengeDescription, iBest, iEarnedMedal, iNext,
                     iNextMedal);

  // Set comment label
  SetControlString("ChallengeCommentLabel", pchChallengeComment);
  SetControlVisible("ChallengeCommentLabel", true);

  // Set earned medal
  if (iEarnedMedal > -1) {
    if (iChallenge < 10)
      Q_snprintf(szBuff, sizeof(szBuff), "medals/medal_0%i_%s", iChallenge,
                 g_pszMedalNames[iEarnedMedal]);
    else
      Q_snprintf(szBuff, sizeof(szBuff), "medals/medal_%i_%s", iChallenge,
                 g_pszMedalNames[iEarnedMedal]);

    CBitmapImagePanel *pBitmap = dynamic_cast<CBitmapImagePanel *>(
        FindChildByName("ChallengeEarnedMedal"));
    pBitmap->SetVisible(true);
    pBitmap->setTexture(szBuff);
  }

  // Set next medal
  if (iNextMedal > 0) {
    if (iChallenge < 10)
      Q_snprintf(szBuff, sizeof(szBuff), "medals/medal_0%i_%s", iChallenge,
                 g_pszMedalNames[iNextMedal]);
    else
      Q_snprintf(szBuff, sizeof(szBuff), "medals/medal_%i_%s", iChallenge,
                 g_pszMedalNames[iNextMedal]);

    CBitmapImagePanel *pBitmap = dynamic_cast<CBitmapImagePanel *>(
        FindChildByName("ChallengeNextMedal"));
    pBitmap->SetVisible(true);
    pBitmap->setTexture(szBuff);
  } else {
    SetControlVisible("ChallengeNextMedal", false);
  }

  wchar_t szWideBuff[64];
  wchar_t szWideBuff2[64];

  // Best label
  if (iBest != -1) {
    Q_snprintf(szBuff, sizeof(szBuff), "%i", iBest);
    g_pVGuiLocalize->ConvertANSIToUnicode(szBuff, szWideBuff2,
                                          sizeof(szWideBuff2));
    g_pVGuiLocalize->ConstructString(
        szWideBuff, sizeof(szWideBuff),
        g_pVGuiLocalize->Find("#GameUI_BonusMapsBest"), 1, szWideBuff2);
    g_pVGuiLocalize->ConvertUnicodeToANSI(szWideBuff, szBuff, sizeof(szBuff));

    SetControlString("ChallengeBestLabel", szBuff);
    SetControlVisible("ChallengeBestLabel", true);
  } else
    SetControlVisible("ChallengeBestLabel", false);

  // Next label
  if (iNext != -1) {
    Q_snprintf(szBuff, sizeof(szBuff), "%i", iNext);
    g_pVGuiLocalize->ConvertANSIToUnicode(szBuff, szWideBuff2,
                                          sizeof(szWideBuff2));
    g_pVGuiLocalize->ConstructString(
        szWideBuff, sizeof(szWideBuff),
        g_pVGuiLocalize->Find("#GameUI_BonusMapsGoal"), 1, szWideBuff2);
    g_pVGuiLocalize->ConvertUnicodeToANSI(szWideBuff, szBuff, sizeof(szBuff));

    SetControlString("ChallengeNextLabel", szBuff);
    SetControlVisible("ChallengeNextLabel", true);
  } else
    SetControlVisible("ChallengeNextLabel", false);
}

void CBonusMapsDialog::RefreshCompletionPercentage(void) {
  float fPercentage = BonusMapsDatabase()->GetCompletionPercentage();
  if (fPercentage > 0.0f) {
    char szBuff[256];
    if (fPercentage * 100.0f < 1.0f)
      Q_snprintf(szBuff, 256, "%.2f%%",
                 fPercentage * 100.0f);  // Show decimal places if less than 1%
    else
      Q_snprintf(szBuff, 256, "%.0f%%", fPercentage * 100.0f);

    SetControlString("PercentageText", szBuff);
    SetControlVisible("PercentageText", true);
    SetControlVisible("CompletionText", true);

    // Blend the color from backround color 0% to selected color 100%
    Color cProgressBar =
        Color(static_cast<float>(m_PercentageBarBackgroundColor.r()) *
                      (1.0f - fPercentage) +
                  static_cast<float>(m_PercentageBarColor.r()) * fPercentage,
              static_cast<float>(m_PercentageBarBackgroundColor.g()) *
                      (1.0f - fPercentage) +
                  static_cast<float>(m_PercentageBarColor.g()) * fPercentage,
              static_cast<float>(m_PercentageBarBackgroundColor.b()) *
                      (1.0f - fPercentage) +
                  static_cast<float>(m_PercentageBarColor.b()) * fPercentage,
              static_cast<float>(m_PercentageBarBackgroundColor.a()) *
                      (1.0f - fPercentage) +
                  static_cast<float>(m_PercentageBarColor.a()) * fPercentage);

    m_pPercentageBar->SetFillColor(cProgressBar);
    m_pPercentageBar->SetWide(m_pPercentageBarBackground->GetWide() *
                              fPercentage);

    SetControlVisible("PercentageBarBackground", true);
    SetControlVisible("PercentageBar", true);
  } else {
    // 0% complete so don't display
    SetControlVisible("PercentageText", false);
    SetControlVisible("CompletionText", false);
    SetControlVisible("PercentageBarBackground", false);
    SetControlVisible("PercentageBar", false);
  }
}

void CBonusMapsDialog::ApplySchemeSettings(IScheme *pScheme) {
  m_PercentageBarBackgroundColor = Color(0, 0, 0, 64);
  m_PercentageBarColor =
      pScheme->GetColor("NewGame.SelectionColor", Color(255, 255, 255, 255));

  BaseClass::ApplySchemeSettings(pScheme);
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CBonusMapsDialog::OnCommand(const char *command) {
  if (!_stricmp(command, "loadbonusmap")) {
    int mapIndex = GetSelectedItemBonusMapIndex();
    if (BonusMapsDatabase()->IsValidIndex(mapIndex)) {
      BonusMapDescription_t *pBonusMap =
          BonusMapsDatabase()->GetBonusData(mapIndex);

      // Don't do anything with locked items
      if (pBonusMap->bLocked ||
          (m_pChallengeSelection->IsEnabled() && GetSelectedChallenge() == -1))
        return;

      const char *shortName = pBonusMap->szShortName;
      if (shortName && shortName[0]) {
        if (pBonusMap->bIsFolder) {
          BonusMapsDatabase()->AppendPath(shortName);

          // repopulate list with current directory
          BuildMapsList();

          m_pGameList->MoveScrollBarToTop();
        } else {
          // Load the game, return to top and switch to engine
          char sz[256];

          // Set the challenge mode if one is selected
          int iChallenge = GetSelectedChallenge() + 1;

          if (iChallenge > 0) {
            Q_snprintf(sz, sizeof(sz), "sv_bonus_challenge %i\n", iChallenge);
            engine->ClientCmd_Unrestricted(sz);

            ChallengeDescription_t *pChallengeDescription =
                &((*pBonusMap->m_pChallenges)[iChallenge - 1]);

            // Set up medal goals
            BonusMapsDatabase()->SetCurrentChallengeObjectives(
                pChallengeDescription->iBronze, pChallengeDescription->iSilver,
                pChallengeDescription->iGold);
            BonusMapsDatabase()->SetCurrentChallengeNames(
                pBonusMap->szFileName, pBonusMap->szMapName,
                pChallengeDescription->szName);
          }

          if (pBonusMap->szMapFileName[0] != '.') {
            Q_snprintf(sz, sizeof(sz), "map %s\n", pBonusMap->szMapFileName);
          } else {
            const char *pchSubDir =
                Q_strnchr(BonusMapsDatabase()->GetPath(), '/',
                          Q_strlen(BonusMapsDatabase()->GetPath()));

            if (pchSubDir) {
              pchSubDir = Q_strnchr(pchSubDir + 1, '/', Q_strlen(pchSubDir));

              if (pchSubDir) {
                ++pchSubDir;
                const char *pchMapFileName = pBonusMap->szMapFileName + 2;
                Q_snprintf(sz, sizeof(sz), "map %s/%s\n", pchSubDir,
                           pchMapFileName);
              }
            }
          }

          engine->ClientCmd_Unrestricted(sz);

          // Close this dialog
          // OnClose();
        }
      }
    }
  } else if (!_stricmp(command, "back")) {
    BonusMapsDatabase()->BackPath();

    // repopulate list with current directory
    BuildMapsList();

    m_pChallengeSelection->RemoveAll();
    m_pChallengeSelection->AddItem(
        "<Select A Challenge>",
        new KeyValues("ChallengeSelection", "challenge", -1));

    RefreshDialog(NULL);

    m_pGameList->MoveScrollBarToTop();
  } else if (!_stricmp(command, "ImportBonusMaps")) {
    if (m_hImportBonusMapsDialog == NULL) {
      m_hImportBonusMapsDialog =
          new FileOpenDialog(NULL, "#GameUI_ImportBonusMaps", true);
      m_hImportBonusMapsDialog->AddFilter("*.bmz", "#GameUI_BMZ_Files", true);
      m_hImportBonusMapsDialog->AddActionSignalTarget(this);
    }
    m_hImportBonusMapsDialog->DoModal(false);
    m_hImportBonusMapsDialog->Activate();
  } else {
    BaseClass::OnCommand(command);
  }
}

//-----------------------------------------------------------------------------
// Purpose: One item has been selected
//-----------------------------------------------------------------------------
void CBonusMapsDialog::OnPanelSelected() {
  CBonusMapPanel *pSelectedBonusMapPanel =
      (CBonusMapPanel *)m_pGameList->GetSelectedPanel();
  BonusMapDescription_t *pMap = BonusMapsDatabase()->GetBonusData(
      pSelectedBonusMapPanel->GetBonusMapListItemID());

  SetControlString("CommentLabel", pMap->szComment);

  // Handle challenge selection box
  int iNumChallenges = 0;

  if (pMap->m_pChallenges) {
    // Get the name of the previously selected challenge so that we can select a
    // matching challenge (if one exists)
    int iSelectedChallenge = GetSelectedChallenge();

    m_pChallengeSelection->RemoveAll();

    m_pChallengeSelection->AddItem(
        "<Select A Challenge>",
        new KeyValues("ChallengeSelection", "challenge", -1));

    int iFoundSimilar = 0;

    for (iNumChallenges; iNumChallenges < pMap->m_pChallenges->Count();
         ++iNumChallenges) {
      ChallengeDescription_t *pChallenge =
          &(*pMap->m_pChallenges)[iNumChallenges];
      int iType = iNumChallenges;

      // If the challenge type was specified then use that instead of (legacy)
      // the order the challenges were listed
      if (pChallenge->iType != -1) iType = pChallenge->iType;

      m_pChallengeSelection->AddItem(
          pChallenge->szName,
          new KeyValues("ChallengeSelection", "challenge", iType));

      if (iSelectedChallenge == iNumChallenges)
        iFoundSimilar = iNumChallenges + 1;
    }

    m_pChallengeSelection->ActivateItemByRow(iFoundSimilar);
  }

  if (iNumChallenges > 0) {
    m_pChallengeSelection->SetEnabled(true);
    m_pChallengeSelection->SetVisible(true);
    m_pChallengeSelection->SetNumberOfEditLines(iNumChallenges + 1);
  } else {
    m_pChallengeSelection->SetEnabled(false);
    m_pChallengeSelection->SetVisible(false);
  }

  RefreshDialog(pMap);
}

void CBonusMapsDialog::OnControlModified() {
  CBonusMapPanel *pSelectedBonusMapPanel =
      (CBonusMapPanel *)m_pGameList->GetSelectedPanel();
  BonusMapDescription_t *pMap = BonusMapsDatabase()->GetBonusData(
      pSelectedBonusMapPanel->GetBonusMapListItemID());

  RefreshDialog(pMap);
}

// file selected.  This can only happen when someone selects an image to be
// imported as a spray logo.
void CBonusMapsDialog::OnFileSelected(const char *fullpath) {
  if (fullpath == NULL || fullpath[0] == '\0') return;

  // this can take a while, put up a waiting cursor
  surface()->SetCursor(dc_hourglass);

  ImportZippedBonusMaps(fullpath);

  // change the cursor back to normal
  surface()->SetCursor(dc_user);
}
