text: #include "selhero.h"
  
  #include <algorithm>
  #include <chrono>
  #include <random>
  
  #include "DiabloUI/diabloui.h"
  #include "../../DiabloUI/diabloui.h"
  #include "devilution.h"
  #include "DiabloUI/dialogs.h"
  #include "DiabloUI/scrollbar.h"
  #include "DiabloUI/selyesno.h"
  #include "DiabloUI/selok.h"
  
  namespace dvl {
  
  std::size_t selhero_SaveCount = 0;
  _uiheroinfo selhero_heros[MAX_CHARACTERS];
  _uiheroinfo selhero_heroInfo;
  std::size_t listOffset = 0;
  constexpr std::size_t kMaxViewportItems = 6;
  char textStats[5][4];
  char title[32];
  char selhero_Lable[32];
  char selhero_Description[256];
  int selhero_result;
  bool selhero_endMenu;
  bool selhero_isMultiPlayer;
  bool selhero_navigateYesNo;
  bool selhero_deleteEnabled;
  
  BOOL(*gfnHeroStats)
  (unsigned int, _uidefaultstats *);
  BOOL(*gfnHeroCreate)
  (_uiheroinfo *);
  
  namespace {
  
  UiItem SELHERO_DIALOG[] = {
  	MAINMENU_BACKGROUND,
  	MAINMENU_LOGO,
  	UiArtText(title, { 24, 161, 590, 35 }, UIS_CENTER | UIS_BIG),
  	UiImage(&ArtHero, UI_NUM_CLASSES, { 30, 211, 180, 76 }),
  	UiArtText("Level:", { 39, 323, 110, 21 }, UIS_RIGHT),
  	UiArtText(textStats[0], { 159, 323, 40, 21 }, UIS_CENTER),
  	UiArtText("Strength:", { 39, 358, 110, 21 }, UIS_RIGHT),
  	UiArtText(textStats[1], { 159, 358, 40, 21 }, UIS_CENTER),
  	UiArtText("Magic:", { 39, 380, 110, 21 }, UIS_RIGHT),
  	UiArtText(textStats[2], { 159, 380, 40, 21 }, UIS_CENTER),
  	UiArtText("Dexterity:", { 39, 401, 110, 21 }, UIS_RIGHT),
  	UiArtText(textStats[3], { 159, 401, 40, 21 }, UIS_CENTER),
  	UiArtText("Vitality:", { 39, 422, 110, 21 }, UIS_RIGHT),
  	UiArtText(textStats[4], { 159, 422, 40, 21 }, UIS_CENTER),
  };
  UiImage *SELHERO_DIALOG_HERO_IMG = &SELHERO_DIALOG[3].image;
  
  UiListItem SELLIST_DIALOG_ITEMS[kMaxViewportItems];
  UiItem SELLIST_DIALOG[] = {
  	UiArtText("Select Hero", { 264, 211, 320, 33 }, UIS_CENTER | UIS_BIG),
  	UiList(SELLIST_DIALOG_ITEMS, 265, 256, 320, 26, UIS_CENTER | UIS_MED | UIS_GOLD),
  	MakeScrollBar({ 585, 244, 25, 178 }),
  	UiArtTextButton("OK", &UiFocusNavigationSelect, { 239, 429, 120, 35 }, UIS_CENTER | UIS_BIG | UIS_GOLD),
  	UiArtTextButton("Delete", &selhero_UiFocusNavigationYesNo, { 364, 429, 120, 35 }, UIS_CENTER | UIS_BIG | UIS_DISABLED),
  	UiArtTextButton("Cancel", &UiFocusNavigationEsc, { 489, 429, 120, 35 }, UIS_CENTER | UIS_BIG | UIS_GOLD)
  };
  UiList *SELLIST_DIALOG_LIST = &SELLIST_DIALOG[1].list;
  UiScrollBar *SELLIST_SCROLLBAR = &SELLIST_DIALOG[2].scrollbar;
  UiArtTextButton *SELLIST_DIALOG_DELETE_BUTTON = &SELLIST_DIALOG[4].art_text_button;
  
  UiListItem SELCLAS_DIALOG_ITEMS[] = {
  	{ "Warrior", UI_WARRIOR },
  	{ "Rogue", UI_ROGUE },
  	{ "Sorcerer", UI_SORCERER }
  };
  UiItem SELCLASS_DIALOG[] = {
  	UiArtText("Choose Class", { 264, 211, 320, 33 }, UIS_CENTER | UIS_BIG),
  	UiList(SELCLAS_DIALOG_ITEMS, 264, 285, 320, 33, UIS_CENTER | UIS_MED | UIS_GOLD),
  	UiArtTextButton("OK", &UiFocusNavigationSelect, { 279, 429, 140, 35 }, UIS_CENTER | UIS_BIG | UIS_GOLD),
  	UiArtTextButton("Cancel", &UiFocusNavigationEsc, { 429, 429, 140, 35 }, UIS_CENTER | UIS_BIG | UIS_GOLD)
  };
  
  UiItem ENTERNAME_DIALOG[] = {
  	UiArtText("Enter Name", { 264, 211, 320, 33 }, UIS_CENTER | UIS_BIG),
  	UiEdit(selhero_heroInfo.name, 15, { 265, 317, 320, 33 }, UIS_MED | UIS_GOLD),
  	UiArtTextButton("OK", &UiFocusNavigationSelect, { 279, 429, 140, 35 }, UIS_CENTER | UIS_BIG | UIS_GOLD),
  	UiArtTextButton("Cancel", &UiFocusNavigationEsc, { 429, 429, 140, 35 }, UIS_CENTER | UIS_BIG | UIS_GOLD)
  };
  
  UiListItem SELLOAD_DIALOG_ITEMS[] = {
  	{ "Load Game", 0 },
  	{ "New Game", 1 }
  };
  UiItem SELLOAD_DIALOG[] = {
  	UiArtText("Save File Exists", { 264, 211, 320, 33 }, UIS_CENTER | UIS_BIG),
  	UiList(SELLOAD_DIALOG_ITEMS, 265, 285, 320, 33, UIS_CENTER | UIS_MED | UIS_GOLD),
  	UiArtTextButton("OK", &UiFocusNavigationSelect, { 279, 427, 140, 35 }, UIS_CENTER | UIS_VCENTER | UIS_BIG | UIS_GOLD),
  	UiArtTextButton("Cancel", &UiFocusNavigationEsc, { 429, 427, 140, 35 }, UIS_CENTER | UIS_VCENTER | UIS_BIG | UIS_GOLD)
  };
  
  } // namespace
  
  void selhero_UiFocusNavigationYesNo()
  {
  	if (selhero_deleteEnabled)
  		UiFocusNavigationYesNo();
  }
  
  void selhero_Free()
  {
  	ArtBackground.Unload();
  }
  
  void selhero_SetStats()
  {
  	SELHERO_DIALOG_HERO_IMG->frame = selhero_heroInfo.heroclass;
  	sprintf(textStats[0], "%d", selhero_heroInfo.level);
  	sprintf(textStats[1], "%d", selhero_heroInfo.strength);
  	sprintf(textStats[2], "%d", selhero_heroInfo.magic);
  	sprintf(textStats[3], "%d", selhero_heroInfo.dexterity);
  	sprintf(textStats[4], "%d", selhero_heroInfo.vitality);
  }
  
  namespace {
  
  void selhero_UpdateViewportItems()
  {
  	const std::size_t num_viewport_heroes = std::min(selhero_SaveCount - listOffset, kMaxViewportItems);
  	SELLIST_DIALOG_LIST->length = num_viewport_heroes;
  	for (std::size_t i = 0; i < num_viewport_heroes; i++) {
  		const std::size_t index = i + listOffset;
  		SELLIST_DIALOG_ITEMS[i] = { selhero_heros[index].name, static_cast<int>(index) };
  	}
  	if (num_viewport_heroes < kMaxViewportItems) {
  		SELLIST_DIALOG_ITEMS[num_viewport_heroes] = { "New Hero", static_cast<int>(selhero_SaveCount) };
  		++SELLIST_DIALOG_LIST->length;
  	}
  }
  
  void selhero_ScrollIntoView(std::size_t index)
  {
  	std::size_t new_offset = listOffset;
  	if (index >= listOffset + kMaxViewportItems)
  		new_offset = index - (kMaxViewportItems - 1);
  	if (index < listOffset)
  		new_offset = index;
  	if (new_offset != listOffset) {
  		listOffset = new_offset;
  		selhero_UpdateViewportItems();
  	}
  }
  
  } // namespace
  
  void selhero_List_Init()
  {
  	listOffset = 0;
  	selhero_UpdateViewportItems();
  	UiInitList(0, selhero_SaveCount, selhero_List_Focus, selhero_List_Select, selhero_List_Esc, SELLIST_DIALOG, size(SELLIST_DIALOG), false, selhero_List_DeleteYesNo);
  	UiInitScrollBar(SELLIST_SCROLLBAR, kMaxViewportItems, &listOffset);
  	if (selhero_isMultiPlayer) {
  		strcpy(title, "Multi Player Characters");
  	} else {
  		strcpy(title, "Single Player Characters");
  	}
  }
  
  void selhero_List_Focus(int value)
  {
  	const std::size_t index = static_cast<std::size_t>(value);
  	selhero_ScrollIntoView(index);
  	int baseFlags = UIS_CENTER | UIS_BIG;
  	if (selhero_SaveCount && index < selhero_SaveCount) {
  		memcpy(&selhero_heroInfo, &selhero_heros[index], sizeof(selhero_heroInfo));
  		selhero_SetStats();
  		SELLIST_DIALOG_DELETE_BUTTON->flags = baseFlags | UIS_GOLD;
  		selhero_deleteEnabled = true;
  		return;
  	}
  
  	SELHERO_DIALOG_HERO_IMG->frame = UI_NUM_CLASSES;
  	strcpy(textStats[0], "--");
  	strcpy(textStats[1], "--");
  	strcpy(textStats[2], "--");
  	strcpy(textStats[3], "--");
  	strcpy(textStats[4], "--");
  	SELLIST_DIALOG_DELETE_BUTTON->flags = baseFlags | UIS_DISABLED;
  	selhero_deleteEnabled = false;
  }
  
  bool selhero_List_DeleteYesNo()
  {
  	selhero_navigateYesNo = selhero_deleteEnabled;
  
  	return selhero_navigateYesNo;
  }
  
  void selhero_List_Select(int value)
  {
  	if (static_cast<std::size_t>(value) == selhero_SaveCount) {
  		UiInitList(0, 2, selhero_ClassSelector_Focus, selhero_ClassSelector_Select, selhero_ClassSelector_Esc, SELCLASS_DIALOG, size(SELCLASS_DIALOG));
  		memset(&selhero_heroInfo.name, 0, sizeof(selhero_heroInfo.name));
  		strcpy(title, "New Single Player Hero");
  		if (selhero_isMultiPlayer) {
  			strcpy(title, "New Multi Player Hero");
  		}
  		return;
  	} else if (selhero_heroInfo.hassaved) {
  		UiInitList(0, 1, selhero_Load_Focus, selhero_Load_Select, selhero_List_Init, SELLOAD_DIALOG, size(SELLOAD_DIALOG), true);
  		strcpy(title, "Single Player Characters");
  		return;
  	}
  
  	UiInitList(0, 0, NULL, NULL, NULL, NULL, 0);
  	selhero_endMenu = true;
  }
  
  void selhero_List_Esc()
  {
  	UiInitList(0, 0, NULL, NULL, NULL, NULL, 0);
  	selhero_endMenu = true;
  	selhero_result = SELHERO_PREVIOUS;
  }
  
  void selhero_ClassSelector_Focus(int value)
  {
  	_uidefaultstats defaults;
  	gfnHeroStats(value, &defaults);
  
  	selhero_heroInfo.level = 1;
  	selhero_heroInfo.heroclass = value;
  	selhero_heroInfo.strength = defaults.strength;
  	selhero_heroInfo.magic = defaults.magic;
  	selhero_heroInfo.dexterity = defaults.dexterity;
  	selhero_heroInfo.vitality = defaults.vitality;
  
  	selhero_SetStats();
  }
  
  void selhero_ClassSelector_Select(int value)
  {
  	if (gbSpawned && (value == 1 || value == 2)) {
  		selhero_Free();
  		BlackPalette();
  		UiSelOkDialog(nullptr, "The Rogue and Sorcerer are only available in the full retail version of Diablo. Visit https://www.gog.com/game/diablo to purchase.", false);
  		LoadBackgroundArt("ui_art\\selhero.pcx");
  		selhero_List_Select(selhero_SaveCount);
  		return;
  	}
  
  	strcpy(title, "New Single Player Hero");
  	if (selhero_isMultiPlayer) {
  		strcpy(title, "New Multi Player Hero");
  	}
  	memset(selhero_heroInfo.name, '\0', sizeof(selhero_heroInfo.name));
  #ifdef PREFILL_PLAYER_NAME
  	strcpy(selhero_heroInfo.name, selhero_GenerateName(selhero_heroInfo.heroclass));
  #endif
  	UiInitList(0, 0, NULL, selhero_Name_Select, selhero_Name_Esc, ENTERNAME_DIALOG, size(ENTERNAME_DIALOG));
  }
  
  void selhero_ClassSelector_Esc()
  {
  	if (selhero_SaveCount) {
  		selhero_List_Init();
  		return;
  	}
  
  	selhero_List_Esc();
  }
  
  void selhero_Name_Select(int value)
  {
  
  	if (!UiValidPlayerName(selhero_heroInfo.name)) {
  		selhero_Free();
  		BlackPalette();
  		UiSelOkDialog(title, "Invalid name. A name cannot contain spaces, reserved characters, or reserved words.\n", false);
  		LoadBackgroundArt("ui_art\\selhero.pcx");
  	} else {
  		bool overwrite = true;
  		for (std::size_t i = 0; i < selhero_SaveCount; i++) {
  			if (strcasecmp(selhero_heros[i].name, selhero_heroInfo.name) == 0) {
  				selhero_Free();
  				BlackPalette();
  				char dialogText[256];
  				sprintf(dialogText, "Character already exists. Do you want to overwrite \"%s\"?", selhero_heroInfo.name);
  				overwrite = UiSelHeroYesNoDialog(title, dialogText);
  				LoadBackgroundArt("ui_art\\selhero.pcx");
  				break;
  			}
  		}
  
  		if (overwrite) {
  			if (gfnHeroCreate(&selhero_heroInfo)) {
  				UiInitList(0, 0, NULL, NULL, NULL, NULL, 0);
  				selhero_endMenu = true;
  				return;
  			} else {
  				UiErrorOkDialog("Unable to create character.", SELHERO_DIALOG, size(SELHERO_DIALOG));
  			}
  		}
  	}
  
  	memset(selhero_heroInfo.name, '\0', sizeof(selhero_heroInfo.name));
  #ifdef PREFILL_PLAYER_NAME
  	strcpy(selhero_heroInfo.name, selhero_GenerateName(selhero_heroInfo.heroclass));
  #endif
  	selhero_ClassSelector_Select(selhero_heroInfo.heroclass);
  }
  
  void selhero_Name_Esc()
  {
  	selhero_List_Select(selhero_SaveCount);
  }
  
  void selhero_Load_Focus(int value)
  {
  }
  
  void selhero_Load_Select(int value)
  {
  	UiInitList(0, 0, NULL, NULL, NULL, NULL, 0);
  	selhero_endMenu = true;
  	if (value == 0) {
  		selhero_result = SELHERO_CONTINUE;
  		return;
  	}
  
  	selhero_result = 0;
  }
  
  BOOL SelHero_GetHeroInfo(_uiheroinfo *pInfo)
  {
  	selhero_heros[selhero_SaveCount] = *pInfo;
  	selhero_SaveCount++;
  
  	return true;
  }
  
  BOOL UiSelHeroDialog(
      BOOL (*fninfo)(BOOL (*fninfofunc)(_uiheroinfo *)),
      BOOL (*fncreate)(_uiheroinfo *),
      BOOL (*fnstats)(unsigned int, _uidefaultstats *),
      BOOL (*fnremove)(_uiheroinfo *),
      int *dlgresult,
      char *name)
  {
  	do {
  		LoadBackgroundArt("ui_art\\selhero.pcx");
  		LoadScrollBar();
  
  		selhero_result = *dlgresult;
  		gfnHeroStats = fnstats;
  		gfnHeroCreate = fncreate;
  
  		selhero_navigateYesNo = false;
  
  		selhero_SaveCount = 0;
  		fninfo(SelHero_GetHeroInfo);
  		std::reverse(selhero_heros, selhero_heros + selhero_SaveCount);
  
  		if (selhero_SaveCount) {
  			selhero_List_Init();
  		} else {
  			selhero_List_Select(selhero_SaveCount);
  		}
  
  		selhero_endMenu = false;
  		while (!selhero_endMenu && !selhero_navigateYesNo) {
  			UiRenderItems(SELHERO_DIALOG, size(SELHERO_DIALOG));
  			UiPollAndRender();
  		}
  		BlackPalette();
  		selhero_Free();
  
  		if (selhero_navigateYesNo) {
  			char dialogTitle[32];
  			char dialogText[256];
  			if (selhero_isMultiPlayer) {
  				strcpy(dialogTitle, "Delete Multi Player Hero");
  			} else {
  				strcpy(dialogTitle, "Delete Single Player Hero");
  			}
  			sprintf(dialogText, "Are you sure you want to delete the character \"%s\"?", selhero_heroInfo.name);
  
  			if (UiSelHeroYesNoDialog(dialogTitle, dialogText))
  				fnremove(&selhero_heroInfo);
  		}
  	} while (selhero_navigateYesNo);
  
  	*dlgresult = selhero_result;
  	strcpy(name, selhero_heroInfo.name);
  	heroLevel = selhero_heroInfo.level;
  
  	UnloadScrollBar();
  	return true;
  }
  
  BOOL UiSelHeroSingDialog(
      BOOL (*fninfo)(BOOL (*fninfofunc)(_uiheroinfo *)),
      BOOL (*fncreate)(_uiheroinfo *),
      BOOL (*fnremove)(_uiheroinfo *),
      BOOL (*fnstats)(unsigned int, _uidefaultstats *),
      int *dlgresult,
      char *name,
      int *difficulty)
  {
  	selhero_isMultiPlayer = false;
  	return UiSelHeroDialog(fninfo, fncreate, fnstats, fnremove, dlgresult, name);
  }
  
  BOOL UiSelHeroMultDialog(
      BOOL (*fninfo)(BOOL (*fninfofunc)(_uiheroinfo *)),
      BOOL (*fncreate)(_uiheroinfo *),
      BOOL (*fnremove)(_uiheroinfo *),
      BOOL (*fnstats)(unsigned int, _uidefaultstats *),
      int *dlgresult,
      BOOL *hero_is_created,
      char *name)
  {
  	selhero_isMultiPlayer = true;
  	return UiSelHeroDialog(fninfo, fncreate, fnstats, fnremove, dlgresult, name);
  }
  
  const char *selhero_GenerateName(std::uint8_t hero_class)
  {
  	static const char *const kNames[3][10] = {
  		{
  		    "Aidan",
  		    "Qarak",
  		    "Born",
  		    "Cathan",
  		    "Halbu",
  		    "Lenalas",
  		    "Maximus",
  		    "Vane",
  		    "Myrdgar",
  		    "Rothat",
  		},
  		{
  		    "Moreina",
  		    "Akara",
  		    "Kashya",
  		    "Flavie",
  		    "Divo",
  		    "Oriana",
  		    "Iantha",
  		    "Shikha",
  		    "Basanti",
  		    "Elexa",
  		},
  		{
  		    "Jazreth",
  		    "Drognan",
  		    "Armin",
  		    "Fauztin",
  		    "Jere",
  		    "Kazzulk",
  		    "Ranslor",
  		    "Sarnakyle",
  		    "Valthek",
  		    "Horazon",
  		}
  	};
  	const auto seed = std::chrono::system_clock::now().time_since_epoch().count();
  	std::default_random_engine generator(seed);
  	std::uniform_int_distribution<std::size_t> dist(0, sizeof(kNames[0]) / sizeof(kNames[0][0]) - 1);
  	return kNames[hero_class][dist(generator)];
  }
  
  } // namespace dvl
  