/// <summary>
        /// Find those step types in the database that are not being used by any workflow.
        /// </summary>
        /// <returns>The total number of orphaned items found</returns>
        private int UpdateOrphanedStepTypes()
        {
            Dictionary <int, int> usedStepTypes = new Dictionary <int, int>();
            IJTXDatabase3         wmxDb         = this.WmxDatabase;
            IJTXConfiguration3    configMgr     = wmxDb.ConfigurationManager as IJTXConfiguration3;
            IJTXWorkflowSet       workflows     = configMgr.Workflows;

            // Iterate through each workflow, building up the list of used steps
            for (int i = 0; i < workflows.Count; i++)
            {
                // Skip over those workflows that aren't in use
                IJTXWorkflow workflow = workflows.get_Item(i);
                if (m_unusedWorkflows.Keys.Contains(workflow.ID))
                {
                    continue;
                }

                // Examine the remaining workflows
                IJTXWorkflowConfiguration workflowCfg = workflows.get_Item(i) as IJTXWorkflowConfiguration;
                int[] stepIds = workflowCfg.GetAllSteps();
                foreach (int stepId in stepIds)
                {
                    int stepTypeId = workflowCfg.GetStep(stepId).StepTypeID;
                    usedStepTypes[stepTypeId] = stepTypeId;
                }
            }

            // Get the complete list of step types in the database
            IJTXStepTypeSet allStepTypeObjs = configMgr.StepTypes;

            // Loop over all of the step types.  For anything whose step type ID is not
            // contained in the "used" list, add it to the "unused" list.
            // If all of the items are used, don't bother trying to add to the
            // unused list.
            if (usedStepTypes.Count != allStepTypeObjs.Count)
            {
                for (int i = 0; i < allStepTypeObjs.Count; i++)
                {
                    IJTXStepType2 stepType   = allStepTypeObjs.get_Item(i) as IJTXStepType2;
                    int           stepTypeId = stepType.ID;
                    if (!usedStepTypes.ContainsKey(stepTypeId))
                    {
                        m_unusedStepTypes[stepTypeId] = stepType.Name;
                    }
                }
            }

            return(m_unusedStepTypes.Count);
        }
        /// <summary>
        /// Find those workflows in the database that are not being used by any job type.
        /// </summary>
        /// <returns>The total number of orphaned items found</returns>
        private int UpdateOrphanedWorkflows()
        {
            Dictionary <int, int> usedWorkflows = new Dictionary <int, int>();
            IJTXDatabase3         wmxDb         = this.WmxDatabase;
            IJTXConfiguration3    configMgr     = wmxDb.ConfigurationManager as IJTXConfiguration3;
            IJTXJobTypeSet        jobTypes      = configMgr.JobTypes;

            // Iterate through each item in the set, building up the list of used types
            for (int i = 0; i < jobTypes.Count; i++)
            {
                // TODO: Do not consider items that we know are not in use

                IJTXJobType3 type = jobTypes.get_Item(i) as IJTXJobType3;
                // job type don't always need to have a workflow
                if (type.Workflow != null)
                {
                    usedWorkflows[type.Workflow.ID] = type.Workflow.ID;
                }
            }

            // Get the complete list of this type of object in the database
            IJTXWorkflowSet workflows = configMgr.Workflows;

            // Loop through the complete list of this object type.  If any of the IDs
            // are not in the "used" list, add that object to the "unused" list.
            // If all of the items are used, don't bother trying to add to the
            // unused list.
            if (usedWorkflows.Count != workflows.Count)
            {
                for (int i = 0; i < workflows.Count; i++)
                {
                    IJTXWorkflow workflow = workflows.get_Item(i) as IJTXWorkflow;
                    if (!usedWorkflows.ContainsKey(workflow.ID))
                    {
                        m_unusedWorkflows[workflow.ID] = workflow.Name;
                    }
                }
            }

            return(m_unusedWorkflows.Count);
        }
        /// <summary>
        /// Find those users in the database who are not being referenced in any way
        /// </summary>
        /// <returns>The total number of orphaned items found</returns>
        private int UpdateOrphanedUsers()
        {
            SortedList <string, string> unusedItems = new SortedList <string, string>();
            IJTXDatabase3      wmxDb     = this.WmxDatabase;
            IJTXConfiguration3 configMgr = wmxDb.ConfigurationManager as IJTXConfiguration3;
            IJTXJobManager     jobMgr    = wmxDb.JobManager;
            IJTXUserSet        allUsers  = configMgr.Users;

            Dictionary <string, string> usedItems = new Dictionary <string, string>();

            // Get all of the users who are members of a group
            IJTXUserGroupSet allGroups = configMgr.UserGroups;

            for (int i = 0; i < allGroups.Count; i++)
            {
                IJTXUserGroup2 group = allGroups.get_Item(i) as IJTXUserGroup2;
                for (int j = 0; j < group.Users.Count; j++)
                {
                    IJTXUser3 user = group.Users.get_Item(j) as IJTXUser3;
                    usedItems[user.UserName] = user.FullName;
                }
            }

            // If necessary, add in the users who have jobs assigned to them
            if (usedItems.Count < allUsers.Count)
            {
                IJTXJobSet allJobs = jobMgr.GetAllJobs();
                for (int i = 0; i < allJobs.Count; i++)
                {
                    IJTXJob3 job = allJobs.get_Item(i) as IJTXJob3;
                    if (job.AssignedType == jtxAssignmentType.jtxAssignmentTypeUser)
                    {
                        IJTXUser3 user = configMgr.GetUser(job.AssignedTo) as IJTXUser3;

                        // It's possible for a user to have a job assigned, but have
                        // already been removed from the DB.  Throw an exception in
                        // this case, as the DB needs to be cleaned up.
                        if (user == null)
                        {
                            throw new WmauException(WmauErrorCodes.C_USER_NOT_FOUND_ERROR);
                        }
                        usedItems[user.UserName] = user.FullName;
                    }
                }
            }

            // If necessary, add in the users who have a job type's default assignment
            // set to them
            if (usedItems.Count < allUsers.Count)
            {
                IJTXJobTypeSet allJobTypes = configMgr.JobTypes;
                for (int i = 0; i < allJobTypes.Count; i++)
                {
                    // TODO: Exclude orphaned job types

                    IJTXJobType3 jobType = allJobTypes.get_Item(i) as IJTXJobType3;
                    if (jobType.DefaultAssignedType == jtxAssignmentType.jtxAssignmentTypeUser)
                    {
                        IJTXUser3 user = configMgr.GetUser(jobType.DefaultAssignedTo) as IJTXUser3;

                        // It's possible for a user to have a job assigned, but have
                        // already been removed from the DB.  Throw an exception in
                        // this case, as the DB needs to be cleaned up.
                        if (user == null)
                        {
                            throw new WmauException(WmauErrorCodes.C_USER_NOT_FOUND_ERROR);
                        }
                        usedItems[user.UserName] = user.FullName;
                    }
                }
            }

            // If necessary, add in the users who have steps assigned to them
            // by default
            if (usedItems.Count < allUsers.Count)
            {
                IJTXWorkflowSet allWorkflows = configMgr.Workflows;
                for (int i = 0; i < allWorkflows.Count; i++)
                {
                    // Skip over unused workflows
                    IJTXWorkflow workflow = allWorkflows.get_Item(i);
                    if (m_unusedWorkflows.Keys.Contains(workflow.ID))
                    {
                        continue;
                    }

                    // Examine the other items
                    IJTXWorkflowConfiguration workflowCfg = allWorkflows.get_Item(i) as IJTXWorkflowConfiguration;
                    int[] workflowStepIds = workflowCfg.GetAllSteps();
                    foreach (int j in workflowStepIds)
                    {
                        IJTXStep3 step = workflowCfg.GetStep(j) as IJTXStep3;
                        if (step.AssignedType == jtxAssignmentType.jtxAssignmentTypeUser)
                        {
                            IJTXUser3 user = configMgr.GetUser(step.AssignedTo) as IJTXUser3;

                            // It's possible for a user to have a job assigned, but have
                            // already been removed from the DB.  Throw an exception in
                            // this case, as the DB needs to be cleaned up.
                            if (user == null)
                            {
                                throw new WmauException(WmauErrorCodes.C_USER_NOT_FOUND_ERROR);
                            }
                            usedItems[user.UserName] = user.FullName;
                        }
                    }
                }
            }

            // Loop over all the users in the DB, looking for anything
            // that we didn't identify as "in use"
            for (int i = 0; i < allUsers.Count; i++)
            {
                IJTXUser3 item = allUsers.get_Item(i) as IJTXUser3;
                if (!usedItems.ContainsKey(item.UserName))
                {
                    m_unusedUsers[item.UserName] = item.FullName;
                }
            }

            return(m_unusedUsers.Count);
        }
        /// <summary>
        /// Find those status types in the database that are not being used by any step.
        /// </summary>
        /// <returns>The total number of orphaned items found</returns>
        private int UpdateOrphanedStatusTypes()
        {
            Dictionary <int, int> usedStatusTypes = new Dictionary <int, int>();
            IJTXDatabase3         wmxDb           = this.WmxDatabase;

            string[] coreStatusNames =
            {
                C_STATUS_CLOSED,
                C_STATUS_CREATED,
                C_STATUS_DONE_WORKING,
                C_STATUS_READY_TO_WORK,
                C_STATUS_WORKING
            };

            IJTXConfiguration3 configMgr = wmxDb.ConfigurationManager as IJTXConfiguration3;

            // Iterate through each job currently in the system, adding the status types used
            // by these jobs
            IJTXJobSet jobs = wmxDb.JobManager.GetAllJobs();

            for (int i = 0; i < jobs.Count; i++)
            {
                IJTXJob3 job = jobs.get_Item(i) as IJTXJob3;
                if (job.Status != null)
                {
                    usedStatusTypes[job.Status.ID] = job.Status.ID;
                }
            }

            // Iterate through each workflow, building up the list of used status types
            // based on the statuses that are assigned by each step
            IJTXWorkflowSet workflows = configMgr.Workflows;

            for (int i = 0; i < workflows.Count; i++)
            {
                // Skip over those workflows that aren't in use
                IJTXWorkflow workflow = workflows.get_Item(i);
                if (m_unusedWorkflows.Keys.Contains(workflow.ID))
                {
                    continue;
                }

                // Examine the remaining workflows
                IJTXWorkflowConfiguration workflowCfg = workflows.get_Item(i) as IJTXWorkflowConfiguration;
                int[] stepIds = workflowCfg.GetAllSteps();
                foreach (int stepId in stepIds)
                {
                    IJTXStep3 step = workflowCfg.GetStep(stepId) as IJTXStep3;
                    usedStatusTypes[step.StatusID] = step.StatusID;
                }
            }

            // Add the status types used by Workflow Manager itself
            foreach (string s in coreStatusNames)
            {
                IJTXStatus2 status = configMgr.GetStatus(s) as IJTXStatus2;

                // Avoid problems if someone deleted one of these mandatory types from the database
                if (status != null)
                {
                    int id = status.ID;
                    usedStatusTypes[id] = id;
                }
            }

            // Get the complete list of status types in the database
            IJTXStatusSet allStatusTypes = configMgr.Statuses;

            // Loop over all of the status types.  For anything whose ID is not contained
            // in the "used" list, add it to the "unused" list. If all of the items are
            // used, don't bother trying to add to the unused list.
            if (usedStatusTypes.Count != allStatusTypes.Count)
            {
                for (int i = 0; i < allStatusTypes.Count; i++)
                {
                    IJTXStatus2 statusType = allStatusTypes.get_Item(i) as IJTXStatus2;
                    if (!usedStatusTypes.ContainsKey(statusType.ID))
                    {
                        m_unusedStatusTypes[statusType.ID] = statusType.Name;
                    }
                }
            }

            return(m_unusedStatusTypes.Count);
        }
        /// <summary>
        /// Helper function that runs all of those checks that operate on each step in
        /// every workflow; intended to make the checks slightly more efficient by running
        /// through them all at once rather than looping through all of the elements
        /// multiple times
        /// </summary>
        /// <param name="msgs">Add any GP messages to this object</param>
        /// <param name="errorCount">Counter used to track the number of problems found</param>
        /// <param name="logFileWriter">Object used to write error descriptions to a text file</param>
        private void ExecuteWorkflowStepChecks(IGPMessages msgs, ref int errorCount, StreamWriter logFileWriter)
        {
            // Only continue executing this function if needed
            if (!m_flagInvalidStepAssign &&
                !m_flagUnassignedSteps &&
                !m_flagZeroPctSteps &&
                !m_flagDifferingStepNames)
            {
                return;
            }

            IJTXConfiguration3     configMgr  = this.WmxDatabase.ConfigurationManager as IJTXConfiguration3;
            IJTXConfigurationEdit2 configEdit = configMgr as IJTXConfigurationEdit2;

            // Put the items into a sorted list in order to make the output easier to
            // read/follow
            IJTXWorkflowSet allWorkflows = configMgr.Workflows;
            SortedList <string, IJTXWorkflow> allWorkflowsSorted = new SortedList <string, IJTXWorkflow>();

            for (int i = 0; i < allWorkflows.Count; i++)
            {
                allWorkflowsSorted[allWorkflows.get_Item(i).Name] = allWorkflows.get_Item(i);
            }

            // Iterate through each item
            foreach (IJTXWorkflow workflow in allWorkflowsSorted.Values)
            {
                IJTXWorkflowConfiguration workflowCfg = workflow as IJTXWorkflowConfiguration;
                int[] allStepIds = workflowCfg.GetAllSteps();
                foreach (int j in allStepIds)
                {
                    IJTXStep3 step       = workflowCfg.GetStep(j) as IJTXStep3;
                    string    assignedTo = step.AssignedTo;

                    // Check for any default step types with an invalid step assignment
                    if (m_flagInvalidStepAssign)
                    {
                        if (step.AssignedType == jtxAssignmentType.jtxAssignmentTypeUser && configMgr.GetUser(assignedTo) == null)
                        {
                            string message = "Workflow '" + workflow.Name + "', step '" +
                                             step.StepName + "' assigned to unknown user '" + assignedTo + "'";
                            RecordMessage(message, msgs, logFileWriter);
                            errorCount++;
                        }
                        else if (step.AssignedType == jtxAssignmentType.jtxAssignmentTypeGroup && configMgr.GetUserGroup(assignedTo) == null)
                        {
                            string message = "Workflow '" + workflow.Name + "', step '" +
                                             step.StepName + "' assigned to unknown group '" + assignedTo + "'";
                            RecordMessage(message, msgs, logFileWriter);
                            errorCount++;
                        }
                    }

                    // Check for any steps that have not been assigned to a group or user
                    if (m_flagUnassignedSteps)
                    {
                        if (step.AssignedType == jtxAssignmentType.jtxAssignmentTypeUnassigned)
                        {
                            string message = "Workflow '" + workflow.Name + "', step '" +
                                             step.StepName + "' is unassigned";
                            RecordMessage(message, msgs, logFileWriter);
                            errorCount++;
                        }
                    }

                    // Check for any steps whose "post-complete" percentage is 0
                    if (m_flagZeroPctSteps)
                    {
                        if (step.DefaultPercComplete < double.Epsilon)
                        {
                            string message = "Workflow '" + workflow.Name + "', step '" +
                                             step.StepName + "' sets percent complete to 0";
                            RecordMessage(message, msgs, logFileWriter);
                            errorCount++;
                        }
                    }

                    // Check for any steps whose descriptions in a workflow does not match
                    // the underlying step type name
                    if (m_flagDifferingStepNames)
                    {
                        IJTXStepType2 stepType = configMgr.GetStepTypeByID(step.StepTypeID) as IJTXStepType2;
                        if (!step.StepName.Equals(stepType.Name))
                        {
                            string message = "Workflow '" + workflow.Name + "', step name '" +
                                             step.StepName + "' does not match step type name '" + stepType.Name + "'";
                            RecordMessage(message, msgs, logFileWriter);
                            errorCount++;
                        }
                    }
                } // end for each step
            }     // end for each workflow
        }