Declarative-Languages / GHGReports / Program.cs
Program.cs
Raw
/*
 * Author:          Essam Fahmy
 * Program:         GHGReports
 * File:            Program.cs
 * Date:            August 7, 2023
 * Course:          INFO-3138
 * Description:     A console app that displays data about green house gases in Canada from an xml file.
 *                  User can use settings to display specific data about green house gases in Canada
 *                  such as start year and end years of data as well as specific regions and ghg sources.
 */

using System.Xml;
using System.Xml.XPath;

namespace GHGReports
{
    internal class Program
    {
        // Global variables to hold the data and settings file paths

        const string DataFilePath = "..\\..\\..\\ghg-canada.xml";
        const string SettingsFilePath = "..\\..\\..\\settings.xml";

        // Global variables to hold the start year, end year, region, and ghg source settings

        private static int startYear;
        private static int endYear;
        private static string region;
        private static string ghgSource;

        /* 
         *     Name: Main
         *  Purpose: This is the main entry point of the program. 
         *           We will intialize and call required functions through here.
         *  Accepts: Nothing
         *  Returns: Nothing
         */

        static void Main()
        {
            // Try catch block to handle exceptions

            try
            {
                // While loop to continuously display menu until user quits

                while (true)
                {
                    // Creating XmlDocument object and loading the data using DataFilePath

                    XmlDocument doc = new XmlDocument();
                    doc.Load(DataFilePath);

                    // Calling LoadSettings() to load the settings data into the global variables

                    LoadSettings();

                    // Clearing console and display main menu with current settings

                    Console.Clear();
                    Console.WriteLine("----------------------------------------------------------------------");
                    Console.WriteLine("|            Greenhouse Gas Emissions in Canada ~ Main Menu          |");
                    Console.WriteLine("----------------------------------------------------------------------");
                    Console.WriteLine($"| (1) To adjust the range of years (currently {startYear} to {endYear})");
                    Console.WriteLine($"| (2) To select a different region (currently {region})");
                    Console.WriteLine($"| (3) To select a different GHG source (currently {ghgSource})");
                    Console.WriteLine($"| (4) To learn about {region}'s emissions");
                    Console.WriteLine($"| (5) To learn about the {ghgSource}'s emissions");
                    Console.WriteLine("| (X) To quit");
                    Console.WriteLine("----------------------------------------------------------------------");

                    // Gaining user selection

                    Console.Write("\nEnter your selection: ");
                    string choice = Console.ReadLine() ?? "";
                    int numericChoice = 0;

                    // Handling user selection and calling appropriate methods or outputting error if selection was invalid

                    if (int.TryParse(choice, out numericChoice))
                    {
                        switch (numericChoice)
                        {
                            case 1:
                                numericChoice = 0;
                                Console.Write("\nStarting year (1990 to 2019): ");
                                choice = Console.ReadLine() ?? "";
                                while (int.TryParse(choice, out numericChoice) == false || numericChoice < 1990 || numericChoice > 2019)
                                {
                                    Console.WriteLine("ERROR: Starting year must be an integer between 1990 and 2019.");
                                    Console.Write("\nStarting year (1990 to 2019): ");
                                    choice = Console.ReadLine() ?? "";
                                }

                                startYear = numericChoice;

                                Console.Write("\nEnding year (1990 to 2019): ");
                                choice = Console.ReadLine() ?? "";
                                while (int.TryParse(choice, out numericChoice) == false || numericChoice < startYear || numericChoice > 2019 || numericChoice > startYear + 4)
                                {
                                    Console.WriteLine($"ERROR: Ending year must be an integer between {startYear} and {startYear + 4}.");
                                    Console.Write("\nStarting year (1990 to 2019): ");
                                    choice = Console.ReadLine() ?? "";
                                }

                                endYear = numericChoice;
                                SaveSettings();
                                Console.WriteLine("Press any key to continue...");
                                Console.ReadKey();
                                break;
                            case 2:
                                RegionMenu(doc);
                                break;
                            case 3:
                                SourceMenu(doc);
                                break;
                            case 4:
                                RegionReport(doc);
                                break;
                            case 5:
                                SourceReport(doc);
                                break;
                            default:
                                Console.WriteLine("Invalid input");
                                break;
                        }
                    }
                    else
                    {
                        if (choice.ToLower() == "x")
                        {
                            return;
                        }
                        else
                        {
                            Console.WriteLine("Invalid input");
                        }
                    }
                }
            }
            catch (XmlException err)
            {
                Console.WriteLine("\nXML ERROR: " + err.Message);
            }
            catch (XPathException err)
            {
                Console.WriteLine("\nXPATH ERROR: " + err.Message);
            }
            catch (Exception err)
            {
                Console.WriteLine("\nERROR: " + err.Message);
            }
        }

        /* 
         *     Name: RegionMenu
         *  Purpose: Displays the regions and gains user selection for the region they want to display data for
         *  Accepts: XmlDocument doc
         *  Returns: Nothing
         */

        private static void RegionMenu(XmlDocument doc)
        {
            // Clearing console

            Console.Clear();

            // Selecting region nodes

            XmlNodeList? regions = doc.SelectNodes("/ghg-canada/region");
            List<string> regionNames = new List<string>();

            Console.WriteLine("Select a geographical region:\n");

            // Outputting each region name and adding them to list

            foreach (XmlNode region in regions)
            {
                string regionName = region.Attributes["name"]?.InnerText;
                if (!regionNames.Contains(regionName))
                {
                    regionNames.Add(regionName);
                    Console.WriteLine($"({regionNames.Count}) {regionName}");
                }
            }

            // Gaining user input

            Console.Write("\nEnter a source #: ");
            string choice = Console.ReadLine() ?? "";
            int numericChoice = 0;

            // Checking user input

            while (int.TryParse(choice, out numericChoice) == false || numericChoice < 1 || numericChoice > regionNames.Count)
            {
                Console.WriteLine($"Invalid input. Please enter a number from 1-{regionNames.Count}.");
                Console.Write("\nEnter a source #: ");
                choice = Console.ReadLine() ?? "";
            }

            // Setting newly selected region

            region = regionNames[numericChoice - 1];

            // Calling SaveSettings() to save new settings

            SaveSettings();
        }


        /* 
         *     Name: SourceMenu
         *  Purpose: Displays the sources and gains user selection for the source they want to display data for
         *  Accepts: XmlDocument doc
         *  Returns: Nothing
         */
        private static void SourceMenu(XmlDocument doc)
        {
            // Clearing console

            Console.Clear();

            // Selecting source nodes

            XmlNodeList? sources = doc.SelectNodes("/ghg-canada/region/source");
            List<string> sourceDescriptions = new List<string>();

            Console.WriteLine("Select a GHG source:\n");

            // Outputting each source description and adding them to list

            foreach (XmlNode source in sources)
            {
                string sourceDescription = source.Attributes["description"]?.InnerText;
                if (!sourceDescriptions.Contains(sourceDescription))
                {
                    sourceDescriptions.Add(sourceDescription);
                    Console.WriteLine($"({sourceDescriptions.Count}) {sourceDescription}");
                }
            }

            // Gaining user input

            Console.Write("\nEnter a source #: ");
            string choice = Console.ReadLine() ?? "";
            int numericChoice = 0;

            // Checking user input

            while (int.TryParse(choice, out numericChoice) == false || numericChoice < 1 || numericChoice > sourceDescriptions.Count)
            {
                Console.WriteLine($"Invalid input. Please enter a number from 1-{sourceDescriptions.Count}.");
                Console.Write("\nEnter a source #: ");
                choice = Console.ReadLine() ?? "";
            }

            // Setting newly selected region

            ghgSource = sourceDescriptions[numericChoice - 1];

            // Calling SaveSettings() to save new settings

            SaveSettings();
        }

        /* 
         *     Name: LoadSettings
         *  Purpose: Loads settings data from the settings xml file and initializes the settings global variables
         *  Accepts: Nothing
         *  Returns: Nothing
         */
        private static void LoadSettings()
        {
            // Creating XmlDocument object and loading the settings xml file data using the SettingsFilePath

            XmlDocument doc = new XmlDocument();
            doc.Load(SettingsFilePath);

            // Selecting each setting data

            XmlNode? startYearNode = doc.SelectSingleNode("/settings/startYear");
            XmlNode? endYearNode = doc.SelectSingleNode("/settings/endYear");
            XmlNode? regionNode = doc.SelectSingleNode("/settings/region");
            XmlNode? ghgSourceNode = doc.SelectSingleNode("/settings/ghgSource");

            // Setting each setting global variable with its respective data

            startYear = int.TryParse(startYearNode?.InnerText, out var parsedStartYear) ? parsedStartYear : 0;
            endYear = int.TryParse(endYearNode?.InnerText, out var parsedEndYear) ? parsedEndYear : 0;
            region = regionNode?.InnerText ?? "";
            ghgSource = ghgSourceNode?.InnerText ?? "";
        }

        /* 
         *     Name: SaveSettings
         *  Purpose: Saves the current settings global variables to the settings xml file
         *  Accepts: Nothing
         *  Returns: Nothing
         */
        private static void SaveSettings()
        {
            // Creating XmlDocument object and loading the settings xml file data using the SettingsFilePath

            XmlDocument doc = new XmlDocument();
            doc.Load(SettingsFilePath);

            // Selecting each setting data

            XmlNode? startYearNode = doc.SelectSingleNode("/settings/startYear");
            XmlNode? endYearNode = doc.SelectSingleNode("/settings/endYear");
            XmlNode? regionNode = doc.SelectSingleNode("/settings/region");
            XmlNode? ghgSourceNode = doc.SelectSingleNode("/settings/ghgSource");

            // Setting each node with its respective data using our setting global variables

            if (startYearNode != null)
                startYearNode.InnerText = startYear.ToString();

            if (endYearNode != null)
                endYearNode.InnerText = endYear.ToString();

            if (regionNode != null)
                regionNode.InnerText = region;

            if (ghgSourceNode != null)
                ghgSourceNode.InnerText = ghgSource;

            // Saving new XML file data to the SettingsFilePath

            doc.Save(SettingsFilePath);
        }

        /* 
         *     Name: RegionReport
         *  Purpose: To display the report/data for the selected region
         *  Accepts: XmlDocument doc
         *  Returns: Nothing
         */
        private static void RegionReport(XmlDocument doc)
        {
            // Clearing console

            Console.Clear();

            // Outputting title

            Console.WriteLine("--------------------------------------------------------------------------");
            Console.WriteLine($"|       Greenhouse Gas Emissions (Megatonnes) ~ {region,-25}|");
            Console.WriteLine("--------------------------------------------------------------------------");

            // Selecting the selected region node from our XmlDocument

            XmlNode? regionNode = doc.SelectSingleNode($"/ghg-canada/region[@name='{region}']");

            // Selecting the sources in the region

            XmlNodeList? sources = regionNode.SelectNodes("source");

            // Creating a list to hold each year to display

            List<string> years = new List<string>();

            for (int i = startYear; i <= endYear; i++)
            {
                years.Add(i.ToString());
            }

            // Printing table header and years

            Console.Write("| Source                   ");
            foreach (string year in years)
            {
                Console.Write($" {year,8}");
            }
            Console.WriteLine(" |");
            Console.WriteLine("--------------------------------------------------------------------------");

            // Printing emissions data for each source with proper formatting

            foreach (XmlNode source in sources)
            {
                string sourceDescription = source.Attributes["description"]?.InnerText;

                // Checking length of source description and truncating it if needed

                if (sourceDescription.Length > 25)
                {
                    sourceDescription = sourceDescription.Substring(0, 22) + "...";
                }

                // Outputting source description

                Console.Write($"| {sourceDescription,-25}");

                // Outputting source data for each year

                foreach (string year in years)
                {
                    XmlNode? emission = source.SelectSingleNode($"emissions[@year='{year}']");

                    string value = emission?.InnerText ?? "-";

                    // Rounding to 2 decimal places and outputting

                    if (double.TryParse(value, out double parsedValue))
                    {
                        double roundedValue = Math.Round(parsedValue, 2);
                        Console.Write($" {roundedValue,8}");
                    }
                    else
                    {
                        Console.Write($" {value,8}");
                    }
                }

                Console.WriteLine(" |");
            }

            Console.WriteLine("--------------------------------------------------------------------------");

            // Lets user see data before continuing

            Console.WriteLine("\nPress any key to continue...");
            Console.ReadKey();
        }

        /* 
         *     Name: SourceReport
         *  Purpose: To display the report/data for the selected source for each region
         *  Accepts: XmlDocument doc
         *  Returns: Nothing
         */

        private static void SourceReport(XmlDocument doc)
        {
            // Clearing console

            Console.Clear();

            // Outputting title

            Console.WriteLine("--------------------------------------------------------------------------");
            Console.WriteLine($"|       Greenhouse Gas Emissions (Megatonnes) ~ {(ghgSource.Length > 23 ? ghgSource.Substring(0, 23) + ".." : ghgSource),-25}|");
            Console.WriteLine("--------------------------------------------------------------------------");
            Console.Write("| Region                   ");

            // Selecting all regions

            XmlNodeList? regions = doc.SelectNodes("/ghg-canada/region");

            // Creating a list to hold each year to display

            List<string> years = new List<string>();

            for (int i = startYear; i <= endYear; i++)
            {
                years.Add(i.ToString());
            }

            // Printing table header and years

            foreach (string year in years)
            {
                Console.Write($" {year,8}");
            }
            Console.WriteLine(" |");

            Console.WriteLine("--------------------------------------------------------------------------");

            // Printing emissions data for each region with proper formatting

            foreach (XmlNode regionNode in regions)
            {
                string regionName = regionNode.Attributes["name"]?.InnerText;

                // Checking length of region name and truncating it if needed

                Console.Write($"| {(regionName.Length > 23 ? regionName.Substring(0, 23) + ".." : regionName),-25}");

                // Outputting region source data for each year

                foreach (string year in years)
                {
                    double emissionValue = 0;
                    XmlNodeList? sources = regionNode.SelectNodes("source");
                    foreach (XmlNode source in sources)
                    {
                        if (source.Attributes["description"]?.InnerText == ghgSource)
                        {
                            XmlNode emission = source.SelectSingleNode($"emissions[@year='{year}']");
                            if (double.TryParse(emission?.InnerText, out double value))
                            {
                                // Rounding to 2 decimal places

                                emissionValue = Math.Round(value, 2);

                            }
                        }
                    }

                    // Outputting emission value

                    Console.Write($" {emissionValue,8}");
                }

                Console.WriteLine(" |");
            }

            Console.WriteLine("--------------------------------------------------------------------------");

            // Lets user see data before continuing

            Console.WriteLine("\nPress any key to continue...");
            Console.ReadKey();
        }
    }
}