<commit_before>/** Manages asteroid group configs
 * @file PopulationLoader.cs
 * @author %Starstrider42
 * @date Created October 26, 2014
 */

using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;

namespace Starstrider42 {

	namespace CustomAsteroids {

		/** Stores raw asteroid data
		 * 
		 * @invariant At most one instance of this class exists
		 * 
		 * @todo Clean up this class
		 */
		internal class PopulationLoader {
			/** Creates an empty solar system
			 * 
			 * @post No asteroids will be created
			 * 
			 * @exceptsafe Does not throw exceptions.
			 */
			internal PopulationLoader() {
				asteroidSets = new List<Population>();
				untouchedSet = new DefaultAsteroids();
			}

			/** Factory method obtaining Custom Asteroids settings from KSP config state
			 * 
			 * @return A newly constructed PopulationLoader object containing a full list
			 * 		of all valid asteroid groups in asteroid config files
			 * 
			 * @exception System.TypeInitializationException Thrown if the PopulationLoader object 
			 * 		could not be constructed
			 * 
			 * @exceptsafe The program is in a consistent state in the event of an exception
			 * 
			 * @todo Can I make Load() atomic?
			 */
			internal static PopulationLoader Load() {
				try {
					// UrlConfig x;
					// x.parent.fullPath;		// Name of file to write to
					// x.config					// AsteroidSet node

					// Start with an empty population list
					PopulationLoader allPops = new PopulationLoader();

					// Search for populations in all config files
					UrlDir.UrlConfig[] configList = GameDatabase.Instance.GetConfigs("AsteroidSets");
					foreach (UrlDir.UrlConfig curSet in configList) {
						foreach (ConfigNode curNode in curSet.config.nodes) {
							if (curNode.name == "ASTEROIDGROUP") {
								try {
									#if DEBUG
									Debug.Log("[CustomAsteroids]: ConfigNode '" + curNode + "' loaded");
									#endif
									Population newPop = new Population();
									ConfigNode.LoadObjectFromConfig(newPop, curNode);
									allPops.asteroidSets.Add(newPop);
								} catch (TypeInitializationException e) {
									Debug.LogError("[CustomAsteroids]: failed to load population '" + curNode.GetValue("name") + "'");
									Debug.LogException(e);
								}	// Attempt to parse remaining populations
							}
							else if (curNode.name == "DEFAULT") {
								try {
									#if DEBUG
									Debug.Log("[CustomAsteroids]: ConfigNode '" + curNode + "' loaded");
									#endif
									// Construct-and-swap for better exception safety
									DefaultAsteroids oldPop = new DefaultAsteroids();
									ConfigNode.LoadObjectFromConfig(oldPop, curNode);
									allPops.untouchedSet = oldPop;
								} catch (TypeInitializationException e) {
									Debug.LogError("[CustomAsteroids]: failed to load population '" + curNode.GetValue("name") + "'");
									Debug.LogException(e);
								}	// Attempt to parse remaining populations
							}
							// ignore any other nodes present
						}
					}

					#if DEBUG
					foreach (Population x in allPops.asteroidSets) {
						Debug.Log("[CustomAsteroids]: Population '" + x + "' loaded");
					}
					#endif

					if (allPops.asteroidSets.Count == 0) {
						Debug.LogWarning("[CustomAsteroids]: Custom Asteroids could not find any configs in GameData!");
						ScreenMessages.PostScreenMessage(
							"Custom Asteroids could not find any configs in GameData.\nAsteroids will not appear.", 
							10.0f, ScreenMessageStyle.UPPER_CENTER);
					}

					return allPops;
				// No idea what kinds of exceptions are thrown by ConfigNode
				} catch (Exception e) {
					throw new TypeInitializationException("Starstrider42.CustomAsteroids.PopulationLoader", e);
				}
			}

			/** Randomly selects an asteroid population
			 * 
			 * The selection is weighted by the spawn rate of each population; a population with 
			 * 		a rate of 2.0 is twice as likely to be chosen as one with a rate of 1.0.
			 * 
			 * @return A reference to the selected population
			 * 
			 * @exception System.InvalidOperationException Thrown if there are no populations from 
			 * 		which to choose, or if all spawn rates are zero, or if any rate is negative
			 */
			internal Population drawPopulation() {
				try {
					// A typedef! A typedef! My kerbdom for a typedef!
					List<Pair<Population, double>> bins = new List<Pair<Population, double>>();
					bins.Add(new Pair<Population, double>(null, untouchedSet.getSpawnRate()));
					foreach (Population x in asteroidSets) {
						bins.Add(new Pair<Population, double>(x, x.getSpawnRate()));
					}

					return RandomDist.weightedSample(bins);
				} catch (ArgumentException e) {
					throw new InvalidOperationException("[CustomAsteroids]: could not draw population", e);
				}
			}

			/** Returns the total spawn rate of all asteroid populations.
			 * 
			 * @return The sum of all spawn rates for all populations.
			 * 
			 * @exceptsafe Does not throw exceptions
			 */
			internal double getTotalRate() {
				double total = 0.0;
				foreach (Population x in asteroidSets) {
					total += x.getSpawnRate();
				}
				return total;
			}

			/** Returns the name used for asteroids on stock orbits
			 * 
			 * @return The name with which to replace "Ast."
			 * 
			 * @exceptsafe Does not throw exceptions
			 */
			internal string defaultName() {
				return untouchedSet.getAsteroidName();
			}

			/** Debug function for traversing node tree
			 *
			 * @param[in] node The top-level node of the tree to be printed
			 * 
			 * @post The current node and all nodes beneath it are printed, in depth-first order
			 */<commit_msg>Bug fix: total spawn rate should include default population.<commit_after>/** Manages asteroid group configs
 * @file PopulationLoader.cs
 * @author %Starstrider42
 * @date Created October 26, 2014
 */

using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;

namespace Starstrider42 {

	namespace CustomAsteroids {

		/** Stores raw asteroid data
		 * 
		 * @invariant At most one instance of this class exists
		 * 
		 * @todo Clean up this class
		 */
		internal class PopulationLoader {
			/** Creates an empty solar system
			 * 
			 * @post No asteroids will be created
			 * 
			 * @exceptsafe Does not throw exceptions.
			 */
			internal PopulationLoader() {
				asteroidSets = new List<Population>();
				untouchedSet = new DefaultAsteroids();
			}

			/** Factory method obtaining Custom Asteroids settings from KSP config state
			 * 
			 * @return A newly constructed PopulationLoader object containing a full list
			 * 		of all valid asteroid groups in asteroid config files
			 * 
			 * @exception System.TypeInitializationException Thrown if the PopulationLoader object 
			 * 		could not be constructed
			 * 
			 * @exceptsafe The program is in a consistent state in the event of an exception
			 * 
			 * @todo Can I make Load() atomic?
			 */
			internal static PopulationLoader Load() {
				try {
					// UrlConfig x;
					// x.parent.fullPath;		// Name of file to write to
					// x.config					// AsteroidSet node

					// Start with an empty population list
					PopulationLoader allPops = new PopulationLoader();

					// Search for populations in all config files
					UrlDir.UrlConfig[] configList = GameDatabase.Instance.GetConfigs("AsteroidSets");
					foreach (UrlDir.UrlConfig curSet in configList) {
						foreach (ConfigNode curNode in curSet.config.nodes) {
							if (curNode.name == "ASTEROIDGROUP") {
								try {
									#if DEBUG
									Debug.Log("[CustomAsteroids]: ConfigNode '" + curNode + "' loaded");
									#endif
									Population newPop = new Population();
									ConfigNode.LoadObjectFromConfig(newPop, curNode);
									allPops.asteroidSets.Add(newPop);
								} catch (TypeInitializationException e) {
									Debug.LogError("[CustomAsteroids]: failed to load population '" + curNode.GetValue("name") + "'");
									Debug.LogException(e);
								}	// Attempt to parse remaining populations
							}
							else if (curNode.name == "DEFAULT") {
								try {
									#if DEBUG
									Debug.Log("[CustomAsteroids]: ConfigNode '" + curNode + "' loaded");
									#endif
									// Construct-and-swap for better exception safety
									DefaultAsteroids oldPop = new DefaultAsteroids();
									ConfigNode.LoadObjectFromConfig(oldPop, curNode);
									allPops.untouchedSet = oldPop;
								} catch (TypeInitializationException e) {
									Debug.LogError("[CustomAsteroids]: failed to load population '" + curNode.GetValue("name") + "'");
									Debug.LogException(e);
								}	// Attempt to parse remaining populations
							}
							// ignore any other nodes present
						}
					}

					#if DEBUG
					foreach (Population x in allPops.asteroidSets) {
						Debug.Log("[CustomAsteroids]: Population '" + x + "' loaded");
					}
					#endif

					if (allPops.asteroidSets.Count == 0) {
						Debug.LogWarning("[CustomAsteroids]: Custom Asteroids could not find any configs in GameData!");
						ScreenMessages.PostScreenMessage(
							"Custom Asteroids could not find any configs in GameData.\nAsteroids will not appear.", 
							10.0f, ScreenMessageStyle.UPPER_CENTER);
					}

					return allPops;
				// No idea what kinds of exceptions are thrown by ConfigNode
				} catch (Exception e) {
					throw new TypeInitializationException("Starstrider42.CustomAsteroids.PopulationLoader", e);
				}
			}

			/** Randomly selects an asteroid population
			 * 
			 * The selection is weighted by the spawn rate of each population; a population with 
			 * 		a rate of 2.0 is twice as likely to be chosen as one with a rate of 1.0.
			 * 
			 * @return A reference to the selected population
			 * 
			 * @exception System.InvalidOperationException Thrown if there are no populations from 
			 * 		which to choose, or if all spawn rates are zero, or if any rate is negative
			 */
			internal Population drawPopulation() {
				try {
					// A typedef! A typedef! My kerbdom for a typedef!
					List<Pair<Population, double>> bins = new List<Pair<Population, double>>();
					bins.Add(new Pair<Population, double>(null, untouchedSet.getSpawnRate()));
					foreach (Population x in asteroidSets) {
						bins.Add(new Pair<Population, double>(x, x.getSpawnRate()));
					}

					return RandomDist.weightedSample(bins);
				} catch (ArgumentException e) {
					throw new InvalidOperationException("[CustomAsteroids]: could not draw population", e);
				}
			}

			/** Returns the total spawn rate of all asteroid populations.
			 * 
			 * @return The sum of all spawn rates for all populations.
			 * 
			 * @exceptsafe Does not throw exceptions
			 */
			internal double getTotalRate() {
				double total = untouchedSet.getSpawnRate();
				foreach (Population x in asteroidSets) {
					total += x.getSpawnRate();
				}
				return total;
			}

			/** Returns the name used for asteroids on stock orbits
			 * 
			 * @return The name with which to replace "Ast."
			 * 
			 * @exceptsafe Does not throw exceptions
			 */
			internal string defaultName() {
				return untouchedSet.getAsteroidName();
			}

			/** Debug function for traversing node tree
			 *
			 * @param[in] node The top-level node of the tree to be printed
			 * 
			 * @post The current node and all nodes beneath it are printed, in depth-first order
			 */
{'fraction_non_alphanumeric': 0.09112457203055044, 'fraction_numerical': 0.003687121411640769, 'mean_word_length': 2.7277486910994764, 'pattern_counts': {'":': 0, '<': 17, '<?xml version=': 0, '>': 17, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7356197', 'n_tokens_mistral': 4254, 'n_tokens_neox': 2984, 'n_words': 1060}