/// <summary>
 /// Record all preferred crew assignments for the ship.
 /// </summary>
 /// <param name="construct"></param>
 private void LogPreferredAssignments(ShipConstruct construct)
 {
     foreach (Part part in construct.Parts)
     {
         Assignment[] assignments = Assignment.GetSlotAssignments(part);
         for (int index = 0; index < assignments.Length; ++index)
         {
             String     message    = Logging.ToString(part) + " slot " + index + ": ";
             Assignment assignment = assignments[index];
             if (assignment == null)
             {
                 message += "unassigned";
             }
             else
             {
                 message += "assign " + assignment;
             }
             Logging.Log(message);
         } // for each crew slot on the part
         foreach (ModuleCrewRequirement requirement in ModuleCrewRequirement.CrewRequirementsOf(part))
         {
             Logging.Log(Logging.ToString(part) + ": require " + requirement);
         }
     } // for each part on the ship
 }
        /// <summary>
        /// Find all professions required by parts on the ship. Key is profession name, value is
        /// the requirement that introduced it.
        /// </summary>
        /// <param name="construct"></param>
        /// <returns></returns>
        private static Dictionary <string, ModuleCrewRequirement> FindRequiredProfessions(ShipConstruct construct)
        {
            Dictionary <string, ModuleCrewRequirement> requirements = new Dictionary <string, ModuleCrewRequirement>();

            foreach (Part part in construct.parts)
            {
                foreach (ModuleCrewRequirement requirement in part.Modules.GetModules <ModuleCrewRequirement>())
                {
                    string profession = requirement.profession;
                    if ((profession == null) || (profession.Length == 0))
                    {
                        continue;
                    }
                    if (requirements.ContainsKey(profession))
                    {
                        ModuleCrewRequirement previousRequirement = requirements[profession];
                        if (requirement.importance > previousRequirement.importance)
                        {
                            requirements[profession] = requirement; // it's more important, replace it
                        }
                        else if (requirement.importance == previousRequirement.importance)
                        {
                            if (requirement.minimumLevel > previousRequirement.minimumLevel)
                            {
                                // They're equally important, but one of them needs a higher level. Go with the lower one.
                                requirements[profession] = requirement;
                            }
                        }
                    }
                    else
                    {
                        // first requirement for this profession, add it
                        requirements[profession] = requirement;
                    }
                }
            }
            return(requirements);
        }
        /// <summary>
        /// As needed, assign additional crew members to fulfill part requirements.
        /// </summary>
        /// <param name="crewables"></param>
        /// <param name="construct"></param>
        private static void FulfillPartRequirements(CrewableList crewables, ShipConstruct construct)
        {
            // Find any professions that are required on the ship
            Dictionary <string, ModuleCrewRequirement> requirements = FindRequiredProfessions(construct);

            if (requirements.Count == 0)
            {
                return;                          // there aren't any requirements
            }
            // We can ignore any that are already provided for
            List <string> ignoreList = new List <string>();

            foreach (string requiredProfession in requirements.Keys)
            {
                int requiredLevel = requirements[requiredProfession].minimumLevel;
                if (HasProfession(crewables.All, requiredProfession, requiredLevel))
                {
                    ignoreList.Add(requiredProfession);
                }
            }
            foreach (string ignoreProfession in ignoreList)
            {
                requirements.Remove(ignoreProfession);
            }
            if (requirements.Count == 0)
            {
                return;                          // all requirements already taken care of
            }
            // We now have a set of required professions that we want to have on board,
            // but haven't yet satisfied. There might not be enough slots to hold them all,
            // so build a prioritized list with the most-desired profession first.
            List <String> prioritizedRequirements = new List <string>(requirements.Keys);

            if (prioritizedRequirements.Count > 1)
            {
                // Sort our remaining requirements in descending order of importance
                Comparison <string> byPriority = (profession1, profession2) =>
                {
                    // First, sort by *declared* importance from the parts. i.e. if the
                    // Mystery Goo asks for a scientist and assigns importance 0, and the
                    // ISRU asks for an engineer and assigns importance 1, then the ISRU wins.
                    int importance1 = requirements[profession1].importance;
                    int importance2 = requirements[profession2].importance;
                    if (importance1 > importance2)
                    {
                        return(-1);
                    }
                    if (importance2 > importance1)
                    {
                        return(1);
                    }
                    // In case of a tie (e.g. one part asks for a scientist with importance 1
                    // and the other asks for an engineer with importance 1), then pick the
                    // profession that's "better".
                    importance1 = ProfessionImportance(profession1);
                    importance2 = ProfessionImportance(profession2);
                    if (importance1 > importance2)
                    {
                        return(-1);
                    }
                    if (importance2 > importance1)
                    {
                        return(1);
                    }
                    return(0);
                };
                prioritizedRequirements.Sort(byPriority);
            }

            // Now go through our prioritized profession requirements and try to find
            // an empty slot (and available unassigned crew member) to satisfy each one.
            foreach (string requiredProfession in prioritizedRequirements)
            {
                ModuleCrewRequirement requirement = requirements[requiredProfession];
                string          part = Logging.ToString(requirement.part);
                ProtoCrewMember highest;
                ProtoCrewMember lowest;
                if (FindHighestLowestAvailable(requiredProfession, requirement.minimumLevel, crewables, out highest, out lowest))
                {
                    // Got a crew member to fulfill the requirement. In the case of pilots,
                    // we want the lowest-level possible (to leave the highest freed up to
                    // satisfy SAS requirements for other ships). But for anyone else, we
                    // want the highest available.
                    ProtoCrewMember crew = PILOT_PROFESSION.Equals(requiredProfession) ? lowest : highest;
                    // Is there a command slot available?
                    CrewSlot slot = Crewable.FindEmptySlot(crewables.Command);
                    if (slot == null)
                    {
                        // okay then, how about a non-command slot?
                        slot = Crewable.FindEmptySlot(crewables.All);
                    }
                    if (slot == null)
                    {
                        Logging.Warn("No open slot is available to assign a " + requirement.Description + " to operate " + part);
                    }
                    else
                    {
                        slot.Occupant = crew;
                        Logging.Log("Assigning " + Logging.ToString(crew) + " to operate " + part);
                    }
                }
                else
                {
                    // there's nobody to fill the slot
                    Logging.Warn("No " + requirement.Description + " is available to operate " + part + ", not assigning anyone");
                }
            } // for each required profession
        }